using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
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 PLCDeviceMXC
    {
        public FunctionEventHandler EventAlwaysUpdate { get; set; }
        public FunctionEventHandler EventChangeUpdate { get; set; }

        public int STATION_ID { set; get; } //PLC STATION

        private int ScanningIntervalOperation = 100;
        private int ScanningIntervalDataTime = 500;

        private bool MonitorExe = true;

        private Stopwatch stReadTimeCheck;

        private PLCMxComponent _conn;  //MxComponent PLC Instance

        Dictionary<string, PLCAddressTemplate> DicOperationAddresses = new Dictionary<string, PLCAddressTemplate>();
        Dictionary<string, PLCAddressTemplate> DicDataAddresses = new Dictionary<string, PLCAddressTemplate>();

        Stopwatch xSwDiag = new Stopwatch();

        private static object objLock = new object();

        public PLCDeviceMXC()
        {
            stReadTimeCheck = new Stopwatch();
            stReadTimeCheck.Start();
        }

        public bool DevClose()
        {
            if (_conn != null)
            {
                StopMonitor();

                return _conn.Close();
            }

            return true;
        }

        public bool DevOpen()
        {
            try
            {
                _conn = new PLCMxComponent();

                _conn.Initialize();

                return _conn.Open(STATION_ID.ToString());
            }
            catch (Exception ex)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Error during PLC Open : {ex.Message}", ConsoleColor.Red, LogMessage.LogMessageLevel.FATAL);

                return false;
            }
        }

        public bool AddOperationDevices(PLCAddressTemplate deviceReg)
        {
            DicOperationAddresses.Add(deviceReg.PLCAddress, deviceReg);

            return true;
        }
        public bool AddDataDevices(PLCAddressTemplate deviceReg)
        {
            DicDataAddresses.Add(deviceReg.PLCAddress, deviceReg);

            return true;
        }

        public void SingleScanStartAsync()
        {
            lock (objLock)
            {
                ScanDataArea();

                ScanOperationArea(true);
            }
        }
        public void NowDataAreaScan()
        {
            lock (objLock)
            {
                ScanDataArea();
            }
        }
        public void StartMonitor()
        {
            MonitorExe = true;

            Task<int> monitor = new Task<int>(() => ExecuteMonitor().Result);

            monitor.Start();
        }

        public void StopMonitor()
        {
            MonitorExe = false;
        }

        private async Task<int> ExecuteMonitor()
        {
            //int nCountTime = 0;

            stReadTimeCheck.Restart();

            while (MonitorExe)
            {
                lock (objLock)
                {
                    ScanOperationArea();

                    /*
                    if (stReadTimeCheck.ElapsedMilliseconds >= ScanningIntervalDataTime)
                    {
                        ScanDataArea();

                        stReadTimeCheck.Restart();
                    }
                    */

                    //

                    /*
                    if (nCountTime % ScanningIntervalDataTime == 0)
                    {
                        nCountTime = 1;

                        ScanDataArea();
                    }

                    nCountTime++;
                    */
                }

                await Task.Delay(ScanningIntervalOperation);
            }

            return 0;
        }

        void ScanDataArea()
        {
            foreach (PLCAddressTemplate addrTemplate in DicDataAddresses.Values)
                UpdateDevice(addrTemplate, true);
        }

        void ScanOperationArea(bool bSingleScan = false)
        {
            foreach (PLCAddressTemplate addrTemplate in DicOperationAddresses.Values)
            {
                string strFormerValue = addrTemplate.Value;

                UpdateDevice(addrTemplate, true);

                EventAlwaysUpdate.DoReceive(this, addrTemplate);

                if (addrTemplate.Value != strFormerValue)
                    EventChangeUpdate.DoReceive(this, addrTemplate);

                if (bSingleScan)
                    EventChangeUpdate.DoReceive(this, addrTemplate);
            }
        }

        public string GetModuleName()
        {
            return Assembly.GetExecutingAssembly().ManifestModule.Name.Replace(".dll", string.Empty);
        }

        Int16[] ReadPLCData(PLCAddressTemplate template)
        {
            if (_conn == null)
                return null;

            int nSize = Convert.ToInt32(template.Length);

            Int16[] readData = new Int16[nSize];

            _conn.BlockReadData(template.PLCAddress, nSize, ref readData);

            return readData;
        }

        bool WritePLCData(PLCAddressTemplate template, Int16[] arnWriteData)
        {
            if (_conn == null)
                return false;

            int nSize = Convert.ToInt32(template.Length);

            bool ret = _conn.BlockWriteData(template.PLCAddress, nSize, arnWriteData);

            if (ret == false)
            {
                _conn.BlockWriteData(template.PLCAddress, nSize, arnWriteData);

                Thread.Sleep(30);
            }

            Thread.Sleep(30);

            //bool ret = PLC_COMM.WriteData(addr, nSize, arnWriteData);
            return ret;
        }

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

            List<int> vnValues = (from value in ReadPLCData(tmplt) let value32 = Convert.ToInt32(value) select value32).ToList();

            return vnValues;
        }

        List<short> GetWriteValues(PLCAddressTemplate tmplt, string strValue)
        {
            List<short> vnValues = new List<short>();

            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(strValue) select Convert.ToInt16(value)).ToList();
            else if (eType == eDataType.DECIMAL)
            {
                int nInputValue = string.IsNullOrWhiteSpace(strValue) ? 0 : Convert.ToInt16(strValue);
                vnValues.Add(Convert.ToInt16(nInputValue));
            }

            return vnValues;
        }

        void UpdateValue(PLCAddressTemplate tmplt, bool bRefineWrongChar, bool bAsciiReverseBit = false)
        {
            List<int> vValues = GetReadValues(tmplt);

            eDataType eType = (eDataType)Enum.Parse(typeof(eDataType), tmplt.DataType);

            string strData = string.Empty;

            if (eType == eDataType.ASCII)
                strData = CommonUtil.GetStringFromASCII(vValues.ToArray(), bAsciiReverseBit);
            else if (eType == eDataType.BIT)
                strData = vValues[0].ToString();
            else if (eType == eDataType.DECIMAL)
                strData = vValues[0].ToString();

            string strResult = bRefineWrongChar ? RefineInvalidChars(strData) : strData;

            tmplt.SetStringValue(strResult);
        }

        string RefineInvalidChars(string strSource)
        {
            string strReturn = strSource.Trim(InvalidChars);

            strReturn = new string((from cCode in strSource.ToCharArray() where (48 <= Convert.ToInt32(cCode) && Convert.ToInt32(cCode) <= 95) select cCode).ToArray());

            return strReturn;
        }

        public string GetDeviceValue(PLCAddressTemplate tmplt)
        {
            List<PLCAddressTemplate> Oper = DicOperationAddresses.Values.Where(x => x.DataName == tmplt.DataName).ToList();
            List<PLCAddressTemplate> Data = DicDataAddresses.Values.Where(x => x.DataName == tmplt.DataName).ToList();

            if (Oper.Count > 0)
                return Oper.First().Value;
            else if (Data.Count > 0)
                return Data.First().Value;
            else
                return string.Empty;
        }

        public PLCAddressTemplate UpdateDevice(PLCAddressTemplate tmplt, bool bRefineWrongChar, bool bAsciiReverse = false)
        {
            if (tmplt == null)
            {
                LogMessage.MessageOutput.ConsoleWrite($"Cannot Find a Corresponding Data.");

                return null;
            }

            UpdateValue(tmplt, bRefineWrongChar, bAsciiReverse);

            return tmplt;
        }

        public bool WriteDevice(PLCAddressTemplate tmplt, string strValue)
        {
            List<short> vValues = GetWriteValues(tmplt, strValue);

            return WritePLCData(tmplt, vValues.ToArray());
        }
    }
}
