using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using SystemX.Net.Platform.Common.Util;

using static SystemX.PLC.Interface.PLCCommDefinition;
using static SystemX.PLC.Model.ModelSetupTemplate;

namespace SystemX.PLC.Interface
{
    public class PLCAddressManager
    {
        public bool AutoConnection { get; set; } = false;
        public Dictionary<string, ModelElementData> DescriptionDictionary { get; set; } = new Dictionary<string, ModelElementData>();
        public Dictionary<string, PLCAddressTemplate> DataDictionary { get; set; } = new Dictionary<string, PLCAddressTemplate>();
        public Dictionary<string, PLCAddressTemplate> OperationDictionary { get; set; } = new Dictionary<string, PLCAddressTemplate>();
        public Dictionary<string, PLCConnectionInfo> ConnInfoDictionary { get; set; } = new Dictionary<string, PLCConnectionInfo>();

        public PLCAddressManager(string strPath)
        {
            LoadAddressXML(strPath);
        }

        public ePLCAreaType AddressSectionCheck(string strName)
        {
            if (DataDictionary.Values.Where(x => x.DataName == strName).ToList().Count > 0)
                return ePLCAreaType.Data;
            else if (OperationDictionary.Values.Where(x => x.DataName == strName).ToList().Count > 0)
                return ePLCAreaType.Operation;
            else
                return ePLCAreaType.None;
        }

        public bool IsDataSectionSignal(string strName)
        {
            if (DataDictionary.Values.Where(x => x.DataName == strName).ToList().Count > 0)
                return true;
            else
                return false;
        }

        public bool IsOperationSectionSignal(string strName)
        {
            if (OperationDictionary.Values.Where(x => x.DataName == strName).ToList().Count > 0)
                return true;
            else
                return false;
        }

        public PLCAddressTemplate FindDataDicPLCAddressElementByDataName(string strName)
        {
            List<PLCAddressTemplate> vtmplt = DataDictionary.Values.Where(x => x.DataName == strName).ToList();
            List<PLCAddressTemplate> vOpTmplt = OperationDictionary.Values.Where(x => x.DataName == strName).ToList();

            vtmplt = vtmplt.Union(vOpTmplt).ToList();
            if (vtmplt.Count == 0)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Cannot Find PLC Address which has a name as : {strName}.", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return null;
            }

            return vtmplt[0];
        }

        public PLCAddressTemplate FindOperationPLCAddressElementByDataName(string strName)
        {
            List<PLCAddressTemplate> vtmplt = OperationDictionary.Values.Where(x => x.DataName == strName).ToList();

            if (vtmplt.Count == 0)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Cannot Find PLC Address which has a name as : {strName}.", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return null;
            }

            return vtmplt[0];
        }

        public PLCAddressTemplate FindPLCOperationTemplateElementByPLCDevice(string PLCDevice)
        {
            List<PLCAddressTemplate> vTemplate = OperationDictionary.Values.Where(x => x.PLCAddress == PLCDevice).ToList();

            if (vTemplate.Count <= 0)
                return null;

            return vTemplate[0];
        }

        public PLCAddressTemplate FindPLCAddressElement(string strKey)
        {
            PLCAddressTemplate tmplt = null;

            if (DataDictionary.ContainsKey(strKey))
                tmplt = DataDictionary[strKey];
            else if (OperationDictionary.ContainsKey(strKey))
                tmplt = OperationDictionary[strKey];

            return tmplt;
        }

        public PLCAddressTemplate FindPLCAddressElementByDataName(string strDataName)
        {
            PLCAddressTemplate tmpltDataName = FindDataDicPLCAddressElementByDataName(strDataName);

            if (tmpltDataName == null)
                tmpltDataName = FindOperationPLCAddressElementByDataName(strDataName);

            return tmpltDataName;
        }

        public PLCAddressTemplate FindPLCAddressElementByAssociatedKeyAddress(string strKeyAddress, Dictionary<string, PLCAddressTemplate> dicResult)
        {
            List<PLCAddressTemplate> vResults = (from addrTmplt in dicResult.Values where addrTmplt.IsMyAssociation(strKeyAddress) select addrTmplt).ToList();

            if (vResults.Count > 0)
                return vResults[0];

            return null;
        }

        void LoadAddressXML(string strXmlPath)
        {
            if (!File.Exists(strXmlPath))
                return;

            XDocument xDoc = XDocument.Load(strXmlPath);

            var xElement = xDoc.Element(PLCCommDefinition.ModelRoot);

            if (xElement == null) return;

            DescriptionDictionary = new Dictionary<string, ModelElementData>();
            DataDictionary = new Dictionary<string, PLCAddressTemplate>();
            OperationDictionary = new Dictionary<string, PLCAddressTemplate>();
            ConnInfoDictionary = new Dictionary<string, PLCConnectionInfo>();

            var xelemDescRoot = xElement.Element(PLCCommDefinition.DescriptionRoot);
            var xelemConRoot = xElement.Element(PLCCommDefinition.ConnectionInfoRoot);
            var xelemOpRoot = xElement.Element(PLCCommDefinition.OpearationAreaRoot);
            var xelemDataRoot = xElement.Element(PLCCommDefinition.DataAreaRoot);

            LoadXmlDataForDescTemplate(xelemDescRoot, DescriptionDictionary);
            LoadXmlDataForConninfoTemplate(xelemConRoot, ConnInfoDictionary);
            LoadXmlDataForDataTemplate(xelemOpRoot, OperationDictionary);
            LoadXmlDataForDataTemplate(xelemDataRoot, DataDictionary);

            AutoConnection = Convert.ToBoolean(xelemConRoot.Attribute("AutoConnection").Value);
        }

        void LoadXmlDataForDescTemplate(XElement xElemRoot, Dictionary<string, ModelElementData> dicResult)
        {
            foreach (XElement elem in xElemRoot.Elements())
            {
                if (elem.Name != PLCCommDefinition.DescElement)
                    continue;

                string strName = string.Empty;
                string strDataType = string.Empty;
                string strDesc = string.Empty;
                string strValue = elem.Value;

                GetDescAttributes(elem, ref strName, ref strDataType, ref strDesc);
                dicResult.Add(strName, new ModelElementData() { Name = strName, DataType = strDataType, Desc = strDesc });
            }
        }

        void GetDescAttributes(XElement xElem, ref string strName, ref string strType, ref string strDesc)
        {
            strName = string.Empty;
            strType = string.Empty;
            strDesc = string.Empty;

            foreach (XAttribute xattrb in xElem.Attributes())
            {
                if (xattrb.Name == PLCCommDefinition.AttrbName)
                    strName = xattrb.Value;
                else if (xattrb.Name == PLCCommDefinition.AttrbDataType)
                    strType = xattrb.Value;
                else if (xattrb.Name == PLCCommDefinition.AttrbDesc)
                    strDesc = xattrb.Value;
            }
        }

        void LoadXmlDataForConninfoTemplate(XElement xElemRoot, Dictionary<string, PLCConnectionInfo> dicResult)
        {
            foreach (XElement elem in xElemRoot.Elements())
            {
                if (elem.Name != PLCCommDefinition.ConnectionInfoElement)
                    continue;

                Dictionary<string, string> dicValue = new Dictionary<string, string>();

                GetAddrAttributes(elem, dicValue);
                dicResult.Add(dicValue[PLCCommDefinition.eConnectionElem.Name.ToString()],
                                        new PLCConnectionInfo()
                                        {
                                            Name = dicValue[PLCCommDefinition.eConnectionElem.Name.ToString()],
                                            CPUType = dicValue[PLCCommDefinition.eConnectionElem.CPUType.ToString()],
                                            RWType = dicValue[PLCCommDefinition.eConnectionElem.RWType.ToString()],
                                            IP = dicValue[PLCCommDefinition.eConnectionElem.IP.ToString()],
                                            Port = dicValue[PLCCommDefinition.eConnectionElem.Port.ToString()],
                                            ConnectionType = dicValue[PLCCommDefinition.eConnectionElem.ConnectionType.ToString()],
                                            Timeout = dicValue[PLCCommDefinition.eConnectionElem.Timeout.ToString()]
                                        });
            }
        }

        void LoadXmlDataForDataTemplate(XElement xElemRoot, Dictionary<string, PLCAddressTemplate> dicResult)
        {
            try
            {
                foreach (XElement elem in xElemRoot.Elements())
                {
                    if (elem.Name != PLCCommDefinition.AddrElement)
                        continue;

                    Dictionary<string, string> dicValue = new Dictionary<string, string>();

                    GetAddrAttributes(elem, dicValue);
                    dicResult.Add(dicValue[PLCCommDefinition.AttrbAddrArea] + dicValue[PLCCommDefinition.AttrbAddrAddress],
                                            new PLCAddressTemplate()
                                            {
                                                Area = dicValue[PLCCommDefinition.AttrbAddrArea],
                                                Address = dicValue[PLCCommDefinition.AttrbAddrAddress],
                                                Length = dicValue[PLCCommDefinition.AttrbAddrLength],
                                                DataName = dicValue[PLCCommDefinition.AttrbAddrDataName],
                                                DataType = dicValue[PLCCommDefinition.AttrbAddrDataType],
                                                Operation = dicValue[PLCCommDefinition.AttrbAddrOperation],
                                                Desc = dicValue[PLCCommDefinition.AttrbAddrDesc]
                                            });

                    dicResult[dicValue[PLCCommDefinition.AttrbAddrArea] + dicValue[PLCCommDefinition.AttrbAddrAddress]].SetAssocationKeys();
                }
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Error occured in LoadXmlDataForDataTemplate. \n - Error Message: {ex.Message}");
            }
        }

        void GetAddrAttributes(XElement xElem, Dictionary<string, string> dicValue)
        {
            foreach (XAttribute xattrb in xElem.Attributes())
                dicValue.Add(xattrb.Name.ToString(), xattrb.Value);
        }
    }
}
