using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SystemX.Net.Platform.Common.Event;
using SystemX.Net.Platform.Common.Util;
using static SystemX.PLC.Interface.PLCCommDefinition;

namespace SystemX.PLC.Interface.McProtocol
{
    public class PLCCommMcPMgr : PLCCommManager
    {
        private int TimeOut;
        public Dictionary<ePLCRWType, PLCModMxCMgr> PLCManagers { get; set; } = new Dictionary<ePLCRWType, PLCModMxCMgr>();

        public PLCCommMcPMgr(string strPLCConnInfoXmlPath)
            : base(strPLCConnInfoXmlPath)
        {
            AddrMgr = new PLCAddressManager(strPLCConnInfoXmlPath);
            EventChangeUpdate = new FunctionEventHandler();

            TimeOut = 0;

            foreach (PLCConnectionInfo coninfo in AddrMgr.ConnInfoDictionary.Values)
            {
                if (TimeOut < int.Parse(coninfo.Timeout))
                    TimeOut = int.Parse(coninfo.Timeout);

                PLCManagers.Add((ePLCRWType)Enum.Parse(typeof(ePLCRWType), coninfo.RWType), new PLCModMxCMgr(coninfo));
            }
        }

        public override bool OpenPLCConnection()
        {
            Task<bool> openconn = new Task<bool>(() => TryOpenPLCConnection());

            openconn.Start();

            bool bResult = openconn.Result;

            if (bResult)
            {
                //Task plcoperation = openconn.ContinueWith(x => StartPLCOperation());
                Task<bool> plcoperation = new Task<bool>(() => StartPLCOperation());

                plcoperation.Start();

                bResult = plcoperation.Result;
            }

            return bResult;

            /* 
            if (plcoperation.Wait(TimeOut))
                return true;
            else
                return false;
            */
        }

        public bool TryOpenPLCConnection()
        {
            try
            {
                foreach (PLCModMxCMgr conmgr in PLCManagers.Values)
                {
                    if (!conmgr.OpenDevice())
                        return false;

                    Stopwatch stTimeOut = new Stopwatch();
                    stTimeOut.Start();

                    while (true)
                    {
                        if (conmgr.IsOpened == true)
                            break;

                        if (stTimeOut.ElapsedMilliseconds >= TimeOut)
                        {
                            LogMessage.MessageOutput.ConsoleWrite("PLC Connection Waiting TimeOut.", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                            return false;
                        }

                        Thread.Sleep(50);
                    }

                    conmgr.EventPLC += ReceivePLCEvent;

                    SetAddress(conmgr, AddrMgr.OperationDictionary);
                    SetAddress(conmgr, AddrMgr.DataDictionary);
                }

                LogMessage.MessageOutput.ConsoleWrite("PLC Connection Waiting for Success.", ConsoleColor.Blue, LogMessage.LogMessageLevel.NONE);

                return true;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Open Error.\n - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }

        public override bool StartPLCOperation()
        {
            try
            {
                Thread.Sleep(300);

                PLCManagers[ePLCRWType.Read].SingleScanStartAsync();  // Do not use await

                UpdateAll();

                OperationStarted = true;

                LogMessage.MessageOutput.ConsoleWrite("PLC Operation Started.", ConsoleColor.Blue, LogMessage.LogMessageLevel.NONE);

                return true;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Open Error.\n - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }
        public override void NowScanDataArea()
        {

        }
        public override bool ClosePLCConnection()
        {
            try
            {
                foreach (PLCModMxCMgr conmgr in PLCManagers.Values)
                    conmgr.CloseDevice();

                OperationStarted = false;

                return true;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Close Error.\n - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }

        public override void ReceivePLCEvent(string address, int value)
        {
            PLCAddressTemplate tmpltSearch = SearchMySubscription(address);

            if (tmpltSearch == null)
                return;

            UpdateDevice(tmpltSearch, address, value);

            EventChangeUpdate.DoReceive(this, address);
        }

        public PLCAddressTemplate SearchMySubscription(string strAddress)
        {
            PLCAddressTemplate tmpltOp = AddrMgr.FindPLCAddressElementByAssociatedKeyAddress(strAddress, AddrMgr.OperationDictionary);
            PLCAddressTemplate tmpltData = AddrMgr.FindPLCAddressElementByAssociatedKeyAddress(strAddress, AddrMgr.DataDictionary);

            if (tmpltOp != null)
                return tmpltOp;
            else
                return tmpltData;
        }

        void SetAddress(PLCModMxCMgr module, Dictionary<string, PLCAddressTemplate> addressInfo)
        {
            foreach (PLCAddressTemplate addrinfo in addressInfo.Values)
            {
                int nLength = Convert.ToInt32(addrinfo.Length);
                int nStartAddr = Convert.ToInt32(addrinfo.Address);

                for (int i = 0; i < nLength; i++)
                {
                    int nAddr = nStartAddr + i;
                    module.AddDevices(addrinfo.Area + nAddr.ToString());
                }
            }
        }

        public PLCAddressTemplate UpdateDevice(PLCAddressTemplate tmplt, string strDevice, int nValue)
        {
            if (tmplt == null)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Cannot Find a Corresponding Data. Device: {strDevice}");

                return null;
            }

            tmplt.UpdateValue(strDevice, nValue);

            return tmplt;
        }

        public PLCAddressTemplate UpdateDevice(string strDevice)
        {
            PLCAddressTemplate tmplt = AddrMgr.FindPLCAddressElement(strDevice);

            if (tmplt == null)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Cannot Find a Corresponding Data. Device: {strDevice}");

                return null;
            }

            UpdateValue(tmplt);

            return tmplt;
        }

        void UpdateValue(PLCAddressTemplate tmplt)
        {
            List<int> vValues = GetReadValues(tmplt);
            eDataType eType = (eDataType)Enum.Parse(typeof(eDataType), tmplt.DataType);
            string strData = string.Empty;

            tmplt.SetAssociatedValues(vValues);
        }

        public override string ReadDevice(string strDevice, bool bRefineWrongChar = true)
        {
            PLCAddressTemplate tmplt = UpdateDevice(strDevice);

            return (tmplt != null) ? tmplt.Value : string.Empty;
        }

        public override string ReadDevice(PLCAddressTemplate tmplt)
        {
            try
            {
                tmplt = UpdateDevice(tmplt.AddressKey);

                return (tmplt != null) ? tmplt.Value : string.Empty;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Manager is failed to Read Device. The Address: {tmplt.AddressKey} has not been read by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);
                LogMessage.MessageOutput.ConsoleWrite($" - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return string.Empty;
            }
        }
        public override void UpdateAll(bool bRefineWrongChar = false)
        {
            UpdateArea(AddrMgr.OperationDictionary.Keys.ToList());
            UpdateArea(AddrMgr.DataDictionary.Keys.ToList());
        }
        public virtual void SelectUpdate(PLCAddressTemplate tmplt, bool bRefineWrongChar)
        {
        }
        void UpdateArea(List<string> strKeys)
        {
            foreach (string strKey in strKeys)
                UpdateDevice(strKey);
        }

        public override bool WriteDevice(PLCAddressTemplate tmplt, string strValue, bool bVerification = false)
        {
            try
            {
                tmplt.SetStringValue(strValue);

                WriteDevice(tmplt);

                if (bVerification)
                {
                    string strReadValue = ReadDevice(tmplt.PLCAddress);

                    if (strReadValue == strValue)
                    {
                        LogMessage.MessageOutput.ConsoleWrite($"The Address: {tmplt.PLCAddress} has not been written #Intent:{strValue} - #Actual:{tmplt.Value} by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);

                        return false;
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Manager is failed to Write Device. The Address: {tmplt.AddressKey} has not been written by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);
                LogMessage.MessageOutput.ConsoleWrite($" - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }


        public void WriteDevice(string strDevice)
        {
            int nCount = 0;
            if (!OperationStarted)
            {
                while (true)
                {
                    if (OperationStarted || nCount > 100)
                        break;

                    nCount++;

                    Thread.Sleep(100);
                }
            }

            PLCAddressTemplate tmplt = AddrMgr.FindPLCAddressElement(strDevice);

            WriteDevice(tmplt);
        }

        public bool WriteDevice(PLCAddressTemplate tmplt)
        {
            try
            {
                int nCount = 0;
                if (!OperationStarted)
                {
                    while (true)
                    {
                        if (OperationStarted || nCount > 100)
                            break;

                        nCount++;
                        Thread.Sleep(100);
                    }
                }

                int nLength = Convert.ToInt32(tmplt.Length);

                if (nLength > 1)
                    WriteBlockDevice(tmplt, nLength, tmplt.RawValue);
                else
                    WriteDeviceSequence(tmplt, nLength, tmplt.RawValue);

                //UpdateValue(tmplt, false);

                return true;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Manager is failed to Write Device. The Address: {tmplt.AddressKey} has not been written by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);
                LogMessage.MessageOutput.ConsoleWrite($" - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }


        bool WriteBlockDevice(PLCAddressTemplate tmplt, int nLength, List<int> vnValues)
        {
            try
            {
                string strAddr = tmplt.Area + (Convert.ToInt32(tmplt.Address)).ToString();
                bool result = PLCManagers[ePLCRWType.Write].WriteBlockDevice(strAddr, vnValues.Count, vnValues.ToArray());

                return result;

                /*
                if (result)
                {
                    int[] anResult = PLCManagers[ePLCRWType.Read].ReadBlockDevice(strAddr, nLength);

                    for(int nIdx = 0; nIdx < vnValues.Count; nIdx++)
                        if (anResult[nIdx] != vnValues[nIdx])
                        {
                            LogMessage.MessageOutput.ConsoleWrite($"During: WriteBlockDevice, The Address: {strAddr} + {nIdx} has not been written #Intent:{vnValues[nIdx]} - #Actual:{anResult[nIdx]} by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);

                            return false;
                        }
                }
                */

            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"During: WriteBlockDevice, PLC Manager is failed to Write Device. The Address: {tmplt.AddressKey} has not been written by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);
                LogMessage.MessageOutput.ConsoleWrite($" - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }

        bool WriteDeviceSequence(PLCAddressTemplate tmplt, int nLength, List<int> vnValues)
        {
            try
            {
                for (int i = 0; i < nLength; i++)
                {
                    //LogMessage.MessageOutput.ConsoleWrite($"The Address: {tmplt.AddressKey}. ", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);

                    string strAddr = tmplt.Area + (Convert.ToInt32(tmplt.Address) + i).ToString();

                    bool result = PLCManagers[ePLCRWType.Write].WriteDevice(strAddr, vnValues[i]);

                    return result;

                    /*
                   if (i >= vnValues.Count)
                       PLCManagers[ePLCRWType.Write].WriteDevice(strAddr, 0);
                   else
                   {
                       bool result = PLCManagers[ePLCRWType.Write].WriteDevice(strAddr, vnValues[i]);

                       return result;


                       if (result)
                       {
                           int nResult = PLCManagers[ePLCRWType.Read].ReadDevice(strAddr);

                           if (nResult != vnValues[i])
                           {
                               LogMessage.MessageOutput.ConsoleWrite($"During: WriteDeviceSequence, The Address: {strAddr} has not been written #Intent:{vnValues[i]} - #Actual:{nResult} by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.NONE);

                               return false;
                           }
                       }

                   }*/
                }
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"During: WriteDeviceSequence, PLC Manager is failed to Write Device. The Address: {tmplt.AddressKey} has not been written by unidentified errors. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);
                LogMessage.MessageOutput.ConsoleWrite($" - Error Message: {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }

            return true;
        }

        List<int> GetWriteValues(PLCAddressTemplate tmplt)
        {
            List<int> vnValues = new List<int>();
            int nLength = Convert.ToInt32(tmplt.Length);
            eDataType eType = (eDataType)Enum.Parse(typeof(eDataType), tmplt.DataType);

            if (eType == eDataType.ASCII)
                vnValues = (from value in CommonUtil.HexStringToBytes(tmplt.Value) select Convert.ToInt32(value)).ToList();
            else if (eType == eDataType.DECIMAL)
            {
                int nInputValue = string.IsNullOrWhiteSpace(tmplt.Value) ? 0 : Convert.ToInt32(tmplt.Value);
                vnValues.Add(nInputValue);
            }

            return vnValues;
        }

        List<int> GetReadValues(PLCAddressTemplate tmplt)
        {
            List<int> vnValues = new List<int>();
            int nLength = Convert.ToInt32(tmplt.Length);

            /*
            for (int i = 0; i < nLength; i++)
            {
                int nAddress = Convert.ToInt32(tmplt.Address) + i;
                int nValue = PLCManagers[ePLCRWType.Read].ReadDevice(tmplt.Area + nAddress.ToString());

                vnValues.Add(nValue);
            }
            */

            if (nLength > 1)
            {
                int[] anResult = PLCManagers[ePLCRWType.Read].ReadBlockDevice(tmplt.PLCAddress, nLength);

                if (anResult != null)
                    vnValues = anResult.ToList();
            }
            else
            {
                for (int i = 0; i < nLength; i++)
                {
                    int nAddress = Convert.ToInt32(tmplt.Address) + i;
                    int nValue = PLCManagers[ePLCRWType.Read].ReadDevice(tmplt.Area + nAddress.ToString());

                    vnValues.Add(nValue);
                }
            }


            return vnValues;
        }
    }
}
