// **********************************************************************************************************
//    Message.cs
//    5/18/2022
//    NGI - Next Generation Interceptor
//    
//    Contract No. HQ0856-21-C-0003/1022000209
//    
//    THIS DOCUMENT DOES NOT CONTAIN TECHNOLOGY OR TECHNICAL DATA CONTROLLED UNDER EITHER THE U.S. 
//    INTERNATIONAL TRAFFIC IN ARMS REGULATIONS OR THE U.S. EXPORT ADMINISTRATION REGULATIONS.
//    
//    RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION PROPRIETARY TO RAYTHEON 
//    COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. 
//    DISCLOSURE TO UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO RAYTHEON 
//    COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS CONTENTS SHALL BE FURNISHED OR DISCLOSED 
//    TO OR COPIED OR USED BY PERSONS OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF 
//    RAYTHEON COMPANY.
//    
//    UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
//    
//    DESTRUCTION NOTICE: FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN DOD 5220.22-M, 
//    NATIONAL INDUSTRIAL SECURITY PROGRAM OPERATING MANUAL, FEBRUARY 2006, 
//    INCORPORATING CHANGE 1, MARCH 28, 2013, CHAPTER 5, SECTION 7, OR DODM 5200.01-VOLUME 3, 
//    DOD INFORMATION SECURITY PROGRAM: PROTECTION OF CLASSIFIED INFORMATION, ENCLOSURE 3, 
//    SECTION 17. FOR CONTROLLED UNCLASSIFIED INFORMATION FOLLOW THE PROCEDURES IN DODM 5200.01-VOLUME 4, 
//    INFORMATION SECURITY PROGRAM: CONTROLLED UNCLASSIFIED INFORMATION.
//    
//    CONTROLLED BY: MISSILE DEFENSE AGENCY
//    CONTROLLED BY: GROUND-BASED MIDCOURSE DEFENSE PROGRAM OFFICE
//    CUI CATEGORY: CTI 
//    DISTRIBUTION/DISSEMINATION CONTROL: F 
//    POC: Alex Kravchenko (1118268)
// **********************************************************************************************************
// Ignore Spelling: Instru

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.XPath;

namespace Raytheon.Common.Coe
{
   public class Message : IEnumerable, ICloneable
   {
      public const string PADDING_ITEM_NAME = "****************";

      public string Name { get { return _name; } set { _name = value; } }
      public string InstruLabel => _instruLabel;

      private string _name;
      private string _label;
      public string Label { get => _label; }

      private string _hexLabel;

      private string _instruLabel;
      private string _adapationStoreLabel;
      private coeDataInterchange.FormatPacketType _packet = null;

      //private Logger Log;
      private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
      private XPathNavigator Messages;

      public MessageData[] MessageDataArray;

      private int _packing = 8;
      private uint _count = 0;
      private readonly int _extraFieldCount = 3;

      private ulong _sendSecond = 0;
      private uint _sendFracOfSec = 0;
      private string _mDomain = "0";

      private MessageXmlDocument m_Icd = null;

      //Message Maps to support faster creation.
      //We only want to parse the XML once.
      private Dictionary<string, Message> m_MsgNameMap = null;
      private Dictionary<string, Message> m_MsgLabelMap = null;

      // Used to protect the critical section
      private static readonly object m_LockObj = new object();

      // Default values
      public static string IntegerDefault = "0";
      public static string FloatDefault = "0.00";
      public static string HexXDefault = "0x0";
      public static string HexBDefault = "0x0";
      public static string BinaryDefault = "0";
      public static string BitFieldDefault = "0";
      public static string StringDefault = "";
      public static string SubimageDefault = "c:\\image.bmp";

      public bool LogData { get; set; }

      // Default Logger object
      //public static Logger m_DefaultLog = new Logger();

      private Message()
      {
         LogData = true;
      }
      private Message(MessageXmlDocument MessageDatabase)
      {
         LogData = true;
         m_Icd = MessageDatabase;

         // Create the static maps if necessary
         if ((m_MsgNameMap == null) || (m_MsgLabelMap == null))
         {
            m_MsgNameMap = new Dictionary<string, Message>();
            m_MsgLabelMap = new Dictionary<string, Message>();
         }
      }

      public Message(string messageName, MessageXmlDocument messageDatabase, bool logData = true)
      {
         LogData = logData;
         m_Icd = messageDatabase;

         // Create the static maps if necessary
         if ((m_MsgNameMap == null) || (m_MsgLabelMap == null))
         {
            m_MsgNameMap = new Dictionary<string, Message>();
            m_MsgLabelMap = new Dictionary<string, Message>();
         }

         if (m_MsgNameMap.ContainsKey(messageName) == true)
         {
            RetrieveClone((Message)m_MsgNameMap[messageName]);
         }
         else
         {
            messageDatabase.LogData = LogData;
            string messageSpecificationString = messageDatabase.GetMessage(messageName);
            if (messageSpecificationString == null)
            {
               throw new Exception(messageName + " Message not found in the ICD.");
            }
            else
            {
               Messages = messageDatabase.CreateNavigator();
               _name = messageName;

               ParseMessageString(messageSpecificationString);

               if (LogData)
                  _logger.Debug("Loading Message Data...");

               Message clone = (Message)Clone();
               if (m_MsgNameMap.ContainsKey(_name) == false)
               {
                  m_MsgNameMap.Add(_name, clone);
               }
               else
               {
                  throw new Exception("Message <" + _name + "> is defined multiple times in the ICD.");
               }

               if (_label != null)
               {
                  if (m_MsgLabelMap.ContainsKey(_label) == false)
                  {
                     m_MsgLabelMap.Add(_label, clone);
                  }
                  else
                  {
                     m_MsgNameMap.Remove(_name);
                     throw new Exception("Message <" + _name + "> and Message <" + m_MsgLabelMap[_label]._instruLabel +
                                         "> are both defined with Label " + _label + " in the ICD.");
                  }
               }
            }
         }
      }

      /// <summary>
      /// This constructor populates the Message based on the Message Label
      /// instead of the Message Name
      /// </summary>
      /// <param name="messageDatabase"></param>
      /// <param name="messageLabel"></param>
      public Message(MessageXmlDocument messageDatabase, string messageLabel, bool logData = true)
      {
         LogData = logData;
         m_Icd = messageDatabase;

         // Create the static maps if necessary
         if ((m_MsgNameMap == null) || (m_MsgLabelMap == null))
         {
            m_MsgNameMap = new Dictionary<string, Message>();
            m_MsgLabelMap = new Dictionary<string, Message>();
         }

         if (m_MsgLabelMap.ContainsKey(messageLabel) == true)
         {
            RetrieveClone((Message)m_MsgLabelMap[messageLabel]);
         }
         else
         {
            var messageSpecificationString = messageDatabase.GetMessageFromLabel(messageLabel);
            if (messageSpecificationString == null)
            {
               throw new Exception("Message with label " + messageLabel + " not found in ICD");
            }
            else
            {
               Messages = messageDatabase.CreateNavigator();

               ParseMessageString(messageSpecificationString);

               if (LogData)
                  _logger.Debug("Loading Message Data...");

               Message clone = (Message)Clone();
               if (m_MsgNameMap.ContainsKey(_name) == false)
               {
                  m_MsgNameMap.Add(_name, clone);
               }
               if (m_MsgLabelMap.ContainsKey(_label) == false)
               {
                  m_MsgLabelMap.Add(_label, clone);
               }
            }
         }
      }

      public MessageXmlDocument ICD
      {
         get { return m_Icd; }
      }

      public string SendTime
      {
         get
         {
            double frac = _sendFracOfSec;
            frac /= 0x100000000;
            string time = _sendSecond.ToString() + frac.ToString(".00000000000000");
            return time;
         }
      }

      public ulong SendSecond
      {
         get { return _sendSecond; }
         set { _sendSecond = value; }
      }

      public uint SendSecondFraction
      {
         get { return _sendFracOfSec; }
         set { _sendFracOfSec = value; }
      }

      public string Domain
      {
         get { return _mDomain; }
         set { _mDomain = value; }
      }

      public static void SetDefaults
         (
          string integerDefault, string floatDefault,
          string hexXDefault, string hexBDefault,
          string binaryDefault, string bitFieldDefault,
          string stringDefault, string subimageDefault
         )
      {
         IntegerDefault = integerDefault;
         FloatDefault = floatDefault;
         HexXDefault = hexXDefault;
         HexBDefault = hexBDefault;
         BinaryDefault = binaryDefault;
         BitFieldDefault = bitFieldDefault;
         StringDefault = stringDefault;
         SubimageDefault = subimageDefault;
      }

      public void Default()
      {
         SetEnumerationType(EnumerationType.ALL_NODES);
         foreach (MessageData data in this)
         {
            if ((data.isArray != true) &&
                (data.isArrayOfStructures != true) &&
                (data.isStructure != true))
            {
               if ((data.FieldDefaultValue != null) && (data.FieldDefaultValue.Trim() != ""))
               {
                  data.FieldValue = data.FieldDefaultValue;
               }
               else if ((data.FieldMinValue != null) && (data.FieldMinValue.Trim() != ""))
               {
                  data.FieldValue = data.FieldMinValue;
               }
               else if ((data.FieldMaxValue != null) && (data.FieldMaxValue.Trim() != ""))
               {
                  data.FieldValue = data.FieldMaxValue;
               }
               else
               {
                  // Get the defaults from the Injection
                  // pane of the options window
                  if (data.FieldInstruType == "I")
                     data.FieldValue = IntegerDefault;
                  else if (data.FieldInstruType == "F")
                     data.FieldValue = FloatDefault;
                  else if (data.FieldInstruType == "X")
                     data.FieldValue = HexXDefault;
                  else if (data.FieldInstruType == "B")
                     data.FieldValue = HexBDefault;
                  else if (data.FieldInstruType == "N")
                     data.FieldValue = BinaryDefault;
                  else if (data.FieldInstruType == "T")
                     data.FieldValue = BitFieldDefault;
                  else if (data.FieldInstruType == "S")
                     data.FieldValue = StringDefault;
                  else if (data.FieldInstruType == "P")
                     data.FieldValue = SubimageDefault;
                  else
                     data.FieldValue = "";
               }
            }
         }
      }

      public coeDataInterchange.FormatPacketType GetInstruPacket()
      {
         if (_packet == null)
         {
            int count = 0;
            uint length = 1;
            char instru_type;
            uint size;
            uint ctr = 0;
            MessageData lastData = null;
            coeDataInterchange.FormatType lastInstru = null;

            coeDataInterchange.FormatType[] format = new coeDataInterchange.FormatType[GetCountForDataInterchange()];
            SetEnumerationType(Message.EnumerationType.FIELDS |
                                    Message.EnumerationType.EXPAND_ARRAYS |
                                    Message.EnumerationType.EXPAND_STRUCTURES);

            foreach (MessageData data in this)
            {
               if (ctr++ < _extraFieldCount) // Skip $msg_domain, $msg_label and $msg_send_time
                  continue;

               // When handling bit fields, we do not know if we want to move to the
               // next byte (Index) until we examine the next element.
               //
               // ex.  unsigned x:4; unsigned y:8;  Both x and y operate on the same
               // byte, but we don't know the same byte is being operated on until
               // we see the declaration of y.
               //
               // Therefore, we are going to increase the index by staying one declaration
               // ahead of the Index increase.  If this declaration is operating on a new
               // byte, then add the size of the previous byte.  The last declaration
               // will then be added on the end to get the appropriate size.
               if (lastData != null)
               {
                  if (IncreaseIndex(data))
                  {
                     length = 1;
                     size = GetTypeSize(lastData.FieldType, lastData.isEnum);
                     instru_type = lastData.FieldInstruType[0];
                     if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
                     {
                        // Strings are handled individually
                        length = (uint)lastData.arrayLength;
                     }
                     if (lastData.FieldInstruType == "P")
                     {
                        size = lastData.imagePixelSize;
                        length = lastData.imageHeight * lastData.imageWidth;
                        instru_type = 'X';
                     }

                     format[count] =
                           new coeDataInterchange.FormatType(lastData.FieldName,
                                                          length,
                                                          size,
                                                          instru_type);

                     if (lastData.bitMask != 0)
                     {
                        if (lastInstru != null)
                        {
                           format[count].m_Repetition = 0;
                           format[count].m_FormatLength = GetNumBitsInMask(lastData.bitMask);
                           format[count].m_Format = 't';
                           lastInstru.m_Repetition++;
                           lastInstru = null;
                        }
                        else
                        {
                           format[count].m_Repetition = 1;
                           format[count].m_Format = 'T';

                           format[count + 1] =
                                 new coeDataInterchange.FormatType(lastData.FieldName,
                                                                0,
                                                                GetNumBitsInMask(lastData.bitMask),
                                                                't');
                           count++;
                        }
                     }

                     count++;
                  }
                  else
                  {
                     if (lastInstru == null)
                     {
                        format[count] =
                              new coeDataInterchange.FormatType(lastData.FieldName,
                                                             0,
                                                             GetTypeSize(lastData.FieldType, lastData.isEnum),
                                                             'T');
                        lastInstru = format[count];
                        count++;
                     }

                     format[count] =
                           new coeDataInterchange.FormatType(lastData.FieldName,
                                                          0,
                                                          GetNumBitsInMask(lastData.bitMask),
                                                          't');
                     lastInstru.m_Repetition++;
                     count++;
                  }
               }
               lastData = data;
            }

            if (lastData != null) // The message has 1 or more elements
            {
               // Add the last element to the size
               length = 1;
               size = GetTypeSize(lastData.FieldType, lastData.isEnum);
               instru_type = lastData.FieldInstruType[0];
               if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
               {
                  // Strings are handled individually
                  length = (uint)lastData.arrayLength;
               }
               if (lastData.FieldInstruType == "P")
               {
                  size = lastData.imagePixelSize;
                  length = lastData.imageHeight * lastData.imageWidth;
                  instru_type = 'X';
               }

               if (lastData.bitMask == 0)
               {
                  format[count] =
                     new coeDataInterchange.FormatType(lastData.FieldName,
                                                    length,
                                                    size,
                                                    instru_type);
               }
               else // (lastData.bitMask != 0)
               {
                  if (lastInstru == null)
                  {
                     format[count] =
                           new coeDataInterchange.FormatType(lastData.FieldName,
                                                          0,
                                                          size,
                                                          'T');
                     lastInstru = format[count];
                     count++;
                  }

                  format[count] =
                        new coeDataInterchange.FormatType(lastData.FieldName,
                                                       0,
                                                       GetNumBitsInMask(lastData.bitMask),
                                                       't');
                  lastInstru.m_Repetition++;
               }
               _packet = new coeDataInterchange.FormatPacketType(_name, GetMessageSize(), (uint)format.Length, format);
            }
            else // There are no items in the message
            {
               _packet = new coeDataInterchange.FormatPacketType(_name, GetMessageSize(), 0, new coeDataInterchange.FormatType[0]);
            }
         }

         // FOR DEBUG PURPOSES
#if false
               Debug.WriteLine("");
               Debug.WriteLine("-------------------------------------------------------------------------");
               Debug.WriteLine("Message Name = " + Packet.m_Name);
               Debug.WriteLine("");
               foreach (DataInterchange.FormatType f in Packet.m_Format)
                  Debug.WriteLine("\""   + f.m_FieldName    +
                                  "\", " + f.m_Repetition   +
                                  ", "   + f.m_FormatLength +
                                  ", \'" + f.m_Format       +
                                  "\'");
               Debug.WriteLine("-------------------------------------------------------------------------");
#endif

         return _packet;
      }

      private uint GetNumBitsInMask(ulong mask)
      {
         uint count = 0;
         ulong bit = 1;
         for (int i = 0; i < sizeof(ulong) * 8; i++)
         {
            if ((bit & mask) != 0)
            {
               count++;
            }

            bit <<= 1;
         }

         return count;
      }

      private void ParseMessageString(string messageSpecificationString)
      {
         lock (m_LockObj)
         {
            var messageSpecification = new XmlDocument();
            int index = 0;

            messageSpecification.LoadXml(messageSpecificationString);
            var item = messageSpecification.CreateNavigator();

            //Get the type of packing
            var nodeset = item.Select("/packing");
            if (nodeset.MoveNext() == true)
            {
               if (!Parse.Try(nodeset.Current.Value.Trim(), out _packing))
               {
                  switch (nodeset.Current.Value.Trim())
                  {
                     case "char":
                        _packing = 1;
                        break;
                     case "short":
                        _packing = 2;
                        break;
                     case "long":
                        _packing = 4;
                        break;
                     case "double":
                     default:
                        _packing = 8;
                        break;
                  }
               }
            }

            // Three extra fields will be added to the top of every
            // Message in CMIT, $msg_domain, $msg_label, $msg_send_time
            nodeset = item.Select("/message/item|/message/msg_item");
            MessageDataArray = new MessageData[nodeset.Count + _extraFieldCount];
            //MessageData.m_BitCounter = 0; // Reset the bit counter for each new message

            item.MoveToChild("message", "");
            if (item.MoveToChild("name", ""))
            {
               _name = item.Value.Trim();
               item.MoveToParent();
            }
            if (item.MoveToChild("label", ""))
            {
               // Convert the label from whatever format it is in to decimal.
               uint msgLabel = 0;

               _label = NormalizeValue(item.Value.Trim());

               if (Parse.Try(_label, out int temp) == false)
               {
                  throw new Exception("Message Label for " + _name + " could not be evaluated to an integer");
               }

               if (temp < 0)
               {
                  byte[] array = BitConverter.GetBytes(temp);
                  msgLabel = BitConverter.ToUInt32(array, 0);
               }
               else
                  msgLabel = Convert.ToUInt32(temp);

               _label = msgLabel.ToString();
               _hexLabel = msgLabel.ToString("X");

               item.MoveToParent();
            }
            if (item.MoveToChild("instruLabel", ""))
            {
               _instruLabel = item.Value.Trim();
               item.MoveToParent();
            }
            if (item.MoveToChild("adaptationSaveLabel", ""))
            {
               _adapationStoreLabel = item.Value.Trim();
               item.MoveToParent();
            }

            MessageDataArray[index] = new MessageData("$msg_domain",
                                     "unsigned int", Domain, Domain, m_Icd);
            MessageDataArray[index++].FieldInstruType = "X";
            MessageDataArray[index] = new MessageData("$msg_label",
                                     "unsigned int", _label, _label, m_Icd);
            MessageDataArray[index++].FieldInstruType = "X";
            MessageDataArray[index] = new MessageData("$msg_send_time",
                                     "double", null, null, m_Icd);
            MessageDataArray[index++].FieldInstruType = "F";

            while (nodeset.MoveNext())
            {
               AddToMessageData(nodeset.Current.Clone(), "", MessageDataArray, index++, 0);
            }
         }
      }

      private void ParseMessageString(string messageSpecificationString,
                                      string messageSelectionString,
                                      string namePrefix,
                                      MessageData parentField)
      {
         var messageSpecification = new XmlDocument();
         int index = 0;

         messageSpecification.LoadXml(messageSpecificationString);
         XPathNavigator item = messageSpecification.CreateNavigator();

         XPathNodeIterator nodeset = item.Select(messageSelectionString);
         parentField.SetStructureSize(nodeset.Count);

         while (nodeset.MoveNext())
         {
            AddToMessageData(nodeset.Current.Clone(), namePrefix, parentField.MessageArray, index++, parentField.depth + 1);
         }
      }

      private void AddToMessageData(XPathNavigator item,
                                    string namePrefix,
                                    MessageData[] dataArray,
                                    int arrayIndex,
                                    int depth)
      {

         string fieldName = null;
         string fieldType = null;
         string fieldDefaultValue = null;
         XPathNavigator structureDefinition;
         bool isEnum = false;

         int arrayLength;
         LinkedList<int> length = new LinkedList<int>();
         //
         // Extract the name, type, default value, and array length values
         // for the field
         //

         if (item.MoveToChild("name", ""))
         {
            fieldName = item.Value.Trim();
            item.MoveToParent();
         }
         if (item.MoveToChild("item_name", ""))
         {
            fieldName = item.Value.Trim();
            item.MoveToParent();
         }
         if (item.MoveToChild("type", ""))
         {
            fieldType = NormalizeType(item.Value, ref isEnum);
            // remove namespace
            fieldType = Regex.Replace(fieldType, @"[^:]+::([^:]+)", "$1");
            item.MoveToParent();
         }
         if (item.MoveToChild("default", ""))
         {
            fieldDefaultValue = NormalizeValue(item.Value);
            item.MoveToParent();
         }
         else if (item.MoveToChild("minRange", ""))
         {
            fieldDefaultValue = NormalizeValue(item.Value);
            item.MoveToParent();
         }

         //
         // Check to see if this field is a structure by locating the structure
         // definition.
         //

         structureDefinition = Messages.SelectSingleNode("/interface/structure[name='" + fieldType + "']|/interface/message[name='" + fieldType + "']");

         //
         // Create the MessageData object for this field and put it in the array.
         // Set max and min range values if they exist.
         //

         dataArray[arrayIndex] = new MessageData(namePrefix + fieldName,
                                     fieldType, null, fieldDefaultValue, m_Icd, LogData);
         //If Type is an enum sent the isEnum field to true
         if (isEnum)
         {
            dataArray[arrayIndex].isEnum = true;
         }

         XPathNodeIterator nodeSet = item.Select("arrayLength");
         while (nodeSet.MoveNext())
         {
            arrayLength = 0;
            Parse.Try(NormalizeValue(nodeSet.Current.Value), out arrayLength);
            if (arrayLength != 0)
            {
               length.AddFirst(arrayLength);
            }
         }

         if (item.MoveToChild("imageWidth", ""))
         {
            Parse.Try(NormalizeValue(item.Value), out uint width);
            if (width != 0)
            {
               dataArray[arrayIndex].imageWidth = width;
            }
            item.MoveToParent();
         }
         if (item.MoveToChild("imageHeight", ""))
         {
            Parse.Try(NormalizeValue(item.Value), out uint height);
            if (height != 0)
            {
               dataArray[arrayIndex].imageHeight = height;
            }
            item.MoveToParent();
         }
         if (item.MoveToChild("imagePixelSize", ""))
         {
            Parse.Try(NormalizeValue(item.Value), out uint pixelSize);
            if (pixelSize != 0)
            {
               dataArray[arrayIndex].imagePixelSize = pixelSize;
            }
            item.MoveToParent();
         }
         if (item.MoveToChild("maxRange", ""))
         {
            dataArray[arrayIndex].SetMaxValue(item.Value.Trim());
            item.MoveToParent();
         }
         if (item.MoveToChild("minRange", ""))
         {
            dataArray[arrayIndex].SetMinValue(item.Value.Trim());
            item.MoveToParent();
         }

         if (item.MoveToChild("bits", ""))
         {
            Parse.Try(NormalizeValue(item.Value), out int bits);
            dataArray[arrayIndex].SetBitValue(bits);
            item.MoveToParent();
         }
         else
         {
            dataArray[arrayIndex].SetBitValue(0);
         }

         if (item.MoveToChild("instruType", ""))
         {
            dataArray[arrayIndex].SetInstruType(item.Value.Trim());
            if ((dataArray[arrayIndex].FieldInstruType == "S") && (dataArray[arrayIndex].FieldType == "unsigned char"))
            {
               // In CMIT, all strings are of type char.  If the user defines a string of unsigned chars, we
               // can still handle them if type is changed to char.
               dataArray[arrayIndex].FieldType = "char";
            }
            else if ((dataArray[arrayIndex].FieldInstruType == "S") && (dataArray[arrayIndex].FieldType != "char"))
            {
               // If the type is not char, the CMIT will not handle strings.  We will change the instruType
               // to X, so these fields are handled properly.
               dataArray[arrayIndex].FieldInstruType = "X";
            }
            item.MoveToParent();
         }

         // Initialize the image buffer if this is a subimage item
         if ((dataArray[arrayIndex].imageWidth != 0) && (dataArray[arrayIndex].imageHeight != 0))
         {
            dataArray[arrayIndex].imageBufferSize =
                        dataArray[arrayIndex].imageWidth *
                         dataArray[arrayIndex].imageHeight *
                         GetTypeSize(dataArray[arrayIndex].FieldType, dataArray[arrayIndex].isEnum);
            dataArray[arrayIndex].imageBuffer = new byte[dataArray[arrayIndex].imageBufferSize];
            dataArray[arrayIndex].SetInstruType("P");
            dataArray[arrayIndex].SetPixelSize();
         }
         else if (dataArray[arrayIndex].FieldInstruType == "P")
         {
            // If no imageWidth and imageHeight are defined, then
            // this item can not have an instruType of "P"
            dataArray[arrayIndex].FieldInstruType = "X";
         }

         dataArray[arrayIndex].depth = depth;
         //
         // If the field is a singleton, we're done. Other combinations
         // could be an array, a structure, or an array of structures.
         //

         if (length.Count > 0)
         {
            ParseArray(structureDefinition,
                       dataArray[arrayIndex],
                       length);
         }
         else if (structureDefinition != null)
         {
            ParseMessageString(structureDefinition.OuterXml, "/structure/item|/message/item|/structure/struct_item|/message/msg_item", dataArray[arrayIndex].FieldName + ".",
                               dataArray[arrayIndex]);
         }
      }

      private void ParseArray
         (
          XPathNavigator StructureDefinition,
          MessageData DataArray,
          LinkedList<int> ArrayLength
         )
      {
         int length = ArrayLength.Last.Value;
         ArrayLength.RemoveLast();
         if (length > 0)
         {
            // If the array type is an enumeration type, need to make it an integer
            XPathNavigator navigator = m_Icd.CreateNavigator();
            XPathNavigator position;
            try
            {
               position = navigator.SelectSingleNode("/interface/enum[name='" + DataArray.FieldType + "']");
               if (position != null)
               {
                  DataArray.FieldType = "unsigned int";
               }
            }
            catch (Exception e)
            {
               System.Diagnostics.Debug.WriteLine(e.Message);
            }

            DataArray.SetArraySize(length);

            if (ArrayLength.Count != 0)
            {
               for (int index = 0; index < length; index++)
               {
                  ParseArray(StructureDefinition,
                             DataArray.MessageArray[index],
                             ArrayLength);
               }
            }
            else
            {
               if (StructureDefinition != null)
               {
                  ParseMessageString(StructureDefinition.OuterXml, "/structure/item|/message/item|/structure/struct_item|/message/msg_item",
                                     DataArray.MessageArray[0].FieldName + ".",
                                     DataArray.MessageArray[0]);
                  for (int index = 1; index < length; index++)
                  {
                     DataArray.MessageArray[index].isStructure = true;

                     DataArray.MessageArray[index].MessageArray = CloneMessageData(DataArray.MessageArray[0].MessageArray);
                     CorrectClonedArrayIndex(DataArray.MessageArray[index].MessageArray,
                                             DataArray.MessageArray[0].FieldName.Trim(),
                                             DataArray.MessageArray[index].FieldName.Trim());
                  }
                  DataArray.isArrayOfStructures = true;
               }
            }
         }

         ArrayLength.AddFirst(length);
      }

      private void CorrectClonedArrayIndex(MessageData[] array,
                                           string old_str,
                                           string new_str)
      {
         foreach (MessageData data in array)
         {
            // We want to replace the index in the name of the structure.  The "begin" substring
            // ensures we only replace the first instance of the string
            string begin = data.FieldName.Substring(0, new_str.Length);
            begin = begin.Replace(old_str, new_str);

            data.FieldName = begin + data.FieldName.Substring(new_str.Length);
            if (data.MessageArray != null)
            {
               CorrectClonedArrayIndex(data.MessageArray, old_str, new_str);
            }
         }
      }

      internal string NormalizeValue(string value)
      {
         XPathNavigator messagesItem;
         bool iterating;

         value = value.Trim();
         do
         {
            iterating = false;
            if ((value.Length > 0) && (value[0] != '\"') && (value[0] != '\''))
            {
               messagesItem = Messages.SelectSingleNode("/interface/constant[name='" + value + "']|/interface/enum/item[name='" + value + "']");
               if (messagesItem == null)
               {
                  messagesItem = Messages.SelectSingleNode("/interface/constant[name='" + value + "']|/interface/enum/enum_item[name='" + value + "']");
               }
               if (messagesItem != null)
               {
                  messagesItem.MoveToChild("value", "");
                  value = messagesItem.InnerXml;
                  iterating = true;
               }
            }
         } while (iterating);

         return value;
      }

      private string NormalizeType(string value, ref bool isEnum)
      {
         XPathNavigator MessagesItem;
         bool iterating;

         do
         {
            iterating = false;
            MessagesItem = Messages.SelectSingleNode("/interface/typedef[name='" + NormalizeValue(value) + "']");
            if (MessagesItem != null)
            {
               MessagesItem.MoveToChild("value", "");
               value = MessagesItem.InnerXml;
               iterating = true;
            }
            //Check if Value is an enumeration
            MessagesItem = Messages.SelectSingleNode("/interface/enum[name='" + NormalizeValue(value) + "']");
            if (MessagesItem != null)
            {
               isEnum = true;
            }
         } while (iterating);

         return value;
      }

      public void Reset()
      {
         SetEnumerationType(EnumerationType.EXPANDED_FIELDS);
         foreach (MessageData data in this)
         {
            data.FieldValue = null;
            data.usesRegister = false;
         }
      }

      public MessageData Set(string field, string value)
      {
         MessageData LocatedDataItem = null;

         SetEnumerationType(EnumerationType.ALL_NODES);
         foreach (MessageData data in this)
         {
            if (string.Compare(field, data.FieldName) == 0)
            {
               if (data.isArrayOfStructures)
               {
                  ;   // Do nothing in this case
               }
               if (data.isArray || data.isStructure)
               {
                  data.FieldValue = value;
                  if (value.StartsWith("$"))
                  {
                     data.usesRegister = true;
                  }
                  foreach (MessageData ArrayData in data.MessageArray)
                  {
                     if (!ArrayData.isArray && !ArrayData.isArrayOfStructures && !ArrayData.isStructure)
                     {
                        ArrayData.FieldArrayValue = value;
                        if (value.StartsWith("$"))
                        {
                           ArrayData.usesRegister = true;
                        }
                     }
                  }
               }
               else
               {
                  data.FieldValue = value;
                  if (value.StartsWith("$"))
                  {
                     data.usesRegister = true;
                  }
                  LocatedDataItem = data;
               }
               break;
            }
         }
         return LocatedDataItem;
      }

      public MessageData GetItemFromMsg(string name)
      {
         // Add each message item to the tree
         SetEnumerationType(EnumerationType.ALL_NODES);
         foreach (MessageData Data in this)
         {
            // Find the Item that has been selected in current Message
            if (Data.FieldName == name)
            {
               return Data;
            }
         }

         return null;
      }

      /// <summary>
      /// logs message with all the fields
      /// </summary>
      /// <param name="enumerationType"></param>
      /// <param name="direction"></param>
      public void SendToLog(EnumerationType enumerationType = EnumerationType.EXPANDED_FIELDS, MessageDirection direction = MessageDirection.File)
      {
         string directionSign = direction == MessageDirection.File ? "_" : direction == MessageDirection.In ? "<<<" : ">>>";
         _logger.Log(NLog.LogLevel.Info, $"{directionSign} Message Name: {_name}; Label: {_label} (0x{_hexLabel})");
         SetEnumerationType(enumerationType);
         foreach (MessageData data in this)
         {
            string displayFieldValue;
            if (data.FieldName == "$msg_label")
            {
               displayFieldValue = $"{data.FieldValue} (0x{_hexLabel})";
            }
            else
            {
               displayFieldValue = data.FieldValue ?? $"{data.FieldDefaultValue} (D)";
            }
            var spacer = new string(Enumerable.Repeat(' ', data.depth * 4).ToArray());
            _logger.Log(NLog.LogLevel.Info, $"{spacer}{data.FieldName} ({data.FieldType}) = {displayFieldValue}");
         }
      }

      // helps with visual identification in the log of incoming vs outgoing
      public enum MessageDirection
      {
         File,
         In,
         Out
      }

      // This class supplies an enumerator that is capable of iterating over the fields in the
      // message with a variety of options. The SetEnumerationType method sets how the enumerator
      // will iterate over the fields. The enumeration values can be added to derive various
      // combinations of iterations. The values are as follows:
      //    FIELDS               Only non array and structure fields
      //    ARRAY_NODES          The message_data nodes that have isArray set
      //    STRUCTURE_NODES      The message_data nodes that have isStructure set
      //    ARRAY_PRIMITIVE_NODES   Nodes with isArray set that are not arrays of structures
      //    EXPAND_ARRAYS        The array fields linked onto an array node
      //    EXPAND_STRUCTURES    The structure fields linked onto a structure node
      //    TOP_NODES            Top level Fields and array/structure nodes
      //    EXPANDED_FIELDS      All fields, structures and arrays expanded
      //    ALL_NODES            All nodes (the sum of all the above)
      //

      public enum EnumerationType
      {
         FIELDS = 0x01,
         ARRAY_NODES = 0x02,
         STRUCTURE_NODES = 0x04,
         ARRAY_PRIMITIVE_NODES = 0x08,
         EXPAND_ARRAYS = 0x10,
         EXPAND_STRUCTURES = 0x20,
         TOP_NODES = 0x07,
         EXPANDED_FIELDS = 0x31,
         ALL_NODES = 0xFF
      };
      private EnumerationType EnumType = EnumerationType.TOP_NODES;

      public void SetEnumerationType(EnumerationType type)
      {
         EnumType = type;
      }

      public IEnumerator GetEnumerator()
      {
         return new MessageEnumerator(MessageDataArray, EnumType);
      }

      private class MessageEnumerator : IEnumerator
      {

         private int CurrentIndex;
         private bool doExpandArray;
         private readonly EnumerationType enumType = EnumerationType.TOP_NODES;
         private readonly MessageData[] RootMessageArray;
         private MessageData[] CurrentMessageArray;
         private readonly Stack<int> indexStack = new Stack<int>();
         private readonly Stack<MessageData[]> messageStack = new Stack<MessageData[]>();

         private MessageEnumerator()
         {
            Reset();
         }

         public MessageEnumerator(MessageData[] Root, EnumerationType Type)
         {
            RootMessageArray = Root;
            enumType = Type;
            Reset();
         }

         public void Reset()
         {
            CurrentIndex = -1;
            CurrentMessageArray = RootMessageArray;
            doExpandArray = false;
            indexStack.Clear();
            messageStack.Clear();
         }

         public bool MoveNext()
         {
            bool Scanning = true;
            bool Status = true;

            while (Scanning)
            {
               CurrentIndex++;
               //
               // If need to start expanding an array (array or structure), do so
               //
               if (doExpandArray)
               {
                  doExpandArray = false;
                  indexStack.Push(CurrentIndex);
                  messageStack.Push(CurrentMessageArray);
                  CurrentMessageArray = CurrentMessageArray[CurrentIndex - 1].MessageArray;
                  CurrentIndex = -1;
               }
               //
               // Else see if this node needs to be returned
               //
               else
               {
                  //
                  // If at the end of an array, pop the stacks
                  //
                  while (CurrentIndex >= CurrentMessageArray.Length)
                  {
                     if (indexStack.Count == 0)
                     {
                        return false;   // End of iteration
                     }
                     else
                     {
                        CurrentIndex = indexStack.Pop();
                        CurrentMessageArray = messageStack.Pop();
                     }
                  }
                  if (CurrentMessageArray[CurrentIndex].isStructure)
                  {
                     if ((enumType & EnumerationType.STRUCTURE_NODES) != 0)
                     {
                        Scanning = false;
                     }
                     if ((enumType & EnumerationType.EXPAND_STRUCTURES) != 0)
                     {
                        doExpandArray = true;
                     }
                  }
                  else if (CurrentMessageArray[CurrentIndex].isArray)
                  {
                     if ((enumType & EnumerationType.ARRAY_NODES) != 0)
                     {
                        Scanning = false;
                     }
                     if ((enumType & EnumerationType.ARRAY_PRIMITIVE_NODES) != 0)
                     {
                        if (!CurrentMessageArray[CurrentIndex].isArrayOfStructures)
                        {
                           Scanning = false;
                        }
                     }
                     if ((enumType & EnumerationType.EXPAND_ARRAYS) != 0)
                     {
                        doExpandArray = true;
                     }
                  }
                  else
                  {
                     if ((enumType & EnumerationType.FIELDS) != 0)
                     {
                        Scanning = false;
                     }
                  }
               }
            }
            return Status;
         }

         public object Current
         {
            get
            {
               return CurrentMessageArray[CurrentIndex];
            }
         }

      }

      enum TypeSize
      {
         INT = 4,
         UINT = 4,
         CHAR = 1,
         SHORT = 2,
         USHORT = 2,
         UNSIGNED_CHAR = 1,
         BOOL = 1,
         DOUBLE = 8,
         LONGLONG = 8,
         FLOAT = 4,
         ENUM = 4
      }

      public void MsgDataToBuffer(ref byte[] DataBuffer, out uint MessageSize)
      {
         int Index = 0;
         uint pictureSize;
         int Offset = 0;
         MessageData lastData = null;
         byte[] ByteArray;
         int ctr = 0;

         // Data buffer was just passed from a fresh "new byte[]" so all values are guaranteed to
         // be 0 at the beginning.
         //
         // For each element in data element in the message, the string representation of
         // the data will be passed to the BitFieldGeneric class with its bit mask to
         // be converted into a binary value.  The BitFieldGeneric value will then be
         // Bitwise ORed to buffer, allowing bit fields to be handled properly.  This
         // ORed value will then be converted into a byte array and placed in the data
         // buffer for transfer.

         SetEnumerationType(EnumerationType.FIELDS |
                                 EnumerationType.EXPAND_ARRAYS |
                                 EnumerationType.EXPAND_STRUCTURES);

         foreach (MessageData data in this)
         {
            //Handle $msg_domain, $msg_label and $msg_send_time appropriately
            if (ctr == 0) // handle $msg_domain
            {
               Domain = data.FieldValue;
               ctr++;
               continue;
            }
            else if (ctr == 1) // handle $msg_label
            {
               // Do we want the user to be able to change the label on the fly
               //Label = data.FieldValue;
               ctr++;
               continue;
            }
            else if (ctr == 2) // handle $msg_send_time
            {
               ctr++;
               continue;
            }


            // Padding may have FieldValue set to null, change it to ""
            if (data.FieldValue == null)
               data.FieldValue = "";

            // This increases the index into the buffer.  See GetMessageSize()
            // for a detailed explanation of the algorithm
            if (lastData != null)
            {
               if (IncreaseIndex(data))
               {
                  if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
                  {
                     // Strings are handled individually
                     Index += (int)TypeSize.CHAR * lastData.arrayLength;
                  }
                  else if (lastData.FieldInstruType == "P")
                  {
                     pictureSize = GetTypeSize(lastData.FieldType, lastData.isEnum);
                     pictureSize *= lastData.imageWidth * lastData.imageHeight;
                     Index += (int)pictureSize;
                  }
                  else
                  {
                     Index += (int)GetTypeSize(lastData.FieldType, lastData.isEnum);
                  }
               }
            }
            lastData = data;

            // Process each message element based on its type
            if ((data.FieldInstruType == "P") && (data.imageBuffer != null))
            {
               data.imageBuffer.CopyTo(DataBuffer, Index);
            }
            else
            {
               switch (data.FieldType)
               {
                  case "char":
                     decimal ParsedValue;
                     if (data.FieldInstruType == "S")
                     {
                        // If this is a string, copy the entire string
                        for (int i = 0; i < data.arrayLength; i++)
                        {
                           if (i < data.FieldValue.Length)
                           {
                              DataBuffer[Index + i] = (byte)data.FieldValue[i];
                           }
                           else
                           {
                              DataBuffer[Index + i] = 0;
                           }
                        }
                     }
                     else  // This is not a string
                     {
                        if (Parse.Try(data.FieldValue, out ParsedValue))
                        {
                           BitFieldGeneric<sbyte> sbParsedValue = new BitFieldGeneric<sbyte>
                                 (data.FieldValue, data.bitMask);
                           sbParsedValue.BitOR((sbyte)DataBuffer[Index]);
                           DataBuffer[Index] = (byte)sbParsedValue.ToSByte();
                        }
                        else if ((data.FieldValue != null) && (data.FieldValue.Length > 0))
                        {
                           DataBuffer[Index] = (byte)data.FieldValue[0];
                        }
                        else
                        {
                           DataBuffer[Index] = 0;
                        }
                     }
                     break;
                  case "unsigned char":
                  case "unsignedchar":
                  case "uint8_t":
                     if (Parse.Try(data.FieldValue, out ParsedValue))
                     {
                        BitFieldGeneric<byte> sbParsedValue = new BitFieldGeneric<byte>(data.FieldValue, data.bitMask);
                        sbParsedValue.BitOR(DataBuffer[Index]);
                        DataBuffer[Index] = (byte)sbParsedValue.ToSByte();
                     }
                     else if ((data.FieldValue != null) && (data.FieldValue.Length > 0))
                     {
                        DataBuffer[Index] = (byte)data.FieldValue[0];
                     }
                     else
                     {
                        DataBuffer[Index] = 0;
                     }
                     break;
                  case "short":
                     BitFieldGeneric<short> sParsedValue = new BitFieldGeneric<short>(data.FieldValue, data.bitMask);
                     sParsedValue.BitOR(BitConverter.ToInt16(DataBuffer, Index));
                     ByteArray = BitConverter.GetBytes(sParsedValue.ToShort());
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  case "unsigned short":
                  case "unsignedshort":
                  case "uint16_t":
                     BitFieldGeneric<ushort> usParsedValue = new BitFieldGeneric<ushort>(data.FieldValue, data.bitMask);
                     usParsedValue.BitOR(BitConverter.ToUInt16(DataBuffer, Index));
                     ByteArray = BitConverter.GetBytes(usParsedValue.ToUShort());
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  case "int":
                  case "int32_t":
                     BitFieldGeneric<int> iParsedValue = new BitFieldGeneric<int>(data.FieldValue, data.bitMask);
                     iParsedValue.BitOR(BitConverter.ToInt32(DataBuffer, Index));
                     ByteArray = BitConverter.GetBytes(iParsedValue.ToInt());
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  case "unsigned int":
                  case "unsignedint":
                  case "unsigned":
                  case "uint32_t":
                  case "boolean":
                  case "address":
                     BitFieldGeneric<uint> uiParsedValue = new BitFieldGeneric<uint>(data.FieldValue, data.bitMask);
                     uiParsedValue.BitOR(BitConverter.ToUInt32(DataBuffer, Index));
                     ByteArray = BitConverter.GetBytes(uiParsedValue.ToUInt());
                     ByteArray.CopyTo(DataBuffer, Index + Offset);
                     break;
                  case "float":
                     float FlParsedValue;
                     if (!Parse.Try(string.IsNullOrEmpty(data.FieldValue) ? "0" : data.FieldValue, out FlParsedValue))
                     {
                        throw new Exception($"Unable to parse value = {data.FieldValue} ");
                     }
                     ByteArray = BitConverter.GetBytes(FlParsedValue);
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  case "double":
                     double DbParsedValue;
                     if (!Parse.Try(string.IsNullOrEmpty(data.FieldValue) ? "0" : data.FieldValue, out DbParsedValue))
                     {
                        throw new Exception($"Unable to parse value = {data.FieldValue} ");
                     }
                     ByteArray = BitConverter.GetBytes(DbParsedValue);
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  case "long long":
                  case "longlong":
                  case "int64_t":
                     BitFieldGeneric<long> llParsedValue = new BitFieldGeneric<long>(data.FieldValue, data.bitMask);
                     llParsedValue.BitOR(BitConverter.ToInt64(DataBuffer, Index));
                     ByteArray = BitConverter.GetBytes(llParsedValue.ToLong());
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  case "unsigned long long":
                  case "unsignedlonglong":
                  case "uint64_t":
                     BitFieldGeneric<ulong> ullParsedValue = new BitFieldGeneric<ulong>(data.FieldValue, data.bitMask);
                     ullParsedValue.BitOR(BitConverter.ToUInt64(DataBuffer, Index));
                     ByteArray = BitConverter.GetBytes(ullParsedValue.ToULong());
                     ByteArray.CopyTo(DataBuffer, Index);
                     break;
                  default:
                     if (data.isEnum)
                     {
                        data.FieldValue = GetEnumerationValue(data.FieldValue, data.FieldType);
                        BitFieldGeneric<uint> eParsedValue = new BitFieldGeneric<uint>(data.FieldValue, data.bitMask);
                        eParsedValue.BitOR(BitConverter.ToUInt32(DataBuffer, Index));
                        ByteArray = BitConverter.GetBytes(eParsedValue.ToUInt());
                        ByteArray.CopyTo(DataBuffer, Index);
                     }
                     else
                        throw new Exception($"Type {data.FieldType} is invalid in COE message");

                     break;
               }
            }
         }

         if (lastData != null) // Ensure the message has items
         {
            // Add the last element to the size
            if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
            {
               // Strings are handled individually
               Index += (int)TypeSize.CHAR * lastData.arrayLength;
            }
            else if (lastData.FieldInstruType == "P")
            {
               pictureSize = GetTypeSize(lastData.FieldType, lastData.isEnum);
               pictureSize *= lastData.imageWidth * lastData.imageHeight;
               Index += (int)pictureSize;
            }
            else
            {
               Index += (int)GetTypeSize(lastData.FieldType, lastData.isEnum);
            }
         }

         MessageSize = (uint)Index;
      }

      private string GetEnumerationValue(string Value, string Type)
      {
         uint value; // enum value
         string returnValue = "";

         if (Parse.Try(Value, out value) == false)
         {
            Dictionary<string, string> enums = m_Icd.GetEnumerations(Type);
            foreach (KeyValuePair<string, string> pair in enums)
            {
               if (Value == pair.Key)
               {
                  returnValue = pair.Value;
                  break;
               }
            }
         }
         else
         {
            returnValue = value.ToString();
         }

         if (returnValue == "")
         {
            throw new Exception("Unable to parse value = " + Value + ".  No valid enumeration exists.");
         }

         return returnValue;
      }

      public bool BufferToMsgData(byte[] DataBuffer)
      {
         int index = 0;
         MessageData lastData = null;
         int ctr = 0;

         SetEnumerationType(EnumerationType.FIELDS |
                        EnumerationType.EXPAND_ARRAYS |
                        EnumerationType.EXPAND_STRUCTURES);
         foreach (MessageData data in this)
         {
            if (ctr == 0) // handle $msg_domain
            {
               data.FieldValue = Domain;
               ctr++;
               continue;
            }
            else if (ctr == 1) // handle $msg_label
            {
               data.FieldValue = _label;
               ctr++;
               continue;
            }
            else if (ctr == 2) // handle $msg_send_time
            {
               data.FieldValue = SendTime;
               ctr++;
               continue;
            }

            // This increases the index into the buffer.  See GetMessageSize()
            // for a detailed explanation of the algorithm
            if (lastData != null)
            {
               if (IncreaseIndex(data))
               {
                  if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
                  {
                     // Strings are handled individually
                     index += (int)TypeSize.CHAR * lastData.arrayLength;
                  }
                  else if (lastData.FieldInstruType == "P")
                  {
                     uint pictureSize = GetTypeSize(lastData.FieldType, lastData.isEnum);
                     pictureSize *= lastData.imageWidth * lastData.imageHeight;
                     index += (int)pictureSize;
                  }
                  else
                  {
                     index += (int)GetTypeSize(lastData.FieldType, lastData.isEnum);
                  }
               }
            }
            lastData = data;

            // Process each message element based on its type
            if ((data.FieldInstruType == "P") && (data.imageBuffer != null))
            {
               for (int i = 0; i < data.imageBuffer.Length; i++)
               {
                  data.imageBuffer[i] = DataBuffer[index + i];
               }
               data.FieldValue = "--- Subimage --- ";
            }
            else
            {
               switch (data.FieldType)
               {
                  case "char":
                     if (data.FieldInstruType == "S")
                     {
                        // Handle string
                        data.FieldValue = "";
                        for (int i = 0; i < data.arrayLength; i++)
                        {
                           if (DataBuffer[index + i] == 0) break;
                           data.FieldValue += (char)DataBuffer[index + i];
                        }
                     }
                     else
                     {
                        BitFieldGeneric<sbyte> CValue = new BitFieldGeneric<sbyte>
                           ((sbyte)DataBuffer[index], data.bitMask);
                        CValue.ToSigned();
                        data.FieldValue = CValue.ToString();
                     }
                     break;
                  case "unsigned char":
                  case "unsignedchar":
                  case "uint8_t":
                     BitFieldGeneric<byte> SBValue = new BitFieldGeneric<byte>
                             (DataBuffer[index], data.bitMask);
                     data.FieldValue = SBValue.ToString(); /*ACB*/
                     //data.FieldValue = DataBuffer[Index].ToString();
                     break;
                  case "short":
                     BitFieldGeneric<short> SValue = new BitFieldGeneric<short>
                        (BitConverter.ToInt16(DataBuffer, index), data.bitMask);
                     SValue.ToSigned();
                     data.FieldValue = SValue.ToString();
                     break;
                  case "unsigned short":
                  case "unsignedshort":
                  case "uint16_t":
                     BitFieldGeneric<ushort> USValue = new BitFieldGeneric<ushort>
                               (BitConverter.ToUInt16(DataBuffer, index), data.bitMask);
                     data.FieldValue = USValue.ToString();
                     break;
                  case "int":
                  case "int32_t":
                     BitFieldGeneric<int> IValue = new BitFieldGeneric<int>
                        (BitConverter.ToInt32(DataBuffer, index), data.bitMask);
                     IValue.ToSigned();
                     data.FieldValue = IValue.ToString();
                     break;
                  case "unsigned int":
                  case "unsignedint":
                  case "uint32_t":
                  case "unsigned":
                  case "address":
                  case "boolean":
                     BitFieldGeneric<uint> UIValue = new BitFieldGeneric<uint>
                        (BitConverter.ToUInt32(DataBuffer, index), data.bitMask);
                     data.FieldValue = UIValue.ToString();
                     break;
                  case "float":
                     float FValue = BitConverter.ToSingle(DataBuffer, index);
                     data.FieldValue = FValue.ToString();
                     break;
                  case "double":
                     double DValue = BitConverter.ToDouble(DataBuffer, index);
                     data.FieldValue = DValue.ToString();
                     break;
                  case "long long":
                  case "longlong":
                  case "int64_t":
                     BitFieldGeneric<long> LLValue = new BitFieldGeneric<long>
                               (BitConverter.ToInt64(DataBuffer, index), data.bitMask);
                     LLValue.ToSigned();
                     data.FieldValue = LLValue.ToString();
                     break;
                  case "unsigned long long":
                  case "unsignedlonglong":
                  case "uint64_t":
                     BitFieldGeneric<ulong> ULLValue = new BitFieldGeneric<ulong>
                               (BitConverter.ToUInt64(DataBuffer, index), data.bitMask);
                     data.FieldValue = ULLValue.ToString();
                     break;
                  default:
                     if (data.isEnum)
                     {
                        BitFieldGeneric<uint> EnumValue = new BitFieldGeneric<uint>
                           (BitConverter.ToUInt32(DataBuffer, index), data.bitMask);
                        data.FieldValue = EnumValue.ToString();
                     }
                     else
                        throw new Exception($"Type {data.FieldType} is invalid in COE message");
                     break;
               }
            }
         }

         return true;
      }

      /// <summary>
      /// Get parameters in the message
      /// </summary>
      public List<KeyValuePair<string, string>> GetParameters()
      {
         List<KeyValuePair<string, string>> parms = new List<KeyValuePair<string, string>>();

         foreach (var item in MessageDataArray.Where(m => !m.FieldName.StartsWith("$")))
         {
            parms.Add(new KeyValuePair<string, string>(item.FieldName, item.FieldValue));
            if (item.MessageArray != null && item.MessageArray.Any())
            {
               var innerParams = GetParameters(item.MessageArray);
               if (innerParams != null)
               {
                  parms.AddRange(innerParams);
               }
            }
         }

         return parms;
      }

      /// <summary>
      /// Get parameters recursively in the message
      /// </summary>
      private List<KeyValuePair<string, string>> GetParameters(MessageData[] array)
      {
         List<KeyValuePair<string, string>> parms = new List<KeyValuePair<string, string>>();

         foreach (var item in array.Where(m => !m.FieldName.StartsWith("$")))
         {
            parms.Add(new KeyValuePair<string, string>(item.FieldName, item.FieldValue));
            if (item.MessageArray != null && item.MessageArray.Any())
            {
               var innerParams = GetParameters(item.MessageArray);
               if (innerParams != null)
               {
                  parms.AddRange(innerParams);
               }
            }
         }

         return parms;
      }

      internal uint GetCountForDataInterchange()
      {
         uint ctr = 0;
         if (_count == 0)
         {
            MessageData lastData = null;

            SetEnumerationType(Message.EnumerationType.FIELDS |
                                    Message.EnumerationType.EXPAND_ARRAYS |
                                    Message.EnumerationType.EXPAND_STRUCTURES);

            foreach (MessageData data in this)
            {
               if (ctr++ < _extraFieldCount) // Skip $msg_domain, $msg_label and $msg_send_time
                  continue;

               // When handling bit fields, we do not know if we want to move to the
               // next byte (Index) until we examine the next element.
               //
               // ex.  unsigned x:4; unsigned y:8;  Both x and y operate on the same
               // byte, but we don't know the same byte is being operated on until
               // we see the declaration of y.
               //
               // Therefore, we are going to increase the index by staying one declaration
               // ahead of the Index increase.  If this declaration is operating on a new
               // byte, then add the size of the previous byte.  The last declaration
               // will then be added on the end to get the appropriate size.
               if (lastData != null)
               {
                  if (IncreaseIndex(data))
                  {
                     _count++;
                     if (lastData.bitMask != 0)
                     {
                        _count++;
                     }
                  }
                  else
                  {
                     _count++;
                  }
               }
               lastData = data;
            }

            // Add the last element to the size
            _count++;
            if (lastData.bitMask != 0)
            {
               _count++;
            }
         }

         return (uint)_count;
      }

      public uint GetMessageSize()
      {
         uint Index = 0;
         uint pictureSize;
         MessageData lastData = null;
         uint ctr = 0;

         SetEnumerationType(Message.EnumerationType.FIELDS |
                                 Message.EnumerationType.EXPAND_ARRAYS |
                                 Message.EnumerationType.EXPAND_STRUCTURES);

         foreach (MessageData data in this)
         {

            if (ctr++ < _extraFieldCount) // Skip $msg_domain, $msg_label and $msg_send_time
               continue;
            // When handling bit fields, we do not know if we want to move to the
            // next byte (Index) until we examine the next element.
            //
            // ex.  unsigned x:4; unsigned y:8;  Both x and y operate on the same
            // byte, but we don't know the same byte is being operated on until
            // we see the declaration of y.
            //
            // Therefore, we are going to increase the index by staying one declaration
            // ahead of the Index increase.  If this declaration is operating on a new
            // byte, then add the size of the previous byte.  The last declaration
            // will then be added on the end to get the appropriate size.
            if (lastData != null)
            {
               if (IncreaseIndex(data))
               {
                  if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
                  {
                     // Strings are handled individually
                     Index += (uint)TypeSize.CHAR * (uint)lastData.arrayLength;
                  }
                  else if (lastData.FieldInstruType == "P")
                  {
                     pictureSize = GetTypeSize(lastData.FieldType, lastData.isEnum);
                     pictureSize *= lastData.imageWidth * lastData.imageHeight;
                     Index += pictureSize;
                  }
                  else
                  {
                     Index += GetTypeSize(lastData.FieldType, lastData.isEnum);
                  }
               }
            }
            lastData = data;
         }

         if (lastData != null) // Ensure there are items in the message
         {
            // Add the last element to the size
            if ((lastData.FieldType == "char") && (lastData.FieldInstruType == "S"))
            {
               // Strings are handled individually
               Index += (uint)TypeSize.CHAR * (uint)lastData.arrayLength;
            }
            else if (lastData.FieldInstruType == "P")
            {
               pictureSize = GetTypeSize(lastData.FieldType, lastData.isEnum);
               pictureSize *= lastData.imageWidth * lastData.imageHeight;
               Index += pictureSize;
            }
            else
            {
               Index += GetTypeSize(lastData.FieldType, lastData.isEnum);
            }
         }

         return (uint)Index;
      }

      private bool IncreaseIndex(MessageData Data)
      {
         if ((Data.bitMask == 0) || ((Data.bitMask & 1) == 1))
         {
            return true;
         }
         else
         {
            return false;
         }
      }

      static internal uint GetTypeSize(string Type, bool IsEnum, bool logData = true)
      {
         uint size = 4;

         switch (Type)
         {
            case "char":
            case "unsigned char":
            case "unsignedchar":
            case "uint8_t":
               size = (uint)TypeSize.CHAR;
               break;

            case "short":
            case "unsigned short":
            case "unsignedshort":
            case "uint16_t":
               size = (uint)TypeSize.SHORT;
               break;

            case "int":
            case "int32_t":
            case "unsigned int":
            case "unsignedint":
            case "unsigned":
            case "uint32_t":
            case "address":
            case "boolean":
            case "float":
               size = (uint)TypeSize.INT;
               break;

            case "double":
            case "long long":
            case "longlong":
            case "unsigned long long":
            case "unsignedlonglong":
            case "uint64_t":
            case "int64_t":
               size = (uint)TypeSize.LONGLONG;
               break;

            default:
               if (IsEnum == true)
                  size = (uint)TypeSize.ENUM;
               else
               {
                  if (logData)
                     _logger.Debug($"Type {Type} is not recognized as basic type");
               }
               break;
         }

         return size;
      }

      //
      // This function validates that the FieldValue is of the correct type and
      // is within the Max and Min values.  If all the data is valid this
      // function returns true.  If the data is invalid the function returns
      // false and the inValid MessageData elements will have there isValid
      // field set to false.
      //
      public bool ValidateMsgData()
      {
         bool DataValid = true;
         SetEnumerationType(Message.EnumerationType.FIELDS |
                            Message.EnumerationType.EXPAND_ARRAYS |
                            Message.EnumerationType.EXPAND_STRUCTURES);

         foreach (MessageData data in this)
         {
            data.isValid = true;
            switch (data.FieldType)
            {
               case "int":
                  int IntParsedValue;
                  int IMaxValue, IMinValue;
                  if (!Parse.Try(data.FieldValue, out IntParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out IMinValue) &&
                      Parse.Try(data.FieldMaxValue, out IMaxValue))
                  {
                     if (IntParsedValue < IMinValue || IntParsedValue > IMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;

               case "char":
                  char ChParsedValue;
                  byte ByteParsedValue;
                  //If the value can be parsed as a byte assume it is a byte, if not try as char
                  if (Parse.Try(data.FieldValue, out ByteParsedValue))
                  {
                     //TODO:  Validate against Min and Max here
                  }
                  else
                  {
                     if (Parse.Try(data.FieldValue, out ChParsedValue))
                     {
                        //TODO:  Validate against Min and Max here
                     }
                     else
                     {
                        if (data.FieldValue != null)
                        {
                           DataValid = false;
                           data.isValid = false;
                        }
                     }
                  }
                  break;
               case "short":
                  short ShParsedValue;
                  short ShMaxValue, ShMinValue;
                  if (!Parse.Try(data.FieldValue, out ShParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out ShMinValue) &&
                      Parse.Try(data.FieldMaxValue, out ShMaxValue))
                  {
                     if (ShParsedValue < ShMinValue || ShParsedValue > ShMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "unsigned char":
               case "unsignedchar":
                  byte UchParsedValue;
                  byte UchMaxValue, UchMinValue;
                  if (!Parse.Try(data.FieldValue, out UchParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out UchMinValue) &&
                      Parse.Try(data.FieldMaxValue, out UchMaxValue))
                  {
                     if (UchParsedValue < UchMinValue || UchParsedValue > UchMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "unsigned short":
               case "unsignedshort":
                  ushort UshParsedValue;
                  ushort UshMaxValue, UshMinValue;
                  if (!Parse.Try(data.FieldValue, out UshParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out UshMinValue) &&
                      Parse.Try(data.FieldMaxValue, out UshMaxValue))
                  {
                     if (UshParsedValue < UshMinValue || UshParsedValue > UshMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "unsigned int":
               case "unsignedint":
               case "unsigned":
                  uint UiParsedValue;
                  uint UiMaxValue, UiMinValue;
                  if (!Parse.Try(data.FieldValue, out UiParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out UiMinValue) &&
                      Parse.Try(data.FieldMaxValue, out UiMaxValue))
                  {
                     if (UiParsedValue < UiMinValue || UiParsedValue > UiMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "float":
                  float FlParsedValue;
                  float FlMaxValue, FlMinValue;
                  if (!Parse.Try(data.FieldValue, out FlParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out FlMinValue) &&
                      Parse.Try(data.FieldMaxValue, out FlMaxValue))
                  {
                     if (FlParsedValue < FlMinValue || FlParsedValue > FlMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "double":
                  double DbParsedValue;
                  double DbMaxValue, DbMinValue;
                  if (!Parse.Try(data.FieldValue, out DbParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out DbMinValue) &&
                      Parse.Try(data.FieldMaxValue, out DbMaxValue))
                  {
                     if (DbParsedValue < DbMinValue || DbParsedValue > DbMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "long long":
               case "longlong":
                  long LlParsedValue;
                  long LlMaxValue, LlMinValue;
                  if (!Parse.Try(data.FieldValue, out LlParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out LlMinValue) &&
                      Parse.Try(data.FieldMaxValue, out LlMaxValue))
                  {
                     if (LlParsedValue < LlMinValue || LlParsedValue > LlMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "unsigned long long":
               case "unsignedlonglong":
                  ulong ULlParsedValue;
                  ulong ULlMaxValue, ULlMinValue;
                  if (!Parse.Try(data.FieldValue, out ULlParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out ULlMinValue) &&
                      Parse.Try(data.FieldMaxValue, out ULlMaxValue))
                  {
                     if (ULlParsedValue < ULlMinValue || ULlParsedValue > ULlMaxValue)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "boolean":
                  uint BParsedValue;
                  if (!Parse.Try(data.FieldValue, out BParsedValue))
                  {
                     DataValid = false;
                     data.isValid = false;
                     break;
                  }
                  if (Parse.Try(data.FieldMinValue, out UiMinValue) &&
                      Parse.Try(data.FieldMaxValue, out UiMaxValue))
                  {
                     if (BParsedValue < 0 || BParsedValue > 1)
                     {
                        DataValid = false;
                        data.isValid = false;
                     }
                  }
                  break;
               case "address":

                  break;
               default:
                  //This fn will not validate enumerations
                  if (!data.isEnum)
                  {
                     //Data is invalid because it is of an unknown type
                     DataValid = false;
                     data.isValid = false;
                     throw new Exception($"Type {data.FieldType} is invalid in COE message");
                  }
                  break;
            }
         }
         return DataValid;
      }

      public bool CompareFieldValues(Message Msg)
      {
         Msg.SetEnumerationType(EnumerationType.EXPANDED_FIELDS);
         foreach (MessageData Data in Msg)
         {

            foreach (MessageData ThisData in this)
            {
               if (ThisData.FieldName == Data.FieldName)
               {
                  if (ThisData.FieldValue != Data.FieldValue)
                     return false;
                  break;
               }
            }
         }
         //If this function has not returned at this point than the Value fields are equal
         return true;
      }

      #region Serialization/Deserialization for COE send
      public byte[] serialize()
      {
         uint MsgSize = GetMessageSize();
         uint MsgSizeOut = 0;
         byte[] buffer = new byte[MsgSize];

         //Write MessageData to OE Message Buffer
         MsgDataToBuffer(ref buffer, out MsgSizeOut);
         if (MsgSize != MsgSizeOut)
         {
            buffer = null; // error
            throw new Exception("Message " + _name + ": serialization failed");
         }
         return buffer;
      }

      public bool deserialize(byte[] stream)
      {
         return BufferToMsgData(stream);
      }
      #endregion

      #region ICloneable Members

      public void RetrieveClone(Message clone)
      {
         _name = clone._name;
         _label = clone._label;
         Domain = clone.Domain;
         _instruLabel = clone._instruLabel;
         _packet = clone._packet;
         _adapationStoreLabel = clone._adapationStoreLabel;

         //Log = clone.Log;
         Messages = clone.Messages;
         _packing = clone._packing;
         _count = clone._count;
         m_Icd = clone.m_Icd;

         if (clone.MessageDataArray == null)
         {
            MessageDataArray = null;
         }
         else
         {
            MessageDataArray = new MessageData[clone.MessageDataArray.Length];
            for (int i = 0; i < clone.MessageDataArray.Length; i++)
            {
               MessageDataArray[i] = (MessageData)clone.MessageDataArray[i].Clone();
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public object Clone()
      {
         Message clone = new Message
         {
            _name = _name,
            _label = _label,
            Domain = Domain,
            _instruLabel = _instruLabel,
            _adapationStoreLabel = _adapationStoreLabel,
            _packet = _packet,
            /*clone.Log = Log;*/
            Messages = Messages,
            _packing = _packing,
            _count = _count,
            m_Icd = m_Icd,
            MessageDataArray = CloneMessageData(MessageDataArray)
         };
         return clone;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="orig"></param>
      /// <returns></returns>
      private MessageData[] CloneMessageData(MessageData[] orig)
      {
         MessageData[] clone = null;
         if (orig != null)
         {
            clone = new MessageData[orig.Length];
            for (int i = 0; i < orig.Length; i++)
            {
               clone[i] = (MessageData)orig[i].Clone();
            }
         }

         return clone;
      }

      #endregion
   }
}
