using DevExpress.XtraGauges.Core.Model;
using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LFP_Manager.Threads
{
    class CsRs485Thread124050
    {
        #region VARIABLES

        private readonly CommConfig Config;
        private DeviceSystemData[] SystemData;
        private Task serialCommTask = null;
        private CancellationTokenSource _cts;
        private SerialPort sPort = null;
        private volatile bool SerialPortThreadEnd = true;

        public int ModuleID = 1;
        private bool UartPolling = false;
        private bool AutoUartTx = true;

        private int addr_ch = 0;
        private ushort RequestRegAddr = 0x0000;                 //Byul Init 0x0000
        private ushort RequestRegLen = 50;
        private volatile bool rFlag = false;
        private int wFlag = 0;
        private int rFlag2 = 0;

        private ushort ExtReqRegAddr = 0x0000;

        private ushort WriteRegAddr = 0x0000;                   //Byul Init 0x0000
        private short WriteCoilRegData = 0;
        private byte[] WriteRegData;
        private short WriteParamRegData;

        private TUartTxBuff UartTxBuff;

        public event UartDataUpdateRS485 OnUpdate = null;
        public event UartDataRecvRS485 OnDataRecv = null;
        public event UartDataPrintRS485 OnPrint = null;

        #endregion

        #region CONSTRUCTORS

        public CsRs485Thread124050(int mID, CommConfig aConfig, ref DeviceSystemData[] aSystemData)
        {
            ModuleID = mID;
            Config = aConfig;
            SystemData = aSystemData;

            UartTxBuff = new TUartTxBuff();
            _cts = new CancellationTokenSource();
        }

        public void disposeThread()
        {
            try
            {
                Stop();
            }
            catch { }

            Close();
            _cts?.Dispose();
            _cts = null;
        }

        public bool Start(ref CommConfig aConfig, int mID, bool aPolling)
        {
            bool result = false;

            ModuleID = mID;
            UartPolling = aPolling;

            // Port open with configured baudrate (fallback to 9600)
            int baud = (aConfig?.UartBaudrate > 0) ? aConfig.UartBaudrate : 9600;
            if (Open(aConfig.UartPort, baud))
            {
                SerialPortThreadEnd = false;
                // recreate CTS for this run
                _cts?.Dispose();
                _cts = new CancellationTokenSource();
                serialCommTask = Task.Run(() => uartCommThread(_cts.Token), _cts.Token);
                result = true;
            }
            return result;
        }

        public void Stop()
        {
            try
            {
                SerialPortThreadEnd = true;
                if (_cts != null && !_cts.IsCancellationRequested)
                    _cts.Cancel();

                serialCommTask?.Wait(TimeSpan.FromSeconds(5));
            }
            catch (AggregateException) { }
            catch (OperationCanceledException) { }
            catch { }
            finally
            {
                Close();
            }
        }

        #endregion

        #region COMMPORT CONTROLS

        private readonly object _rxLock = new object();
        private readonly object _portLock = new object();

        private void sDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            try
            {
                if (sPort == null || !sPort.IsOpen) return;

                byte[] sRead = new byte[1024];
                int rLen = sPort.Read(sRead, 0, sRead.Length);

                lock (_rxLock)
                {
                    for (int i = 0; i < rLen; i++)
                    {
                        PutBuff(sRead[i]);
                    }
                }
            }
            catch (Exception ex)
            {
                // 무시하지 말고 로그 출력
                OnPrint?.Invoke(this, $"DataRecv Error: {ex.Message}");
            }
        }

        private void sErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
        {
            OnPrint?.Invoke(this, $"Serial Error: {e.EventType}");
        }

        private void sPinChanged(object sender, System.IO.Ports.SerialPinChangedEventArgs e)
        {
            //
        }

        private bool Open(string cPort, int cBaudrate)
        {
            lock (_portLock)
            {
                try
                {
                    if (sPort != null)
                    {
                        if (sPort.IsOpen) sPort.Close();
                        sPort.Dispose();
                        sPort = null;
                    }

                    sPort = new SerialPort
                    {
                        PortName = cPort,
                        BaudRate = cBaudrate,
                        DataBits = 8,
                        Parity = Parity.None,
                        StopBits = StopBits.One,
                        ReadTimeout = 500,
                        WriteTimeout = 500
                    };

                    sPort.DataReceived += sDataReceived;
                    sPort.ErrorReceived += sErrorReceived;
                    sPort.PinChanged += sPinChanged;

                    sPort.Open();
                    return sPort.IsOpen;
                }
                catch (Exception ex)
                {
                    OnPrint?.Invoke(this, $"Error Open - {ex.Message}");
                    if (sPort != null)
                    {
                        try { sPort.Dispose(); } catch { }
                        sPort = null;
                    }
                    return false;
                }
            }
        }

        private void Close()
        {
            lock (_portLock)
            {
                try
                {
                    if (sPort != null)
                    {
                        try
                        {
                            sPort.DataReceived -= sDataReceived;
                            sPort.ErrorReceived -= sErrorReceived;
                            sPort.PinChanged -= sPinChanged;
                        }
                        catch { }

                        if (sPort.IsOpen) sPort.Close();
                        sPort.Dispose();
                        sPort = null;
                    }
                }
                catch (Exception ex)
                {
                    OnPrint?.Invoke(this, $"Port Close Fail: {ex.Message}");
                }
            }
        }

        #endregion

        #region PUBLIC FUNCTION

        public void SetPolling(bool flag, int mID, ref DeviceSystemData[] aSystemData)
        {
            ModuleID = mID;
            UartPolling = flag;
            SystemData = aSystemData;
            if (SystemData != null && ModuleID - 1 >= 0 && ModuleID - 1 < SystemData.Length)
                SystemData[ModuleID - 1].mNo = ModuleID;
        }

        public void SetAutoTx(bool autoTx)
        {
            AutoUartTx = autoTx;
        }

        public void SetWriteReg(ushort WriteAddr, byte[] WriteData, bool ReplyFlag, int type)
        {
            WriteRegAddr = WriteAddr;

            var uartTRxData = new TUartTRxData
            {
                type = type,
                data = WriteData,
                length = WriteData?.Length ?? 0
            };

            UartTxBuff?.PutBuff(uartTRxData);
        }

        public void SetParam(ushort WriteAddr, short WriteData)
        {
            byte[] sData = CsRs485CommFunction124050.MakeWriteRegisterData((ushort)ModuleID, WriteAddr, WriteData);

            if (sData != null)
            {
                var aData = new TUartTRxData { length = sData.Length, data = sData };
                UartTxBuff.PutBuff(aData);
            }
        }

        public void SetReadWriteParam(ushort WriteAddr, short WriteData)
        {
            byte[] sData = CsRs485CommFunction124050.MakeReadWriteRegisterData((ushort)ModuleID, WriteAddr, WriteData);

            if (sData != null)
            {
                var aData = new TUartTRxData { length = sData.Length, data = sData };
                ExtReqRegAddr = WriteAddr;
                UartTxBuff.PutBuff(aData);
            }
        }

        #endregion

        #region RX BUFFERING

        private const int BUFFER_SIZE = 512;

        private readonly byte[] rBuffer = new byte[BUFFER_SIZE];
        private int rBufStart = 0;
        private int rBufEnd = 0;

        private void PutBuff(byte c)
        {
            // called from DataReceived event -> lock to be safe
            lock (_rxLock)
            {
                rBuffer[rBufStart++] = c;
                rBufStart %= BUFFER_SIZE;

                if (rBufStart == rBufEnd)
                {
                    rBufEnd++;
                    rBufEnd %= BUFFER_SIZE;
                }
            }
        }

        private int GetBuff()
        {
            lock (_rxLock)
            {
                int result = -1;

                if (rBufStart != rBufEnd)
                {
                    result = 0x0100 + rBuffer[rBufEnd++];
                    rBufEnd %= BUFFER_SIZE;
                }
                return result;
            }
        }

        private void FlushBuff()
        {
            lock (_rxLock)
            {
                rBufStart = rBufEnd = 0;
            }
        }

        #endregion

        #region TX BUFFERING

        private byte[] MakeTxDataDelta(bool wData)
        {
            byte[] sData = null;

            if ((UartTxBuff != null) && (UartTxBuff.CheckBuff()))
            {
                TUartTRxData sBuff = UartTxBuff.GetBuff();
                if (sBuff != null)
                {
                    sData = sBuff.data;
                    wData = true;
                    rFlag = true;
                    rFlag2 = sBuff.type;

                    RequestRegAddr = ExtReqRegAddr;
                }
            }
            else
            {
                if (AutoUartTx && UartPolling)
                {
                    ushort sCmd;
                    switch (addr_ch)
                    {
                        case 0:         // Battery Status Data
                            addr_ch = 1;
                            RequestRegAddr = 0x0FFF;        //Byul Init 0x0000
                            RequestRegLen = 0x4C;
                            sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG;     // Command 0x04
                            rFlag = true;
                            break;
                        case 1:         // Gyroscope Data
                            addr_ch = 2;
                            RequestRegAddr = 0x4000;
                            RequestRegLen = 7;
                            sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG;     // Command 0x17
                            rFlag = true;
                            break;
                        case 2:
                            addr_ch = 3;
                            RequestRegAddr = 0x3000;
                            RequestRegLen = 3;
                            sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG;     // Command 0x19
                            rFlag = true;
                            break;
                        case 3:
                            addr_ch = 4;
                            RequestRegAddr = 100;
                            RequestRegLen = 28;
                            sCmd = CsSerialCommFunctionDelta.READ_HOLDING_REG;     // Command 0x03
                            rFlag = true;
                            break;
                        case 4:
                            if (Config.ModuleQty > 1)
                            {
                                ModuleID++;
                                if (ModuleID > Config.ModuleQty)
                                {
                                    ModuleID = 1;
                                }
                            }
                            addr_ch = 0;
                            sCmd = CsSerialCommFunctionDelta.NO_CMD;
                            rFlag = false;
                            break;
                        default:
                            addr_ch = 0;
                            RequestRegAddr = 0x0FFF;
                            RequestRegLen = 27;
                            sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG;
                            rFlag = true;
                            break;
                    }
                    if (sCmd == CsSerialCommFunctionDelta.NO_CMD)
                    {
                        sData = null;
                    }
                    else if (sCmd == CsSerialCommFunctionDelta.READ_DEV_ID)
                    {
                        sData = CsSerialCommFunctionDelta.MakeReadDevIdRegReqData((ushort)ModuleID, sCmd, RequestRegAddr);
                    }
                    else
                    {
                        sData = CsSerialCommFunctionDelta.MakeReadRegisterData((ushort)ModuleID, sCmd, RequestRegAddr, RequestRegLen);
                    }
                }
            }
            return sData;
        }

        #endregion

        #region COMM THREAD

        private readonly byte[] ReadBuf = new byte[BUFFER_SIZE];
        ushort rPosition = 0;
        bool BuffStart = false;

        private void RecvPacketInit()
        {
            BuffStart = false;
            rPosition = 0;
        }

        private void uartCommThread(CancellationToken token)
        {
            try
            {
                int RecvTimeout = (Config != null) ? Config.RecvWaitTime : 1500;
                int getData = 0;
                byte cData = 0;
                int[] TimeOutCount = new int[csConstData.SystemInfo.MAX_MODULE_SIZE];

            StartSend:
                while (!token.IsCancellationRequested && SerialPortThreadEnd == false)
                {
                    if ((sPort == null) || (sPort.IsOpen == false))
                    {
                        Thread.Sleep(100);
                        continue;
                    }

                    FlushBuff();
                    byte[] txData = MakeTxDataDelta(false);
                    if (sPort == null) return;
                    if (txData != null)
                    {
                        try
                        {
                            sPort.Write(txData, 0, txData.Length);
                            OnPrint?.Invoke(this, csLog.trx_data_print(txData, txData.Length, 0));
                        }
                        catch (Exception ex)
                        {
                            OnPrint?.Invoke(this, $"Write error: {ex.Message}");
                        }
                    }

                    if (rFlag == true)
                    {
                        DateTime rDateTime = DateTime.Now;

                        RecvPacketInit();

                        while (!token.IsCancellationRequested)
                        {
                            DateTime nDateTime = rDateTime.AddMilliseconds(RecvTimeout);
                            if (nDateTime < DateTime.Now) break;

                            getData = GetBuff();
                            if ((getData & 0x0100) != 0x0000)
                            {
                                rDateTime = DateTime.Now;

                                cData = (byte)(getData & 0x00FF);

                                if (rPosition >= BUFFER_SIZE) RecvPacketInit();

                                if (!BuffStart)
                                {
                                    if ((cData == (byte)ModuleID) || (cData == (byte)0x7F))
                                    {
                                        rPosition = 0;
                                        ReadBuf[rPosition++] = cData;
                                        BuffStart = true;
                                    }
                                }
                                else
                                {
                                    ReadBuf[rPosition++] = cData;

                                    int chk = CsRs485CommFunction124050.ModbusPacketFromSlaveCheck(ReadBuf, rPosition);
                                    switch (chk)
                                    {
                                        case 0:         // Need more data
                                            break;
                                        case 1:         // Packet OK, no error
                                            OnPrint?.Invoke(this, csLog.trx_data_print(ReadBuf, rPosition, 1));

                                            TimeOutCount[ModuleID - 1] = 0;

                                            int mID = ReadBuf[0];
                                            if (mID > 0) mID--;

                                            SystemData[mID].CommFail = false;
                                            SystemData[mID].ShelfCommFail = false;
                                            short[] result_code = CsRs485CommFunction124050.SerialRxProcess(ReadBuf, RequestRegAddr, rPosition, ref SystemData[mID]);

                                            OnUpdate?.Invoke(this, ref SystemData);

                                            Thread.Sleep(5);
                                            goto StartSend;
                                        case 2:         // Fw Update Packet OK
                                            OnPrint?.Invoke(this, csLog.trx_data_print(ReadBuf, rPosition, 1));

                                            TimeOutCount[ModuleID - 1] = 0;
                                            rFlag = false;
                                            if (OnDataRecv != null)
                                            {
                                                var adata = new byte[rPosition];
                                                Array.Copy(ReadBuf, 0, adata, 0, rPosition);
                                                OnDataRecv(adata);
                                            }
                                            goto StartSend;
                                        case -1:        // Packet error
                                            RecvPacketInit();
                                            Thread.Sleep(100);
                                            goto StartSend;
                                        default:
                                            break;
                                    }
                                }
                                Thread.Sleep(1);
                            }
                            else
                            {
                                Thread.Sleep(1);
                            }
                        }

                        if (rPosition > 0)
                        {
                            OnPrint?.Invoke(this, csLog.trx_data_print(ReadBuf, rPosition, 1));
                            Thread.Sleep(1);
                        }
                        else
                        {
                            TimeOutCount[ModuleID - 1]++;
                            if (TimeOutCount[ModuleID - 1] >= 10)
                            {
                                TimeOutCount[ModuleID - 1] = 10;
                                if (SystemData[ModuleID - 1].ShelfCommFail == false)
                                {
                                    csUtils.DataInit(Config, ref SystemData[ModuleID - 1]);
                                    SystemData[ModuleID - 1].ShelfCommFail = true;
                                }
                            }
                            Thread.Sleep(100);
                        }
                        OnUpdate?.Invoke(this, ref SystemData);
                        Thread.Sleep(1);
                    }
                    else
                    {
                        Thread.Sleep(10);
                    }   /* if (rFlag == true) */
                    rPosition = 0;
                    Thread.Sleep(100);
                }
            }
            catch (OperationCanceledException) { }
            catch (Exception ex)
            {
                OnPrint?.Invoke(this, $"Comm thread error: {ex.Message}");
            }
            finally
            {
                SerialPortThreadEnd = true;
            }
        }

        #endregion
    }
}
