// **********************************************************************************************************
//    XmlParser.cs
//    6/6/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.Xml;
using System.Xml.XPath;

namespace Raytheon.Instruments.XmlUtilities
{
    public class XmlParser
    {
        public class CommonXmlElements
        {
            public List<string> fileIncludes = new List<string>();
            public string projectName = string.Empty;
            public string classification = string.Empty;
            public string fileHeader = string.Empty;
        }

        public class XmlConstant
        {
            public string nameSpace = string.Empty;
            public List<string> comments = new List<string>();
            public string name = string.Empty;
            public string value = string.Empty;
            public string description = string.Empty;
        }

        public class XmlEnumeration
        {
            public string nameSpace = string.Empty;
            public string description = string.Empty;
            public List<string> comments = new List<string>();
            //public Dictionary<string, string> enumKeyAndValuePairs = new Dictionary<string, string>();
            public List<XmlEnumItem> enumItems = new List<XmlEnumItem>();
        }

        public class XmlEnumItem
        {
            public List<string> comments = new List<string>();
            public string name = string.Empty;
            public string value = string.Empty;
        }

        public class XmlStructure
        {
            public string nameSpace = string.Empty;
            public string name = string.Empty;
            public List<string> comments = new List<string>();
            public List<XmlStructureItem> structDataItems = new List<XmlStructureItem>();
        }

        public class XmlStructureItem
        {
            public string nameSpace = string.Empty;
            public List<string> comments = new List<string>();
            public string name = string.Empty;
            public string description = string.Empty;
            public string type = string.Empty;
            public string defaultVal = string.Empty;
            public string arrayLength = string.Empty;
            public string bits = string.Empty;
        }

        public class XmlMbitMessage
        {
            public string nameSpace = string.Empty;
            public string name = string.Empty;
            public string label = string.Empty;
            public string instruLabel = string.Empty;
            public string description = string.Empty;
            public List<string> comments = new List<string>();

            public List<XmlParser.XmlStructureItem> dataItems = new List<XmlParser.XmlStructureItem>();
        }

        ///===================================================================================
        /// XmlParser.getCommonElementsFromXml
        ///===================================================================================
        /// <summary>
        /// Each XML file contains common elements such as header, includes, etc
        /// We want to parse it and save the common elements
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="commonElements">data structure that stores all the common elements of each XML file</param>
        ///===================================================================================
        public static bool GetCommonElementsFromXml(XPathNavigator nav, CommonXmlElements commonElements)
        {
            bool isSuccessful = true;

            if (string.Equals(nav.Name, "include", StringComparison.OrdinalIgnoreCase))
            {
                if (nav.MoveToFirstChild())
                {
                    GetFileIncludes(nav, commonElements);

                    nav.MoveToParent();
                }
            }
            else if (string.Equals(nav.Name, "project", StringComparison.OrdinalIgnoreCase))
            {
                commonElements.projectName = nav.Value.Trim();
            }
            else if (string.Equals(nav.Name, "classification", StringComparison.OrdinalIgnoreCase))
            {
                commonElements.classification = nav.Value.Trim();
            }
            else if (string.Equals(nav.Name, "fileheader", StringComparison.OrdinalIgnoreCase))
            {
                commonElements.fileHeader = nav.Value;
            }
            else if (string.Equals(nav.Name, "packing", StringComparison.OrdinalIgnoreCase))
            {
            }
            else
                isSuccessful = false;

            return isSuccessful;
        }

        ///===================================================================================
        /// XmlParser.getFileIncludes
        ///===================================================================================
        /// <summary>
        /// Get the file includes specify by the XML file
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="commonElements">data structure that stores all the common elements of each XML file</param>
        ///===================================================================================
        public static void GetFileIncludes(XPathNavigator nav, CommonXmlElements commonElements)
        {
            do
            {
                commonElements.fileIncludes.Add(nav.Value.Trim());

            } while (nav.MoveToNext());
        }

        ///===================================================================================
        /// XmlParser.getConstant
        ///===================================================================================
        /// <summary>
        /// Parse the symbolic constant defined by the XML
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="nameSpace">the XML file name that defines this constant</param>
        /// <param name="xmlConstants">the Dictionary that stores this constant information</param>
        /// <param name="duplicateConstants">the Dictioanry that stores duplicate constant definitions</param>
        ///===================================================================================
        public static void GetConstant(XPathNavigator nav, string nameSpace, List<XmlConstant> xmlConstants, Dictionary<string, List<string>> duplicateConstants)
        {
            XmlConstant tempXmlConstant = new XmlConstant();

            if (nav.MoveToFirstChild())
            {
                do
                {
                    if (string.Equals(nav.Name, "name", StringComparison.OrdinalIgnoreCase))
                    {
                        tempXmlConstant.name = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "description", StringComparison.OrdinalIgnoreCase))
                    {
                        tempXmlConstant.description = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "value", StringComparison.OrdinalIgnoreCase))
                    {
                        tempXmlConstant.value = nav.Value.Trim();
                    }
                    else if (nav.Name.Length > 0)
                    {
                        throw new XmlParsingException("Unknown element \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
                    }

                } while (nav.MoveToNext());

                nav.MoveToParent();
            }

            if (tempXmlConstant.name.Length > 0)
            {
                tempXmlConstant.nameSpace = nameSpace;
                xmlConstants.Add(tempXmlConstant);
            }
            else
            {
                throw new XmlParsingException("Child element \"name\" not found for node \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
            }
        }

        ///===================================================================================
        /// XmlParser.getEnumeration
        ///===================================================================================
        /// <summary>
        /// Parse the enumeration type defined by the XML
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="nameSpace">the XML file name that defines this enumeration type</param>
        /// <param name="xmlEnum">data structure that stores enumeration data</param>
        /// <param name="duplicateEnums">the Dictioanry that stores duplicate enumeration type</param>
        ///===================================================================================
        public static void GetEnumeration(XPathNavigator nav, string nameSpace, Dictionary<string, XmlEnumeration> xmlEnums, Dictionary<string, List<string>> duplicateEnums)
        {
            string enumTypeName = string.Empty;
            string tempEnumTypeName = "temp";
            Dictionary<string, XmlEnumeration> tempXmlEnums = new Dictionary<string, XmlEnumeration>
            {
                [tempEnumTypeName] = new XmlEnumeration()
            };
            tempXmlEnums[tempEnumTypeName].nameSpace = nameSpace;

            if (nav.MoveToFirstChild())
            {
                do
                {
                    if (string.Equals(nav.Name, "name", StringComparison.OrdinalIgnoreCase))
                    {
                        enumTypeName = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "description", StringComparison.OrdinalIgnoreCase))
                    {
                        tempXmlEnums[tempEnumTypeName].description = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "comment", StringComparison.OrdinalIgnoreCase))
                    {
                        tempXmlEnums[tempEnumTypeName].comments.Add(nav.Value.Trim());
                    }
                    else if (string.Equals(nav.Name, "item", StringComparison.OrdinalIgnoreCase) || string.Equals(nav.Name, "enum_item", StringComparison.OrdinalIgnoreCase))
                    {
                        if (nav.MoveToFirstChild())
                        {
                            GetEnumItem(nav, tempXmlEnums[tempEnumTypeName]);

                            nav.MoveToParent();
                        }
                    }
                    else if (nav.Name.Length > 0)
                    {
                        throw new XmlParsingException("Unknown element \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
                    }

                } while (nav.MoveToNext());

                nav.MoveToParent();
            }

            if (enumTypeName.Length > 0)
            {
                if (xmlEnums.ContainsKey(enumTypeName))
                {
                    // save file name the defines this message that is a duplicate
                    if (!duplicateEnums.ContainsKey(enumTypeName))
                    {
                        duplicateEnums[enumTypeName] = new List<string>();
                    }

                    int index2 = duplicateEnums[enumTypeName].FindIndex(f => string.Equals(f, nameSpace + ".xml", StringComparison.OrdinalIgnoreCase));

                    if (index2 < 0)
                        duplicateEnums[enumTypeName].Add(nameSpace + ".xml");

                    // see if the official structure is already in the duplicate list
                    int index3 = duplicateEnums[enumTypeName].FindIndex(f => string.Equals(f, xmlEnums[enumTypeName].nameSpace + ".xml", StringComparison.OrdinalIgnoreCase));

                    // if the official structure is not in the duplicate list, we want to save it in the duplicate list
                    if (index3 < 0)
                    {
                        duplicateEnums[enumTypeName].Add(xmlEnums[enumTypeName].nameSpace + ".xml");
                    }
                }
                else
                {
                    xmlEnums[enumTypeName] = tempXmlEnums[tempEnumTypeName];
                }
            }
            else
            {
                throw new XmlParsingException("Child element \"name\" not found for node \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
            }
        }

        ///===================================================================================
        /// XmlParser.getEnumItem
        ///===================================================================================
        /// <summary>
        /// Parse each enumerated key/value pair
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="xmlEnum">data structure that stores enumeration data</param>
        ///===================================================================================
        public static void GetEnumItem(XPathNavigator nav, XmlEnumeration xmlEnum)
        {
            XmlEnumItem enumItem = new XmlEnumItem();

            do
            {
                if (string.Equals(nav.Name, "name", StringComparison.OrdinalIgnoreCase) || string.Equals(nav.Name, "item_name", StringComparison.OrdinalIgnoreCase))
                {
                    enumItem.name = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "value", StringComparison.OrdinalIgnoreCase))
                {
                    enumItem.value = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "comment", StringComparison.OrdinalIgnoreCase))
                {
                    enumItem.comments.Add(nav.Value.Trim());
                }
                else if (nav.Name.Length > 0)
                {
                    throw new XmlParsingException("Unknown element \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
                }

            } while (nav.MoveToNext());

            xmlEnum.enumItems.Add(enumItem);
        }

        public static void GetStructure(XPathNavigator nav, string nameSpace, Dictionary<string, XmlStructure> xmlStructures, List<string> comments, Dictionary<string, List<string>> duplicateStructures)
        {
            string structureTypeName = string.Empty;
            string tempStructureTypeName = "temp";
            Dictionary<string, XmlStructure> tempXmlStructures = new Dictionary<string, XmlStructure>();

            tempXmlStructures[tempStructureTypeName] = new XmlStructure();
            tempXmlStructures[tempStructureTypeName].nameSpace = nameSpace;

            if (nav.MoveToFirstChild())
            {
                do
                {
                    if (string.Equals(nav.Name, "name", StringComparison.OrdinalIgnoreCase))
                    {
                        structureTypeName = nav.Value.Trim();

                        if (comments != null && comments.Count > 0)
                        {
                            tempXmlStructures[tempStructureTypeName].comments = new List<string>(comments);
                        }
                    }
                    else if (string.Equals(nav.Name, "comment", StringComparison.OrdinalIgnoreCase))
                    {
                        tempXmlStructures[tempStructureTypeName].comments.Add(nav.Value.Trim());
                    }
                    else if (string.Equals(nav.Name, "item", StringComparison.OrdinalIgnoreCase) || string.Equals(nav.Name, "struct_item", StringComparison.OrdinalIgnoreCase))
                    {
                        if (nav.MoveToFirstChild())
                        {
                            XmlStructureItem structureItem = new XmlStructureItem();
                            structureItem.nameSpace = nameSpace;
                            GetStructureItem(nav, structureItem);

                            tempXmlStructures[tempStructureTypeName].structDataItems.Add(structureItem);

                            nav.MoveToParent();
                        }
                    }
                    else if (nav.Name.Length > 0)
                    {
                        throw new XmlParsingException("Unknown element \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
                    }

                } while (nav.MoveToNext());

                nav.MoveToParent();
            }

            if (structureTypeName.Length > 0)
            {
                if (xmlStructures.ContainsKey(structureTypeName))
                {
                    // save file name the defines this message that is a duplicate
                    if (!duplicateStructures.ContainsKey(structureTypeName))
                    {
                        duplicateStructures[structureTypeName] = new List<string>();
                    }

                    int index2 = duplicateStructures[structureTypeName].FindIndex(f => string.Equals(f, nameSpace + ".xml", StringComparison.OrdinalIgnoreCase));

                    if (index2 < 0)
                        duplicateStructures[structureTypeName].Add(nameSpace + ".xml");

                    // see if the official structure is already in the duplicate list
                    int index3 = duplicateStructures[structureTypeName].FindIndex(f => string.Equals(f, xmlStructures[structureTypeName].nameSpace + ".xml", StringComparison.OrdinalIgnoreCase));

                    // if the official structure is not in the duplicate list, we want to save it in the duplicate list
                    if (index3 < 0)
                    {
                        duplicateStructures[structureTypeName].Add(xmlStructures[structureTypeName].nameSpace + ".xml");
                    }
                }
                else
                {
                    xmlStructures[structureTypeName] = tempXmlStructures[tempStructureTypeName];
                    xmlStructures[structureTypeName].name = structureTypeName;
                }
            }
            else
            {
                throw new XmlParsingException("Child element \"name\" not found for node \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
            }
        }

        ///===================================================================================
        /// XmlParser.getStructureItem
        ///===================================================================================
        /// <summary>
        /// Parse the data structure defined by the XML
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="xmlStructureItem">data structure that stores all data members of the data structure</param>
        ///===================================================================================
        public static void GetStructureItem(XPathNavigator nav, XmlStructureItem xmlStructureItem)
        {
            do
            {
                if (string.Equals(nav.Name, "name", StringComparison.OrdinalIgnoreCase) || string.Equals(nav.Name, "item_name", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.name = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "type", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.type = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "default", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.defaultVal = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "arrayLength", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.arrayLength = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "comment", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.comments.Add(nav.Value.Trim());
                }
                else if (string.Equals(nav.Name, "description", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.description = nav.Value.Trim();
                }
                else if (string.Equals(nav.Name, "bits", StringComparison.OrdinalIgnoreCase))
                {
                    xmlStructureItem.bits = nav.Value.Trim();
                }
                else if (nav.Name.Length > 0)
                {
                    throw new XmlParsingException("Unknown element \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
                }

            } while (nav.MoveToNext());
        }

        ///===================================================================================
        /// XmlParser.getMbitMessage
        ///===================================================================================
        /// <summary>
        /// Parse the message defined by the XML
        /// </summary>
        /// <param name="nav">navigator object that points to the current XML node we are at</param>
        /// <param name="nameSpace">the XML file name that defines this message</param>
        /// <param name="mbitMessages">list of messages defined by XML</param>
        /// <param name="duplicateMbitMessages">the Dictioanry that stores duplicate messages</param>
        ///===================================================================================
        public static void GetMbitMessage(XPathNavigator nav, string nameSpace, List<XmlMbitMessage> mbitMessages, Dictionary<string, List<string>> duplicateMbitMessages)
        {
            var mbitMsg = new XmlMbitMessage() 
            { 
                nameSpace = nameSpace 
            };

            if (nav.MoveToFirstChild())
            {
                do
                {
                    if (string.Equals(nav.Name, "name", StringComparison.OrdinalIgnoreCase))
                    {
                        mbitMsg.name = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "label", StringComparison.OrdinalIgnoreCase))
                    {
                        mbitMsg.label = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "instrulabel", StringComparison.OrdinalIgnoreCase))
                    {
                        mbitMsg.instruLabel = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "description", StringComparison.OrdinalIgnoreCase))
                    {
                        mbitMsg.description = nav.Value.Trim();
                    }
                    else if (string.Equals(nav.Name, "comment", StringComparison.OrdinalIgnoreCase))
                    {
                        mbitMsg.comments.Add(nav.Value.Trim());
                    }
                    else if (string.Equals(nav.Name, "item", StringComparison.OrdinalIgnoreCase)
                            || string.Equals(nav.Name, "struct_item", StringComparison.OrdinalIgnoreCase)
                            || string.Equals(nav.Name, "msg_item", StringComparison.OrdinalIgnoreCase))
                    {
                        if (nav.MoveToFirstChild())
                        {
                            XmlStructureItem structureItem = new XmlStructureItem() 
                            { 
                                nameSpace = nameSpace
                            };
                            GetStructureItem(nav, structureItem);

                            mbitMsg.dataItems.Add(structureItem);

                            nav.MoveToParent();
                        }
                    }
                    else if (nav.Name.Length > 0)
                    {
                        throw new XmlParsingException("Unknown element \"" + nav.OuterXml.Substring(0, nav.OuterXml.IndexOf(">") + 1) + "\" on line " + ((IXmlLineInfo)nav).LineNumber.ToString());
                    }

                } while (nav.MoveToNext());

                nav.MoveToParent();

                int index = mbitMessages.FindIndex(f => string.Equals(f.name, mbitMsg.name, StringComparison.OrdinalIgnoreCase));

                if (index >= 0)
                {
                    // save file name the defines this message that is a duplicate
                    if (!duplicateMbitMessages.ContainsKey(mbitMsg.name))
                    {
                        duplicateMbitMessages[mbitMsg.name] = new List<string>();
                    }

                    int index3 = duplicateMbitMessages[mbitMsg.name].FindIndex(f => string.Equals(f, nameSpace + ".xml", StringComparison.OrdinalIgnoreCase));

                    if (index3 < 0)
                        duplicateMbitMessages[mbitMsg.name].Add(nameSpace + ".xml");

                    // see if the official message is already in the duplicate list
                    int index2 = duplicateMbitMessages[mbitMsg.name].FindIndex(f => string.Equals(f, mbitMessages[index].nameSpace + ".xml", StringComparison.OrdinalIgnoreCase));

                    // if the official message is not in the duplicate list, we want to save it in the duplicate list
                    if (index2 < 0)
                    {
                        duplicateMbitMessages[mbitMsg.name].Add(mbitMessages[index].nameSpace + ".xml");
                    }

                    // the existing message is defined in an xml file other than MsgsMc.xml.  At this time, we want the messages in MsgsMc.xml to take precedence over other xml files.
                    // Why is the same message being defined in multiple xml files?
                    if (!string.Equals(mbitMessages[index].nameSpace, "msgsmc", StringComparison.OrdinalIgnoreCase) && string.Equals(nameSpace, "msgsmc", StringComparison.OrdinalIgnoreCase))
                    {
                        mbitMessages.RemoveAt(index);
                        mbitMessages.Add(mbitMsg);
                    }
                }
                else
                    mbitMessages.Add(mbitMsg);
            }
        }
    }
}
