// **********************************************************************************************************
//    XmlMbitParser.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.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;

namespace Raytheon.Common.Coe
{
    public class XmlMbitParser
    {
        private readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        private readonly Dictionary<string, XmlParser.CommonXmlElements> _commonElements = new Dictionary<string, XmlParser.CommonXmlElements>(StringComparer.InvariantCultureIgnoreCase);

        // store messages defined in xml files
        private readonly List<XmlParser.XmlMbitMessage> _mbitMessages = new List<XmlParser.XmlMbitMessage>();

        // store duplicate messages for diagnostic purposes
        private readonly Dictionary<string, List<string>> _duplicateMbitMessages = new Dictionary<string, List<string>>();

        // store each enumeration type and its associate key/value pairs
        private readonly Dictionary<string, XmlParser.XmlEnumeration> _xmlEnums = new Dictionary<string, XmlParser.XmlEnumeration>(StringComparer.InvariantCultureIgnoreCase);

        // store duplicate enumerations for diagnostic purposes
        private readonly Dictionary<string, List<string>> _duplicateEnums = new Dictionary<string, List<string>>();

        // store each structure type and its associated data members
        private readonly Dictionary<string, XmlParser.XmlStructure> _xmlStructures = new Dictionary<string, XmlParser.XmlStructure>(StringComparer.InvariantCultureIgnoreCase);

        // store duplicate structures for diagnostic purposes
        private readonly Dictionary<string, List<string>> _duplicateStructures = new Dictionary<string, List<string>>();

        // look up table for constants, the key is the constant name. A constant name can be defined in more than one namespace and have different values
        private ILookup<string, XmlParser.XmlConstant> _xmlConstantsLookUpByConstantName = null;

        // look up table for constants, the key is the namespace in which the constant is defined. A constant name can be defined in more than one namespace and have different values
        private ILookup<string, XmlParser.XmlConstant> _xmlConstantsLookUpByNameSpace = null;

        // store duplicate constants for diagnostic purposes
        private readonly Dictionary<string, List<string>> _duplicateConstants = new Dictionary<string, List<string>>();

        // store names of files that contain data types that we need to generate
        public List<string> m_dataTypeFilesToBeGenerated = new List<string>();

        /// <summary>
        /// Parse XML files from the path folder
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public bool ParseXmlFiles(string path)
        {
            bool isSuccessful = true;
            string xmlNamespace = string.Empty;
            List<XmlParser.XmlConstant> constantList = new List<XmlParser.XmlConstant>();

            if (!Directory.Exists(path))
            {
                _logger.Error($"Path {path} not found");

                isSuccessful = false;
                return isSuccessful;
            }

            string[] files = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories);

            List<string> dataTypeFiles = new List<string>(files.ToList());

            foreach (string xmlFile in dataTypeFiles)
            {
                XPathDocument doc = null;

                int prevEnumCount = _xmlEnums.Count;
                int prevStructureCount = _xmlStructures.Count;
                int prevMessageCount = _mbitMessages.Count;
                int prevConstantCount = constantList.Count;

                List<string> comments = new List<string>();

                try
                {
                    XmlReaderSettings readerSettings = new XmlReaderSettings() 
                    {
                        // tells the xmlreader to ignore comment in XML file
                        IgnoreComments = true
                    };

                    using (XmlReader reader = XmlReader.Create(xmlFile, readerSettings))
                    {
                        // load the XML file
                        doc = new XPathDocument(reader);
                    }

                    XPathNavigator nav = doc.CreateNavigator();

                    xmlNamespace = Path.GetFileNameWithoutExtension(xmlFile);

                    _commonElements[xmlNamespace] = new XmlParser.CommonXmlElements();

                    nav.MoveToRoot();

                    try
                    {
                        _logger.Info($"Parsing {Path.GetFileName(xmlFile)}");

                        if (nav.MoveToFirstChild())
                        {
                            do
                            {
                                if (string.Equals(nav.Name, "interface", StringComparison.OrdinalIgnoreCase))
                                {
                                    if (nav.MoveToFirstChild())
                                    {
                                        do
                                        {
                                            if (!XmlParser.GetCommonElementsFromXml(nav, _commonElements[xmlNamespace]))
                                            {
                                                if (string.Equals(nav.Name, "enum", StringComparison.OrdinalIgnoreCase))
                                                {
                                                    XmlParser.GetEnumeration(nav, xmlNamespace, _xmlEnums, _duplicateEnums);
                                                }
                                                else if (string.Equals(nav.Name, "structure", StringComparison.OrdinalIgnoreCase))
                                                {
                                                    XmlParser.GetStructure(nav, xmlNamespace, _xmlStructures, comments, _duplicateStructures);
                                                    comments.Clear();
                                                }
                                                else if (string.Equals(nav.Name, "constant", StringComparison.OrdinalIgnoreCase))
                                                {
                                                    XmlParser.GetConstant(nav, xmlNamespace, constantList, _duplicateConstants);
                                                }
                                                else if (string.Equals(nav.Name, "message", StringComparison.OrdinalIgnoreCase))
                                                {
                                                    XmlParser.GetMbitMessage(nav, xmlNamespace, _mbitMessages, _duplicateMbitMessages);
                                                }
                                                else if (string.Equals(nav.Name, "comment", StringComparison.OrdinalIgnoreCase))
                                                {
                                                    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());
                                    }
                                }
                            } while (nav.MoveToNext());
                        }
                    }
                    catch (XmlParsingException ex)
                    {
                        string message = "File : " + xmlFile + "\n" + ex.Message;
                        throw new Exception(message);
                    }
                }
                catch (Exception e)
                {
                    _logger.Error(e);
                    isSuccessful = false;
                }

                if (isSuccessful)
                {
                    _logger.Info(" - SUCCESS");

                    _logger.Info("Results:");
                    _logger.Info($"Constants: {constantList.Count - prevConstantCount}");
                    _logger.Info($"Enumerations: {_xmlEnums.Count - prevEnumCount}");
                    _logger.Info($"Structures: {_xmlStructures.Count - prevStructureCount}");
                    _logger.Info($"Messages: {_mbitMessages.Count - prevMessageCount}");
                }
                else
                {
                    _logger.Warn(" - FAIL");
                    break;
                }
            }

            if (constantList.Count > 0)
            {
                // we want to create a look up table from a list of constants
                // the key for this table will be the constant name
                _xmlConstantsLookUpByConstantName = constantList.ToLookup(item => item.name);

                // we want to create a look up table from a list of constants
                // the key for this table will be the namespace
                _xmlConstantsLookUpByNameSpace = constantList.ToLookup(item => item.nameSpace);
            }

            if (_duplicateMbitMessages.Count > 0 || _duplicateConstants.Count > 0 || _duplicateEnums.Count > 0 || _duplicateStructures.Count > 0)
            {
                StreamWriter writer = null;
                FileStream fs = null;
                bool firstLineInFileAllocated = false;
                string textToBeWrittenToFile = string.Empty;
                string diagnosticFile = Path.Combine(path, "diagnostics.txt");

                _logger.Info("Generating diagnostic information...");

                foreach (KeyValuePair<string, List<string>> dictItem in _duplicateMbitMessages)
                {
                    if (!firstLineInFileAllocated)
                        firstLineInFileAllocated = true;
                    else
                        textToBeWrittenToFile += "\r\n\r\n";

                    textToBeWrittenToFile += "Duplicate definition for message \"" + dictItem.Key + "\" found in the following files: ";
                    foreach (string listItem in dictItem.Value)
                    {
                        textToBeWrittenToFile += "\r\n" + GetCodeIndentation(1) + listItem;
                    }
                }

                foreach (KeyValuePair<string, List<string>> dictItem in _duplicateStructures)
                {
                    if (!firstLineInFileAllocated)
                        firstLineInFileAllocated = true;
                    else
                        textToBeWrittenToFile += "\r\n\r\n";

                    textToBeWrittenToFile += "Duplicate definition for structure \"" + dictItem.Key + "\" found in the following files: ";
                    foreach (string listItem in dictItem.Value)
                    {
                        textToBeWrittenToFile += "\r\n" + GetCodeIndentation(1) + listItem;
                    }
                }

                foreach (KeyValuePair<string, List<string>> dictItem in _duplicateEnums)
                {
                    if (!firstLineInFileAllocated)
                        firstLineInFileAllocated = true;
                    else
                        textToBeWrittenToFile += "\r\n\r\n";

                    textToBeWrittenToFile += "Duplicate definition for enum \"" + dictItem.Key + "\" found in the following files: ";
                    foreach (string listItem in dictItem.Value)
                    {
                        textToBeWrittenToFile += "\r\n" + GetCodeIndentation(1) + listItem;
                    }
                }

                foreach (KeyValuePair<string, List<string>> dictItem in _duplicateConstants)
                {
                    if (!firstLineInFileAllocated)
                        firstLineInFileAllocated = true;
                    else
                        textToBeWrittenToFile += "\r\n\r\n";

                    textToBeWrittenToFile += "Duplicate definition for constant \"" + dictItem.Key + "\" found in the following files: ";
                    foreach (string listItem in dictItem.Value)
                    {
                        textToBeWrittenToFile += "\r\n" + GetCodeIndentation(1) + listItem;
                    }
                }

                if (textToBeWrittenToFile.Length > 0)
                {
                    try
                    {
                        fs = new FileStream(diagnosticFile, FileMode.Create, FileAccess.ReadWrite);
                        writer = new StreamWriter(fs, Encoding.Default);
                        writer.Write(textToBeWrittenToFile);
                    }
                    catch (System.Exception ex)
                    {
                        _logger.Error(ex);
                        isSuccessful = false;
                    }
                    finally
                    {
                        if (writer != null)
                        {
                            writer.Close();
                            fs.Close();
                        }
                    }
                }

                //m_mainWindow.updateStatusBox("DONE\n");
            }

            return isSuccessful;
        }
        public static string GetCodeIndentation(int indentMultiples)
        {
            string indentUnit = "   ";
            string indentation = string.Empty;

            for (int i = 1; i <= indentMultiples; i++)
                indentation += indentUnit;

            return indentation;
        }
    }

}
