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.MxComponent
{
    public class PLCCommMxCMgr : PLCCommManager
    {
        private int TimeOut;

        public Dictionary<ePLCRWType, PLCModMxCMgr> PLCManagers { get; set; } = new Dictionary<ePLCRWType, PLCModMxCMgr>();
        public FunctionEventHandler EventAlwaysUpdate { get; set; }

        public PLCCommMxCMgr(string strPLCConnInfoXmlPath) : base(strPLCConnInfoXmlPath)
        {
            AddrMgr = new PLCAddressManager(strPLCConnInfoXmlPath);

            EventAlwaysUpdate = new FunctionEventHandler();
            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, EventAlwaysUpdate, EventChangeUpdate));
            }
        }

        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);
                    }

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

                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;
            }
        }

        private bool findType(ePLCRWType type)
        {
            var findValue = PLCManagers.FirstOrDefault(t => t.Key == type);

            if (findValue.Value != null)
                return true;
            else
                return false;
        }

        private PLCModMxCMgr findMgr(ePLCRWType type)
        {
            var findValue = PLCManagers.FirstOrDefault(t => t.Key == type);

            if (findValue.Value != null)
                return findValue.Value;
            else
                return null;
        }

        public override bool StartPLCOperation()
        {
            try
            {
                OperationStarted = true;

                if(findType(ePLCRWType.Read))
                {
                    findMgr(ePLCRWType.Read).SingleScanStartAsync();
                    findMgr(ePLCRWType.Read).StartMonitor();
                }
                if (findType(ePLCRWType.ReadWrite))
                {
                    findMgr(ePLCRWType.ReadWrite).SingleScanStartAsync();
                    findMgr(ePLCRWType.ReadWrite).StartMonitor();
                }

                LogMessage.MessageOutput.ConsoleWrite("PLC Operation has been 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()
        {
            if (findType(ePLCRWType.Read))
                findMgr(ePLCRWType.Read).NowScanDataArea();
            if (findType(ePLCRWType.ReadWrite))
                findMgr(ePLCRWType.ReadWrite).NowScanDataArea();
        }

        public override bool StopPLCOperation()
        {
            try
            {
                OperationStarted = false;

                if (findType(ePLCRWType.Read))
                    findMgr(ePLCRWType.Read).StopMonitor();
                if (findType(ePLCRWType.ReadWrite))
                    findMgr(ePLCRWType.ReadWrite).StopMonitor();

                LogMessage.MessageOutput.ConsoleWrite("PLC Operation has been Stopped.", 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 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 void ReceivePLCEvent(PLCModMxCMgr sender, PLCAddressTemplate template)
        {
            if (template == null)
                return;

            if (IsMySubscription(template))
            {
                ReceivePLCEvent(template.Address, template.RawValue[0]);
            }
        }

        public override void ReceivePLCEvent(string address, int value)
        {
        }

        public bool IsMySubscription(PLCAddressTemplate template)
        {
            if (AddrMgr.OperationDictionary.ContainsValue(template))
                return true;
            if (AddrMgr.DataDictionary.ContainsValue(template))
                return true;

            return false;
        }

        void SetAddress(PLCModMxCMgr module, Dictionary<string, PLCAddressTemplate> addressInfo, bool bOperation)
        {
            foreach (PLCAddressTemplate addrinfo in addressInfo.Values)
                module.AddDevices(addrinfo, bOperation);
        }

        public override void UpdateAll(bool bRefineWrongChar)
        {
            foreach (PLCModMxCMgr conmgr in PLCManagers.Values)
            {
                conmgr.UpdateArea(AddrMgr.OperationDictionary.Values.ToList(), bRefineWrongChar);
                conmgr.UpdateArea(AddrMgr.DataDictionary.Values.ToList(), bRefineWrongChar);
            }
        }

        public override void SelectUpdate(ePLCAreaType pat, PLCAddressTemplate tmplt, bool bRefineWrongChar)
        {
            if (pat == ePLCAreaType.Operation)
            {
                foreach (PLCModMxCMgr conmgr in PLCManagers.Values)
                {
                    conmgr.UpdateSelectArea(tmplt, bRefineWrongChar);
                    //conmgr.UpdateArea(AddrMgr.DataDictionary.Values.ToList(), bRefineWrongChar);
                }
            }
            if (pat == ePLCAreaType.Data)
            {
                foreach (PLCModMxCMgr conmgr in PLCManagers.Values)
                {
                    //conmgr.UpdateArea(AddrMgr.OperationDictionary.Values.ToList(), bRefineWrongChar);
                    conmgr.UpdateSelectArea(tmplt, bRefineWrongChar);
                }
            }
        }

        public override string GetDeviceValue(PLCAddressTemplate tmplt)
        {
            try
            {
                if (!CheckConnectionState(tmplt))
                    return string.Empty;

                string strReadValue = "";

                if (findType(ePLCRWType.Read))
                    strReadValue = findMgr(ePLCRWType.Read).GetDeviceValue(tmplt);
                else if (findType(ePLCRWType.ReadWrite))
                    strReadValue = findMgr(ePLCRWType.ReadWrite).GetDeviceValue(tmplt);

                return strReadValue;
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"PLC Manager is failed to Get Device Value. 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 string ReadDevice(PLCAddressTemplate tmplt)
        {
            try
            {
                if (!CheckConnectionState(tmplt))
                    return string.Empty;

                string strReadValue = "";

                if (findType(ePLCRWType.Read))
                    strReadValue = findMgr(ePLCRWType.Read).ReadDevice(tmplt);
                else if (findType(ePLCRWType.ReadWrite))
                    strReadValue = findMgr(ePLCRWType.ReadWrite).ReadDevice(tmplt);

                return strReadValue;
            }
            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 string ReadDevice(PLCAddressTemplate tmplt, bool bAsciiReverse = false)
        {
            try
            {
                if (!CheckConnectionState(tmplt))
                    return string.Empty;

                string strReadValue = "";

                if (findType(ePLCRWType.Read))
                    strReadValue = findMgr(ePLCRWType.Read).ReadDevice(tmplt, bAsciiReverse);
                else if (findType(ePLCRWType.ReadWrite))
                    strReadValue = findMgr(ePLCRWType.ReadWrite).ReadDevice(tmplt, bAsciiReverse);

                return strReadValue;
            }
            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 bool WriteDevice(PLCAddressTemplate tmplt, string strValue, bool bVerification = false)
        {
            try
            {
                if (!CheckConnectionState(tmplt))
                    return false;

                if (findType(ePLCRWType.Write))
                    findMgr(ePLCRWType.Write).WriteDevice(tmplt, strValue);
                else if (findType(ePLCRWType.ReadWrite))
                    findMgr(ePLCRWType.ReadWrite).WriteDevice(tmplt, strValue);

                if (bVerification)
                {
                    string strReadValue = "";

                    if (findType(ePLCRWType.Read))
                        strReadValue = findMgr(ePLCRWType.Read).ReadDevice(tmplt);
                    else if (findType(ePLCRWType.ReadWrite))
                        strReadValue = findMgr(ePLCRWType.ReadWrite).ReadDevice(tmplt);

                    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;
            }
        }

        bool CheckConnectionState(PLCAddressTemplate tmplt)
        {
            try
            {
                int nCount = 0;
                bool bConError = false;
                if (!OperationStarted)
                {
                    while (true)
                    {
                        if (OperationStarted || nCount > 100)
                        {
                            bConError = true;
                            break;
                        }

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

                if (bConError == true)
                {
                    LogMessage.MessageOutput.ConsoleWrite($"PLC Connection Trouble has been detected. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);
                    LogMessage.MessageOutput.ConsoleWrite($"The Address: {tmplt.PLCAddress} has not been written. ", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                    return false;
                }

                return true;
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}
