using System;

using System.Threading;
using System.IO.Ports;
using System.Windows.Forms;

using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using DevExpress.XtraGauges.Core.Model;
using System.Collections.Concurrent;
using System.Drawing.Printing;
using System.ComponentModel;

namespace LFP_Manager.Threads
{
    class CsUartThreadSB
    {
        #region DELEGATE
        // 표준 이벤트 패턴
        public sealed class ModuleDataEventArgs : EventArgs
        {
            public CsDeviceData.DeviceModuleData[] Modules { get; }
            public ModuleDataEventArgs(CsDeviceData.DeviceModuleData[] modules) => Modules = modules;
        }
        #endregion

        #region CONSTANTS
        private const int RX_PACKET_BUFFER_SIZE = 512;
        #endregion

        #region VARIABLES

        private readonly CommConfig Config;
        private CsDeviceData.DeviceModuleData[] ModuleData;
        private Thread _serialComm = null;
        private SerialPort sPort = null;
        private CancellationTokenSource _cts;
        private volatile bool _serialPortThreadEnd = true;

        private readonly object _startStopLock = new object();

        private readonly ISynchronizeInvoke _ui; // 보통 Form 또는 Control

        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 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 readonly ConcurrentQueue<byte> _rxQ = new ConcurrentQueue<byte>();

        private TUartTxBuff UartTxBuff;

        // 이벤트
        public event EventHandler<ModuleDataEventArgs> OnUpdate;
        public event EventHandler<DataRecvEventArgs> OnDataRecv;
        public event EventHandler<PrintEventArgs> OnPrint;

        #endregion

        #region CONSTRUCTORS

        public CsUartThreadSB(int mId,
                                 CommConfig aConfig,
                                 ISynchronizeInvoke uiInvoker,
                                 CsDeviceData.DeviceModuleData[] aModuleData)
        {
            ModuleID = mId;
            Config = aConfig;
            ModuleData = aModuleData;

            UartTxBuff = new TUartTxBuff();
        }

        public void Dispose()
        {
            Stop();
        }

        public bool Start(ref CommConfig aConfig, int mID, bool aPolling)
        {
            if (aConfig == null) throw new ArgumentNullException(nameof(aConfig));

            string modelname = csConstData.UART_MODEL[aConfig.UartModelIndex];
            int mQty = aConfig.ModuleQty;

            if (ModuleData == null || ModuleData.Length < mQty)
                throw new InvalidOperationException($"ModuleData length({ModuleData?.Length ?? 0}) < ModuleQty({mQty})");

            for (int i = 0; i < mQty; i++)
            {
                ModuleData[i].mNo = i + 1;
                //ModuleData[i].Information.ModelName = modelname;
            }

            ModuleID = mID;
            UartPolling = aPolling;

            if (!Open(aConfig.UartPort, aConfig.UartBaudrate))
                return false;

            // 이중 시작 방지
            if (_serialComm != null && _serialComm.IsAlive)
                return true;

            _serialPortThreadEnd = false;
            _cts = new CancellationTokenSource();

            _serialComm = new Thread(() => uartCommThreadSB(_cts.Token))
            {
                IsBackground = true,
                Name = "UartCommThreadDelta"
            };
            _serialComm.Start();
            return true;
        }

        public void Stop()
        {
            _serialPortThreadEnd = true;
            var cts = _cts;
            _cts = null;
            if (cts != null && !cts.IsCancellationRequested)
                cts.Cancel();

            var t = _serialComm;
            _serialComm = null;
            if (t != null && t.IsAlive)
            {
                try { t.Join(500); } catch { /* timeout 시 로그만 */ }
            }

            Close(); // SerialPort 닫기 및 핸들러 해제
        }

        #endregion

        #region SAFETY RAISE HELPER
        private void RaiseOnUpdate(CsDeviceData.DeviceModuleData[] snapshot)
        {
            var handler = OnUpdate;
            if (handler == null) return;

            var args = new ModuleDataEventArgs(snapshot);

            if (_ui != null && _ui.InvokeRequired)
            {
                try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
                catch { /* 폼 종료 중 등 예외 무시(로그만) */ }
            }
            else
            {
                handler(this, args);
            }
        }

        private void RaiseOnDataRecv(byte[] data)
        {
            var handler = OnDataRecv;
            if (handler == null) return;

            var args = new DataRecvEventArgs(data);

            if (_ui != null && _ui.InvokeRequired)
            {
                try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
                catch { }
            }
            else
            {
                handler(this, args);
            }
        }

        private void RaiseOnPrint(int id, string msg)
        {
            var handler = OnPrint;
            if (handler == null) return;

            var args = new PrintEventArgs(id, msg);

            if (_ui != null && _ui.InvokeRequired)
            {
                try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
                catch { }
            }
            else
            {
                handler(this, args);
            }
        }
        #endregion

        #region COMMPORT CONTROLS

        private void sDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            try
            {
                var sp = (SerialPort)sender;
                int available = sp.BytesToRead;
                if (available <= 0) return;

                byte[] tmp = new byte[available];
                int read = sp.Read(tmp, 0, tmp.Length);
                for (int i = 0; i < read; i++)
                    _rxQ.Enqueue(tmp[i]);
            }
            catch (Exception)
            {
                /* 필요시 로그 */
            }
        }

        private void sErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
        {
            //
            //csMakeDataFunction.SetData()
        }

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

        private bool Open(string cPort, int cBaudrate)
        {
            sPort = new SerialPort();
            sPort.PortName = cPort;
            sPort.BaudRate = cBaudrate;
            sPort.DataReceived += sDataReceived;
            sPort.ErrorReceived += sErrorReceived;
            sPort.PinChanged += sPinChanged;

            try
            {
                sPort.Open();
            }
            catch (Exception ex)
            {
                throw new Exception("Error Open - " + ex.Message);
            }

            return sPort.IsOpen;
        }

        private void Close()
        {
            if (sPort != null)
            {
                try
                {
                    sPort.DataReceived -= sDataReceived;
                    sPort.ErrorReceived -= sErrorReceived;
                    sPort.PinChanged -= sPinChanged;
                    if (sPort.IsOpen) sPort.Close();
                }
                finally
                {
                    sPort.Dispose();
                    sPort = null;
                }
            }
        }

        #endregion

        #region PUBLIC FUNCTION

        public void SetPolling(bool flag, int mID, ref CsDeviceData.DeviceModuleData[] aModuleData)
        {
            ModuleID = mID;
            UartPolling = flag;
            ModuleData = aModuleData;
            ModuleData[0].mNo = ModuleID;
        }

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

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

            TUartTRxData uartTRxData = new TUartTRxData();
            uartTRxData.type = type;
            uartTRxData.data = WriteData;
            uartTRxData.length = uartTRxData.data.Length;

            UartTxBuff?.PutBuff(uartTRxData);
        }

        public void SetParam(ushort WriteAddr, short WriteData)
        {
            short[] wData = new short[1];
            wData[0] = WriteData;

            byte[] sData = CsSerialCommFunctionDelta.MakeWriteRegisterData((ushort)ModuleID, WriteAddr, wData);

            if (sData != null)
            {
                TUartTRxData aData = new TUartTRxData();
                aData.length = sData.Length;
                aData.data = sData;

                UartTxBuff.PutBuff(aData);
            }
        }

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

            if (sData != null)
            {
                TUartTRxData aData = new TUartTRxData();
                aData.length = sData.Length;
                aData.data = sData;

                ExtReqRegAddr = WriteAddr;

                UartTxBuff.PutBuff(aData);
            }
        }

        #endregion

        #region RX BUFFERING

        private int GetBuff()
        {
            return _rxQ.TryDequeue(out var b) ? (0x0100 | b) : 0;
        }

        private void FlushBuff()
        {
            while (_rxQ.TryDequeue(out _)) { }
        }

        #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 = 3;
                            RequestRegAddr = 0x0FFF;        //Byul Init 0x0000
                            RequestRegLen = 0x27;
                            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 = 0x0000;
                            RequestRegLen = 1;
                            sCmd = CsSerialCommFunctionDelta.READ_DEV_ID;     // Command 0x2B
                            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;        //Byul Init 0x0000
                            RequestRegLen = 27;
                            sCmd = CsSerialCommFunctionDelta.READ_INPUT_REG;     // Command 0x04
                            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[RX_PACKET_BUFFER_SIZE];
        ushort rPosition = 0;
        bool BuffStart = false;

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

        private void uartCommThreadSB(CancellationToken token)
        {
            int RecvTimeout = Config.RecvWaitTime;
            int COMM_TIMEOUT_MS = Config.CommFailTime;   // 5초 동안 수신 없으면 Fail
            DateTime[] LastReceiveTime = new DateTime[csConstData.SystemInfo.MAX_MODULE_SIZE];

            // ★ 마지막 수신 시간 초기화 (현재 시각 기준)
            var now = DateTime.UtcNow;
            for (int i = 0; i < LastReceiveTime.Length; i++)
                LastReceiveTime[i] = now;

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

                FlushBuff();

                byte[] txData = MakeTxDataDelta(false);
                if (txData != null)
                {
                    try
                    {
                        sPort.Write(txData, 0, txData.Length);
                        RaiseOnPrint(txData[0], csLog.trx_data_print(txData, txData.Length, 0));
                    }
                    catch
                    {
                        /* 로그 */
                        RaiseOnPrint(ModuleID, "UART Thread - TX Buff Error !");
                    }
                }

                if (rFlag)
                {
                    var deadline = DateTime.UtcNow.AddMilliseconds(RecvTimeout);
                    RecvPacketInit();

                    while (DateTime.UtcNow < deadline)
                    {
                        int gd = GetBuff();
                        if ((gd & 0x0100) == 0) { Thread.Sleep(1); continue; }

                        byte cData = (byte)(gd & 0xFF);

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

                            int chk = CsSerialCommFunctionDelta.ModbusPacketFromSlaveCheck(ReadBuf, rPosition);
                            if (chk == 0) continue;

                            RaiseOnPrint(ReadBuf[0], csLog.trx_data_print(ReadBuf, rPosition, 1));

                            if (chk == 1)
                            {
                                int mID = ReadBuf[0];
                                if (mID > 0) mID--;
                                if (mID >= 0 && mID < ModuleData.Length)
                                {
                                    // 마지막 정상 업데이트 시간 기록
                                    LastReceiveTime[mID] = DateTime.UtcNow;

                                    ModuleData[mID].CommFail = false;
                                    ModuleData[mID].ShelfCommFail = false;
                                    var rc = CsSerialCommFunctionDelta.SerialRxProcess(
                                                ReadBuf, RequestRegAddr, (ushort)rPosition, ref ModuleData[mID]);
                                }

                                var snapshot1 = ModuleData;                  // 필요 시 DeepCopy
                                RaiseOnUpdate(snapshot1);
                                Thread.Sleep(5);
                                goto StartSend;
                            }
                            else if (chk == 2)
                            {
                                rFlag = false;
                                RaiseOnPrint(ReadBuf[0], csLog.trx_data_print(ReadBuf, rPosition, 1));

                                byte[] adata = new byte[rPosition + 1];
                                for (int j = 0; j < adata.Length; j++)
                                {
                                    adata[j] = ReadBuf[j];
                                }
                                RaiseOnDataRecv(adata);

                                goto StartSend;
                            }
                            else // -1
                            {
                                RecvPacketInit();
                                Thread.Sleep(50);
                                goto StartSend;
                            }
                        }
                    }

                    // timeout
                    if (rPosition > 0)
                    {
                        RaiseOnPrint(ReadBuf[0], csLog.trx_data_print(ReadBuf, rPosition, 1));
                    }
                    else
                    {
                        int idx = csUtils.MathEx.Clamp(ModuleID - 1, 0, csConstData.SystemInfo.MAX_MODULE_SIZE - 1);

                        // 아직 한 번도 정상 수신한 적 없는 경우 MinValue일 수 있으므로,
                        // 첫 타임아웃에서는 기준 시간을 현재로 초기화해 준다.
                        if (LastReceiveTime[idx] == DateTime.MinValue)
                            LastReceiveTime[idx] = DateTime.UtcNow;

                        // 시간 차이 계산
                        var elapsed = (DateTime.Now - LastReceiveTime[idx]).TotalMilliseconds;

                        if (elapsed >= COMM_TIMEOUT_MS)
                        {
                            // Fail 처리
                            if (!ModuleData[idx].ShelfCommFail)
                            {
                                ModuleData[idx].Reset();
                                ModuleData[idx].ShelfCommFail = true;
                            }

                            ModuleData[idx].CommFail = true;
                            ModuleData[idx].ShelfCommFail = true;
                        }
                        Thread.Sleep(100);
                    }
                    var snapshot2 = ModuleData;                  // 필요 시 DeepCopy
                    RaiseOnUpdate(snapshot2);
                }
                else
                {
                    Thread.Sleep(10);
                }   /* if (rFlag == true) */
                rPosition = 0;
                Thread.Sleep(100);
            }
        }

        #endregion
    }
}
