// **********************************************************************************************************
//    MessageData.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)
// **********************************************************************************************************
using System;
using System.Collections.Generic;
using System.IO;

namespace Raytheon.Common.Coe
{
   public class MessageData : ICloneable
   {
      public string FieldName;
      public string FieldType;
      public string FieldValue;
      public string FieldArrayValue;
      public string FieldDefaultValue;
      public string FieldMaxValue;
      public string FieldMinValue;
      public string FieldBitValue;
      public string FieldInstruType;
      public string Variable;
      public string MaxOffset;
      public string MinOffset;
      public string VerifyType;
      public bool isSelected;
      public bool isArray;
      public bool isStructure;
      public bool isArrayOfStructures;
      public bool isEnum;
      public bool usesRegister;
      public bool isValid;
      public bool useRange;
      public int arrayLength;
      public uint imageWidth;
      public uint imageHeight;
      public uint imagePixelSize;
      public ulong bitMask;
      public bool expanded;
      public int depth;
      public byte[] imageBuffer;
      public uint imageBufferSize;
      public MessageData[] MessageArray;

      public delegate coe.Status ReadImageDelegate
         (
          string filename,
          uint columns,
          uint rows,
          uint pixel_size,
          byte[] buffer
         );

      private MessageXmlDocument m_Icd;

      internal int m_BitCounter = 0; // used to calculate the bit mask

      internal static Dictionary<string, ReadImageDelegate> m_ReadFunctions = null;

      public bool LogData { get; set; }

      private MessageData() { }
      private MessageData(MessageXmlDocument Icd)
      {
         FieldName = null;
         FieldType = null;
         FieldValue = null;
         FieldArrayValue = null;
         FieldDefaultValue = null;
         FieldMaxValue = null;
         FieldMinValue = null;
         FieldBitValue = null;
         Variable = null;
         MaxOffset = null;
         MinOffset = null;
         VerifyType = null;
         isSelected = false;
         isArray = false;
         isStructure = false;
         isArrayOfStructures = false;
         usesRegister = false;
         useRange = false;
         arrayLength = 0;
         imageWidth = 0;
         imageHeight = 0;
         imagePixelSize = 0;
         bitMask = 0;
         expanded = false;
         depth = 0;
         imageBufferSize = 0;
         imageBuffer = null;

         m_Icd = Icd;
      }

      public MessageData(string fieldname,
                         string fieldtype,
                         string fieldvalue,
                         string fielddefaultvalue,
                         MessageXmlDocument Icd,
                         bool logData = true) :
                         this(Icd)
      {
         FieldName = fieldname;
         FieldType = fieldtype;
         FieldValue = fieldvalue;

         LogData = logData;

         SetInstruType(null);
         SetDefaultValue(fielddefaultvalue);

      }

      public MessageData(string fieldname,
                         string fieldtype,
                         string fieldvalue,
                         string fielddefaultvalue,
                         string fieldmaxvalue,
                         string fieldminvalue,
                         MessageXmlDocument Icd) :
                         this(fieldname, fieldtype, fieldvalue, fielddefaultvalue, Icd)
      {
         SetMaxValue(fieldmaxvalue);
         SetMinValue(fieldminvalue);
      }

      public string FormattedValue
      {
         get { return FormatValue(FieldValue); }
      }
      public string FormattedMinValue
      {
         get { return FormatValue(FieldMinValue); }
      }
      public string FormattedMaxValue
      {
         get { return FormatValue(FieldMaxValue); }
      }

      public static string ImageFileReadTypes
      {
         get
         {
            string fileTypes = "";
            if (m_ReadFunctions == null) return null;
            foreach (string type in m_ReadFunctions.Keys)
            {
               if (fileTypes != "")
                  fileTypes += "|";

               fileTypes += type.ToUpper() + " files (*." + type.ToLower() + ")|*." + type.ToLower();
            }

            return fileTypes;
         }
      }

      public void AddReadExtension(string extension, ReadImageDelegate readFunc)
      {
         if (m_ReadFunctions == null)
            m_ReadFunctions = new Dictionary<string, ReadImageDelegate>();

         if (m_ReadFunctions.ContainsKey(extension))
            m_ReadFunctions[extension] = readFunc;
         else
            m_ReadFunctions.Add(extension, readFunc);
      }

      public void UpdateImage()
      {
         if (FieldInstruType == "P")
         {
            if (File.Exists(FieldValue))
            {
               string extension = FieldValue.Substring(FieldValue.LastIndexOf(".") + 1);
               m_ReadFunctions[extension](FieldValue,
                                          imageWidth,
                                          imageHeight,
                                          imagePixelSize,
                                          imageBuffer);
            }
            else
            {
               // TODO add error message
               //MessageBox.Show("Unable to open file " + FieldValue +
               //                " to populate " + FieldName,
               //                "File Read Error",
               //                MessageBoxButtons.OK,
               //                MessageBoxIcon.Error);
               FieldValue = "";
               for (int i = 0; i < imageBuffer.Length; i++)
               {
                  imageBuffer[i] = 0;
               }
            }
         }
      }


      public void SetDefaultValue(string fieldDefaultValue)
      {
         // Initialize uninitialized value
         if (fieldDefaultValue == null)
         {
            fieldDefaultValue = "";
         }

         if ((FieldType == "char") && (fieldDefaultValue.Contains("\"")))
         {
            FieldInstruType = "S";
         }

         FieldDefaultValue = RemoveCharFromString(fieldDefaultValue, '\"');
      }

      public void SetMaxValue(string fieldmaxvalue)
      {
         if (fieldmaxvalue == null) return; /* Bad argument */

         if ((FieldType == "char") && (fieldmaxvalue.Contains("\"")))
         {
            FieldInstruType = "S";
         }

         FieldMaxValue = RemoveCharFromString(fieldmaxvalue, '\"');
      }

      public void SetMinValue(string fieldminvalue)
      {
         if (fieldminvalue == null) return; /* Bad argument */

         if ((FieldType == "char") && (fieldminvalue.Contains("\"")))
         {
            FieldInstruType = "S";
         }

         FieldMinValue = RemoveCharFromString(fieldminvalue, '\"');
      }

      public void SetBitValue(int bits)
      {
         int size = (int)Message.GetTypeSize(FieldType, isEnum, LogData);

         // Determine the bitMask
         if (bits == 0)
         {
            m_BitCounter = 0;
            FieldBitValue = null;
         }
         else
         {
            FieldBitValue = bits.ToString();

            // If bits overflow across the type boundary, then
            // they start at the next boundary.
            //
            // MSDN : 
            //    "Note that nYear is 8 bits long and would overflow 
            //     the word boundary of the declared type, unsigned short. 
            //     Therefore, it is begun at the beginning of a 
            //     new unsigned short."
            if (m_BitCounter + bits > size * 8)
            {
               m_BitCounter = 0;
            }

            // 2^bits-1 will give a bit mask with bit# of 1's
            // ex: bits = 5, bitMask = 11111
            bitMask = (ulong)Math.Pow(2, (double)bits) - 1;

            // We must slide the bitMask left to put it in place
            bitMask <<= m_BitCounter;
            m_BitCounter += bits;

            // If we have used all the bits in the type that was defined, then 
            // restart the counter
            if (m_BitCounter == size * 8)
               m_BitCounter = 0;
         }

         if (bitMask == 0xffffff)
         {
            bitMask = 0;
         }
      }

      public void SetArraySize(int size)
      {
         char result;

         arrayLength = size; // Save the size

         if (!isArray && !isStructure)
         {
            // Don't handle strings as arrays
            if ((FieldInstruType != "S") && (FieldInstruType != "P"))
            {
               isArray = true;

               MessageArray = new MessageData[size];
               // If the field type is char or unsigned char and the default value  
               // exists and is a string then write one char of the string to 
               // each element of the array
               if ((FieldType == "char" || FieldType == "unsigned char") &&
                    FieldDefaultValue != null &&
                    !Parse.Try(FieldDefaultValue, out result))
               {
                  for (uint index = 0; index < size; index++)
                  {
                     //Only the elements that are required to spell out the string should
                     //receive default values.
                     if (index < FieldDefaultValue.Length)
                     {
                        MessageArray[index] = new MessageData(FieldName + "[" + index + "]",
                                          FieldType, FieldValue, FieldDefaultValue[(int)index].ToString(),
                                          FieldMaxValue, FieldMinValue, m_Icd);
                        MessageArray[index].FieldInstruType = FieldInstruType;
                     }
                     else
                     {
                        MessageArray[index] = new MessageData(FieldName + "[" + index + "]",
                                          FieldType, FieldValue, "0",
                                          FieldMaxValue, FieldMinValue, m_Icd);
                        MessageArray[index].FieldInstruType = FieldInstruType;
                     }
                     MessageArray[index].depth = depth + 1;
                  }
               }
               else
               {
                  for (uint index = 0; index < size; index++)
                  {
                     MessageArray[index] = new MessageData(FieldName + "[" + index + "]",
                                       FieldType, FieldValue, FieldDefaultValue, FieldMaxValue,
                                       FieldMinValue, m_Icd);
                     MessageArray[index].FieldInstruType = FieldInstruType;
                     MessageArray[index].depth = depth + 1;
                  }
               }

            }
         }
      }

      public void SetStructureSize(int size)
      {
         if (!isArray && !isStructure)
         {
            isStructure = true;
            MessageArray = new MessageData[size];
            for (uint index = 0; index < size; index++)
            {
               MessageArray[index] = new MessageData(FieldName + ".", null, null, null, m_Icd);
            }
         }
      }

      internal void SetInstruType(string Type)
      {
         if (Type != null)
         {
            FieldInstruType = Type;

            // Ensure 'S' is used properly
            if ((Type == "S") && (FieldType != "char"))
            {
               return; /* << EXIT >> */
            }
         }
         else
         {
            if ((FieldType != null) &&
                ((FieldType.Trim() == "float") || (FieldType.Trim() == "double")))
            {
               FieldInstruType = "F";
            }
            else
            {
               FieldInstruType = "I";
            }
         }
      }

      internal void SetPixelSize()
      {
         // Only do this if the user did not define a size
         if (imagePixelSize == 0)
         {
            switch (FieldType)
            {
               case "char":
               case "unsigned char":
               case "unsignedchar":
                  imagePixelSize = 1;
                  break;

               case "short":
               case "unsigned short":
               case "unsignedshort":
                  imagePixelSize = 2;
                  break;

               case "int":
               case "unsigned int":
               case "unsignedint":
               case "unsigned":
               case "boolean":
               case "address":
               case "float":
                  imagePixelSize = 4;
                  break;

               case "double":
               case "long long":
               case "longlong":
               case "unsigned long long":
               case "unsignedlonglong":
                  imagePixelSize = 8;
                  break;

               default:
                  if (isEnum)
                  {
                     imagePixelSize = 4;
                  }
                  else // Error case
                  {
                     imagePixelSize = 1;
                  }
                  break;
            }
         }
      }


      private string FormatValue(string Value)
      {
         if ((Value == null) || (Value == ""))
         {
            return "";
         }
         else // Value exists
         {
            if (isEnum == false)
            {
               if (FieldInstruType == "I")
               {
                  // This is being represented as a decimal
                  if (Parse.Try(Value, out long dec) == true)
                  {
                     return dec.ToString();
                  }
               }
               else if (FieldInstruType == "F")
               {
                  // This is being represented as floating point
                  if (Parse.Try(Value, out double flt) == true)
                  {
                     return flt.ToString();
                  }
               }
               else if ((FieldInstruType == "X") ||
                        (FieldInstruType == "B") ||
                        (FieldInstruType == "T"))
               {
                  // This is being represented as a hexadecimal value
                  if (Parse.Try(Value, out long hex) == true)
                  {
                     return "0x" + hex.ToString("X");
                  }
               }
               else if (FieldInstruType == "N")
               {
                  // This is being represented as a binary number
                  if (Parse.Try(Value, out long bin) == true)
                  {
                     return Convert.ToString(bin, 2) + "b";
                  }
               }
               // else InstruType == 'S' or 'P' or anything else return the value
            }
            else // This value is an enumeration
            {
               Dictionary<string, string> enums = m_Icd.GetEnumerations(FieldType);
               if (enums.ContainsValue(Value) == true)
               {
                  foreach (KeyValuePair<string, string> pair in enums)
                  {
                     if (pair.Value.Trim() == Value.Trim())
                     {
                        return pair.Key;
                     }
                  }
               }
            }
         }

         return Value; // If nothing above applies, simply return the value string
      }

      private String RemoveCharFromString(String str, char c)
      {
         if (str == null) return null; // Handle null case

         int index = str.IndexOf(c);
         while (index != -1)
         {
            str = str.Remove(index, 1);
            index = str.IndexOf(c);
         }

         return str;
      }

      #region ICloneable Members

      public object Clone()
      {
         MessageData clone = new MessageData();

         clone.FieldName = FieldName;
         clone.FieldType = FieldType;
         clone.FieldValue = FieldValue;
         clone.FieldArrayValue = FieldArrayValue;
         clone.FieldDefaultValue = FieldDefaultValue;
         clone.FieldMaxValue = FieldMaxValue;
         clone.FieldMinValue = FieldMinValue;
         clone.FieldBitValue = FieldBitValue;
         clone.FieldInstruType = FieldInstruType;
         clone.Variable = Variable;
         clone.MaxOffset = MaxOffset;
         clone.MinOffset = MinOffset;
         clone.VerifyType = VerifyType;
         clone.isSelected = isSelected;
         clone.isArray = isArray;
         clone.isStructure = isStructure;
         clone.isArrayOfStructures = isArrayOfStructures;
         clone.isEnum = isEnum;
         clone.usesRegister = usesRegister;
         clone.isValid = isValid;
         clone.useRange = useRange;
         clone.arrayLength = arrayLength;
         clone.bitMask = bitMask;
         clone.expanded = expanded;
         clone.depth = depth;
         clone.m_Icd = m_Icd;
         clone.imageWidth = imageWidth;
         clone.imageHeight = imageHeight;
         clone.imagePixelSize = imagePixelSize;
         clone.imageBufferSize = imageBufferSize;
         if (imageBufferSize > 0)
         {
            clone.imageBuffer = new byte[imageBufferSize];
            imageBuffer.CopyTo(clone.imageBuffer, 0);
         }

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

         return clone;
      }

      #endregion
   }
}
