// **********************************************************************************************************
//    MessageXmlDocument.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.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using System.Xml.XPath;
using NLog;

namespace Raytheon.Common.Coe
{
   public class MessageXmlDocument : XmlDocument
   {
      private readonly ILogger _logger;
      private readonly string _XmlFileName;
      private string BuiltXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" ?><interface>";

      private uint m_MaxMsgSize = 0;
      private readonly List<string> m_IncludeList;

      public bool LogData { get; set; }

      private MessageXmlDocument() : base()
      {
         LogData = true;
      }

      public MessageXmlDocument(string Pathname, bool logData = true) :
                                base()
      {
         LogData = logData;

         _logger = LogManager.GetCurrentClassLogger();
         _XmlFileName = Pathname;

         m_IncludeList = new List<string>();
         RecurseProcessing(Pathname);

         BuiltXML = string.Concat(BuiltXML, "</interface>");
         BytePackingXml addPacking = new BytePackingXml(BuiltXML);
         LoadXml(addPacking.OuterXml);
      }

      public Dictionary<string, string> GetEnumerations(string Type)
      {
         Dictionary<string, string> enumList = new Dictionary<string, string>();

         // Get XML nodes to parse the XML ICD document
         XPathNavigator Node = CreateNavigator();
         XPathNodeIterator Nodeset = Node.Select("interface/enum/name");

         while (Nodeset.MoveNext())
         {
            // Find the enumeration with the name of the type
            if (Nodeset.Current.Value.Trim().Equals(Type.Trim()))
            {
               // Find all the enumeration items
               XPathNavigator enumNode = Nodeset.Current.Clone();
               while (enumNode.MoveToNext())
               {
                  if (enumNode.Name.Trim().Equals("item") ||
                      enumNode.Name.Trim().Equals("enum_item"))
                  {
                     string name = null;
                     string value = null;

                     // Find all name nodes
                     XPathNavigator childNode = enumNode.Clone();
                     childNode.MoveToFirstChild();
                     do
                     {
                        if (childNode.Name.Trim().Equals("name"))
                        {
                           name = childNode.Value.Trim();
                        }
                        else if (childNode.Name.Trim().Equals("item_name"))
                        {
                           name = childNode.Value.Trim();
                        }

                        if (childNode.Name.Trim().Equals("value"))
                        {
                           value = childNode.Value.Trim();
                        }

                        // Once we find the name & value, add it to the list
                        if ((name != null) && (value != null))
                        {
                           enumList.Add(name, value);
                           break;
                        }
                     } while (childNode.MoveToNext());
                  }
               }

               break; // We found the enumeration we wanted
            }
         }

         return enumList;
      }

      private void RecurseProcessing(string pathName)
      {
         string directory;
         string IncludePathname;
         XPathNodeIterator nodeset;

         // Only process each file once
         pathName = pathName.Replace('/', '\\');
         if (m_IncludeList.Contains(pathName))
         {
            return; // This file has already been processed
         }
         else
         {
            m_IncludeList.Add(pathName);
         }

         if (LogData)
            _logger.Info($"Loading File: {pathName}");
         XPathDocument document = new XPathDocument(pathName);
         XPathNavigator navigator = document.CreateNavigator();

         // Verify this is a COE XML ICD
         nodeset = navigator.Select("/interface");
         if (nodeset.Count == 0)
         {
            // This is not an XML ICD
            throw new Exception($"Invalid COE XML Format. Unable to process {pathName}" +
                                "\nEnsure this is a properly formatted ICD.");
         }

         nodeset = navigator.Select("/interface/include/file");
         while (nodeset.MoveNext())
         {
            try
            {
               directory = DirectoryOf(pathName);
            }
            catch
            {
               directory = ".\\";
            }

            IncludePathname = nodeset.Current.Value.Trim();
            if ((!IncludePathname.StartsWith("\\")) && (!IncludePathname.Contains(":")))
            {
               while (IncludePathname.StartsWith("."))
               {
                  if ((IncludePathname.StartsWith("..\\")) || (IncludePathname.StartsWith("../")))
                  {
                     directory = DirectoryOf(directory);
                     IncludePathname = IncludePathname.Remove(0, 3);
                  }
                  else if ((IncludePathname.StartsWith(".\\")) || (IncludePathname.StartsWith("./")))
                  {
                     IncludePathname = IncludePathname.Remove(0, 2);
                  }
               }
               IncludePathname = string.Concat(directory, "\\", IncludePathname);
            }

            if (Regex.IsMatch(IncludePathname, @"\.xml", RegexOptions.IgnoreCase))
               RecurseProcessing(IncludePathname);
         }

         nodeset = navigator.Select("/interface/packing|/interface/typedef|/interface/namespace/typedef|" +
                                    "/interface/constant|/interface/namespace/constant|interface/enum|interface/namespace/enum|" +
                                    "/interface/structure|/interface/namespace/structure|/interface/message|/interface/namespace/message");
         while (nodeset.MoveNext())
         {
            string item = nodeset.Current.OuterXml;
            int index;
            while ((index = item.IndexOf("<description>")) != -1)
            {
               item = item.Remove(index, item.IndexOf("</description>") + 14 - index);
            }
            while ((index = item.IndexOf("<!--")) != -1)
            {
               item = item.Remove(index, item.IndexOf("-->") + 3 - index);
            }
            while (item.IndexOf("> ") != -1)
            {
               item = item.Replace("> ", ">");
            }
            while (item.IndexOf(" <") != -1)
            {
               item = item.Replace(" <", "<");
            }
            //_logger.Log(LogLevel.Trace, $"Loading Node :\n{item}");
            Thread.Sleep(1);
            BuiltXML = string.Concat(BuiltXML, item);
         }
      }

      private string DirectoryOf(string Pathname)
      {
         return Pathname.Remove(Pathname.LastIndexOf("\\"));
      }

      //
      // From the XML document, the definition of a single message can be identified
      // from the Message Name and returned as an XML string.
      //
      public string XmlFileName
      {
         get
         {
            return _XmlFileName;
         }
      }

      public string GetMessage(string messageName)
      {
         string message;

         messageName = messageName.Trim();

         if (LogData)
            _logger.Info($"Searching for message : {messageName}");
         try
         {
            message = SelectSingleNode($"/interface/message[name='{messageName}']").OuterXml;
            message = TranslateValue(message);

            string labelStr = Regex.Replace(message, @".+<label>([\d]+)[^\d]+", "$1", RegexOptions.IgnoreCase);

            int label = 0;

            Parse.Try(labelStr, out label);

            if (LogData)
               _logger.Trace($"Found by name: {Regex.Replace(message, @"<label>[\d]+", $"<label>0x{label.ToString("X8")}")}");
         }
         catch
         {
            message = null;
            throw new Exception("Message not found");
         }
         return message;
      }

      //
      // From the XML document, the definition of a single message can be identified
      // from the Message Label and returned as an XML string.
      //
      public string GetMessageFromLabel(string messageLabel)
      {
         string message;

         int label = 0;

         if (Parse.Try(messageLabel, out label) == true)
         {
            if (LogData)
               _logger.Debug($"Searching for message: 0x{label.ToString("X8")}");

            // Search by message label
            message = SelectSingleNode($"/interface/message[label='{label.ToString()}']").OuterXml;
            message = TranslateValue(message);

            if (LogData)
               _logger.Debug($"Found by label: {Regex.Replace(message, @"<label>[\d]+", $"<label>0x{label.ToString("X8")}")}");
         }
         else
         {
            if (LogData)
               _logger.Debug($"Searching for message: {messageLabel}");

            // Search by instruLabel
            message = SelectSingleNode($"/interface/message[instruLabel='{messageLabel}']").OuterXml;
            message = TranslateValue(message);

            string labelStr = Regex.Replace(message, @".+<label>([\d]+)[^\d]+", "$1", RegexOptions.IgnoreCase);

            Parse.Try(labelStr, out label);

            if (LogData)
               _logger.Debug($"Found by name: {Regex.Replace(message, @"<label>[\d]+", $"<label>0x{label.ToString("X8")}")}");
         }

         return message;
      }

      //
      // From the XML document, the definition of a single message can be identified
      // from the Message InstruLabel and returned as an XML string.
      //
      public string GetMessageFromInstruLabel(string messageInstruLabel)
      {
         string message;

         messageInstruLabel = messageInstruLabel.Trim();
         if (LogData)
            _logger.Debug($"Searching for message: {messageInstruLabel}");
         try
         {
            message = SelectSingleNode($"/interface/message[instruLabel='{messageInstruLabel}']").OuterXml;
            message = TranslateValue(message);

            if (LogData)
               _logger.Debug($"Found by instrument label: {message}");
         }
         catch
         {
            message = null;
            throw new Exception("Message not found");
         }
         return message;
      }

      public uint GetLargestMessageSize()
      {
         lock (this)
         {
            // return the max message size if we have already calculated it
            if (m_MaxMsgSize != 0)
            {
               return m_MaxMsgSize;
            }
            else
            {
               DateTime t1 = DateTime.Now;

               XPathNavigator navigator = CreateNavigator();
               XPathNodeIterator nodeset = navigator.Select("/interface/message/name");

               while (nodeset.MoveNext())
               {
                  Message msg = new Message(nodeset.Current.Value.Trim(), this);
                  uint msgSize = msg.GetMessageSize();
                  if (msgSize > m_MaxMsgSize)
                  {
                     m_MaxMsgSize = msgSize;
                  }
               }

               DateTime t2 = DateTime.Now;
               TimeSpan duration = t2 - t1;
               Debug.WriteLine("Max Msg Size Algorithm Time = " + duration);
            }
         }

         return m_MaxMsgSize;
      }

      public uint GetMessageSize(string MsgName)
      {
         uint msg_size = 0;

         lock (this)
         {
            XPathNavigator navigator = CreateNavigator();
            XPathNodeIterator nodeset = navigator.Select("/interface/message/name");

            while (nodeset.MoveNext())
            {
               if (MsgName == nodeset.Current.Value.Trim())
               {
                  Message msg = new Message(nodeset.Current.Value.Trim(), this);
                  msg_size = msg.GetMessageSize();
               }
            }
         }

         return msg_size;
      }

      //
      // Since the XML message definitions contain the definitions of all the enumerations and constants,
      // this object is the only one containing the knowledge to interpret strings using enumerations and/or
      // constants. This method will substitute enumerations and constants with their respective base values
      // in a specified string.
      //
      public string TranslateValue(string Value)
      {
         XPathNavigator navigator = CreateNavigator();
         XPathNavigator position;
         string NewValue = Value;

         //
         // Substitute enumeration
         //
         try
         {
            position = navigator.SelectSingleNode("/interface/enum/item[name='" + NewValue + "']");
            if (position == null)
            {
               position = navigator.SelectSingleNode("/interface/enum/item[item_name='" + NewValue + "']");
            }
            if (position != null)
            {
               position.MoveToChild("value", "");
               NewValue = position.Value;
            }

            //
            // Substitute constants
            //
            position = navigator.SelectSingleNode("/interface/constant[name='" + NewValue + "']");
            if (position != null)
            {
               NewValue = position.Value;
               _logger.Debug("Translating field value : " + Value + " -> " + NewValue);
            }
         }
         catch (Exception e)
         {
            _logger.Error(e.Message);
         }

         return NewValue;
      }

   }
}
