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.ComponentModel;

namespace LFP_Manager.Threads
{
    #region DELEGATE
    // 표준 이벤트 패턴
    public sealed class DataRecvEventArgs : EventArgs
    {
        public byte[] Data { get; }
        public DataRecvEventArgs(byte[] data) => Data = data;
    }

    public sealed class PrintEventArgs : EventArgs
    {
        public int Id { get; }
        public string Message { get; }
        public PrintEventArgs(int id, string message)
            => (Id, Message) = (id, message);
    }
    #endregion

    class csUartThread
    {
        #region DELEGATE
        // 표준 이벤트 패턴
        public sealed class ModuleDataEventArgs : EventArgs
        {
            public CsDeviceData.DeviceModuleData Module { get; }
            public ModuleDataEventArgs(CsDeviceData.DeviceModuleData module) => Module = module;
        }
        #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 ISynchronizeInvoke _ui; // 보통 Form 또는 Control

        public int ModuleId = 0;
        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 rFlag2 = 0;

        private ushort ReadRegAddr = 0x0000;                   //Byul Init 0x0000
        private ushort ReadRegLen = 0x0000;                   //Byul Init 0x0000

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

        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 csUartThread(int mId,
                         CommConfig aConfig,
                         ISynchronizeInvoke uiInvoker,   // ← 추가
                         CsDeviceData.DeviceModuleData aModuleData)
        {
            ModuleId = mId;
            Config = aConfig;
            ModuleData = aModuleData;
            _ui = uiInvoker; // Form(this) 또는 어떤 Control

            UartTxBuff = new TUartTxBuff();
        }

        public bool Start(ref CommConfig aConfig, int sId, bool aPolling)
        {
            ModuleId = sId;
            UartPolling = aPolling;

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

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

            _serialPortThreadEnd = false;
            _cts = new CancellationTokenSource();

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

        private 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 닫기 및 핸들러 해제
        }

        public void Dispose()
        {
            Stop();
        }

        #endregion

        #region COMMPORT CONTROLS

        private void sDataReceived(object sender, 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 sId, ref CsDeviceData.DeviceModuleData aSystemData)
        {
            ModuleId = sId;
            UartPolling = flag;
            ModuleData = aSystemData;
            ModuleData.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[0];

            byte[] sData = CsModbusFunction.MakeWriteRegisterData((ushort)ModuleId, WriteAddr, wData);

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

                UartTxBuff.PutBuff(aData);
            }
        }

        public void SendProcessFromApp(int sId, int mode, int index, int flag
            , ref CsDeviceData.DeviceModuleData.DeviceParamData aParam, ref CsDeviceData.DeviceModuleData.DeviceCalibration aCalib)
        {
            short[] data = null;

            switch (mode)
            {
                case csConstData.UART_PARAM_LIST.CELL_UNDER_VOLTAGE:         // Cell Under Volatge Parameter
                    data = new short[1];
                    data[0] = aParam.CellUnderVoltageWarning;   SetParam((ushort)(mode + 0), data);
                    data[0] = aParam.CellUnderVoltageTrip;      SetParam((ushort)(mode + 1), data);
                    data[0] = aParam.CellUnderVoltageRelease;   SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.CELL_OVER_VOLTAGE:         // Cell Over Volatge Parameter
                    data = new short[1];
                    data[0] = aParam.CellOverVoltageWarning; SetParam((ushort)(mode + 0), data);
                    data[0] = aParam.CellOverVoltageTrip; SetParam((ushort)(mode + 1), data);
                    data[0] = aParam.CellOverVoltageRelease; SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.PACK_UNDER_VOLTAGE:         // Pack Under Volatge Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.SysUnderVoltageWarning * 10); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.SysUnderVoltageTrip * 10); SetParam((ushort)(mode + 1), data);
                    data[0] = (short)(aParam.SysUnderVoltageRelease * 10); SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.PACK_OVER_VOLTAGE:         // Pack Over Volatge Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.SysOverVoltageWarning * 10); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.SysOverVoltageTrip * 10); SetParam((ushort)(mode + 1), data);
                    data[0] = (short)(aParam.SysOverVoltageRelease * 10); SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.CHG_LOW_TEMPERATURE:         // Charge Low Temperature Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.ChaLowTempWarning + 50); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.ChaLowTempTrip + 50); SetParam((ushort)(mode + 1), data);
                    data[0] = (short)(aParam.ChaLowTempRelease + 50); SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.CHG_HIGH_TEMPERATURE:         // Pack High Temperature Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.ChaHighTempWarning + 50); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.ChaHighTempTrip + 50); SetParam((ushort)(mode + 1), data);
                    data[0] = (short)(aParam.ChaHighTempRelease + 50); SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.DCH_LOW_TEMPERATURE:         // Charge Low Temperature Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.DchLowTempWarning + 50); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.DchLowTempTrip + 50); SetParam((ushort)(mode + 1), data);
                    data[0] = (short)(aParam.DchLowTempRelease + 50); SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.DCH_HIGH_TEMPERATURE:         // Pack High Temperature Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.DchHighTempWarning + 50); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.DchHighTempTrip + 50); SetParam((ushort)(mode + 1), data);
                    data[0] = (short)(aParam.DchHighTempRelease + 50); SetParam((ushort)(mode + 2), data);
                    break;
                case csConstData.UART_PARAM_LIST.CHG_OVER_CURRENT:         // Charge Over Current Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.ChaOverCurrentReleaseTime * 1); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.ChaOverCurrentTrip1 * 10); SetParam((ushort)(mode + 2), data);
                    data[0] = (short)(aParam.ChaOverCurrentTrip2 * 10); SetParam((ushort)(mode + 5), data);
                    break;
                case csConstData.UART_PARAM_LIST.DCH_OVER_CURRENT:         // Discharge Over Current Parameter
                    data = new short[1];
                    data[0] = (short)(aParam.DchOverCurrentReleaseTime * 1); SetParam((ushort)(mode + 0), data);
                    data[0] = (short)(aParam.DchOverCurrentTrip1 * 10); SetParam((ushort)(mode + 2), data);
                    data[0] = (short)(aParam.DchOverCurrentTrip2 * 10); SetParam((ushort)(mode + 5), data);
                    break;
                case csConstData.UART_PARAM_LIST.LOW_CAPACITY:
                    data = new short[1];
                    data[0] = aParam.LowSocWarning; SetParam((ushort)(mode + 0), data);
                    break;
                case csConstData.UART_PARAM_LIST.DEFAULT_PARAM:         // Default Parameter
                    int i = 0;

                    Forms.fmxWait WaitForm = new Forms.fmxWait();
                    WaitForm.StartPosition = FormStartPosition.CenterScreen;

                    try
                    {
                        //WaitForm.Show();
                        data = new short[1];

                        i = csConstData.UART_PARAM_LIST.CELL_UNDER_VOLTAGE;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Warn_Cell_UV; SetParam((ushort)(i + 0), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Cell_UV; SetParam((ushort)(i + 1), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Release_Cell_UV; SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.PACK_UNDER_VOLTAGE;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Warn_Pack_UV; SetParam((ushort)(i + 0), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Pack_UV; SetParam((ushort)(i + 1), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Release_Pack_UV; SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.CELL_OVER_VOLTAGE;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Warn_Cell_OV; SetParam((ushort)(i + 0), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Cell_OV; SetParam((ushort)(i + 1), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Release_Cell_OV; SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.PACK_OVER_VOLTAGE;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Warn_Pack_OV; SetParam((ushort)(i + 0), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Pack_OV; SetParam((ushort)(i + 1), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Release_Pack_OV; SetParam((ushort)(i + 2), data);

                        i = csConstData.UART_PARAM_LIST.CHG_OVER_CURRENT;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Times_Charge_OC; SetParam((ushort)(i - 2), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.ReleaseTime_Charge_OC; SetParam((ushort)(i + 0), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Charge_OC1; SetParam((ushort)(i + 2), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Charge_OC2; SetParam((ushort)(i + 5), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Delay_Charge_OC1; SetParam((ushort)(i + 8), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Delay_Charge_OC2; SetParam((ushort)(i + 9), data);

                        i = csConstData.UART_PARAM_LIST.DCH_OVER_CURRENT;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Times_Discharge_OC; SetParam((ushort)(i - 2), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.ReleaseTime_Discharge_OC; SetParam((ushort)(i + 0), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Discharge_OC1; SetParam((ushort)(i + 2), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Protect_Discharge_OC2; SetParam((ushort)(i + 5), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Delay_Discharge_OC1; SetParam((ushort)(i + 8), data);
                        data[0] = csConstData.UART_PARAM_DEFAULT.Delay_Discharge_OC2; SetParam((ushort)(i + 9), data);

                        i = csConstData.UART_PARAM_LIST.CHG_LOW_TEMPERATURE;
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Warn_Charge_UT + 50); SetParam((ushort)(i + 0), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Protect_Charge_UT + 50); SetParam((ushort)(i + 1), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Release_Charge_UT + 50); SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.CHG_HIGH_TEMPERATURE;
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Warn_Charge_OT + 50); SetParam((ushort)(i + 0), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Protect_Charge_OT + 50); SetParam((ushort)(i + 1), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Release_Charge_OT + 50); SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.DCH_LOW_TEMPERATURE;
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Warn_Discharge_UT + 50); SetParam((ushort)(i + 0), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Protect_Discharge_UT + 50); SetParam((ushort)(i + 1), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Release_Discharge_UT + 50); SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.DCH_HIGH_TEMPERATURE;
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Warn_Discharge_OT + 50); SetParam((ushort)(i + 0), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Protect_Discharge_OT + 50); SetParam((ushort)(i + 1), data);
                        data[0] = (short)(csConstData.UART_PARAM_DEFAULT.Release_Discharge_OT + 50); SetParam((ushort)(i + 2), data);

                        i = csConstData.UART_PARAM_LIST.LOW_CAPACITY;
                        data[0] = csConstData.UART_PARAM_DEFAULT.Warn_Low_Capacity + 0; SetParam((ushort)(i + 0), data);

                        i = csConstData.UART_PARAM_LIST.ENV_LOW_TEMPERATURE;
                        data[0] = 0; SetParam((ushort)(i + 0), data);
                        data[0] = 0; SetParam((ushort)(i + 1), data);
                        data[0] = 0; SetParam((ushort)(i + 2), data);
                        i = csConstData.UART_PARAM_LIST.ENV_HIGH_TEMPERATURE;
                        data[0] = 0; SetParam((ushort)(i + 0), data);
                        data[0] = 0; SetParam((ushort)(i + 1), data);
                        data[0] = 0; SetParam((ushort)(i + 2), data);
                    }
                    catch (Exception)
                    {

                    }
                    finally
                    {
                        //WaitForm?.Close();
                    }
                    break;
                case 15:         // Capcity change
                    data = new short[1];
                    i = csConstData.UART_PARAM_LIST.DESIGN_CAPACITY;
                    data[0] = (short)aCalib.CapCalib.DesignCapacity; SetParam((ushort)(i + 0), data);
                    i = csConstData.UART_PARAM_LIST.SOC_VALUE;
                    data[0] = (short)aCalib.CapCalib.SocValue; SetParam((ushort)(i + 0), data);
                    break;
                case 16:         // Charge Mode
                    data = new short[1];
                    i = csConstData.UART_CALIB_ADDR_LIST.CHAGE_MODE;
                    data[0] = (short)aCalib.ChaMode.Mode; SetParam((ushort)(i + 0), data);
                    data[0] = (short)aCalib.ChaMode.Value; SetParam((ushort)(i + 1), data);
                    break;
                case 17:         // BMS DateTime Set
                    data = new short[2];
                    i = csConstData.UART_CALIB_ADDR_LIST.BMS_DATETIME;
                    data[0] = (short)aCalib.BmsDateTime.sValue[0]; 
                    data[1] = (short)aCalib.BmsDateTime.sValue[1]; SetParam((ushort)(i + 0), data);
                    break;
                case 18:
                    data = new short[1];
                    i = csConstData.UART_CALIB_ADDR_LIST.ANTI_THEFT_GYRO;
                    data[0] = (short)aCalib.AntiTheft.GyroScope; SetParam((ushort)(i + 0), data);
                    break;
                case 19:    // Clear Protect
                    data = new short[1];
                    i = csConstData.UART_CALIB_ADDR_LIST.ANTI_THEFT_GYRO;
                    data[0] = (short)0; SetParam((ushort)(i + 0), data);
                    data[0] = (short)aCalib.AntiTheft.GyroScope; SetParam((ushort)(i + 0), data);
                    break;
                case 20:
                    data = new short[1];
                    i = csConstData.UART_CALIB_ADDR_LIST.ANTI_THEFT_COMM;
                    data[0] = (short)aCalib.AntiTheft.Comm; SetParam((ushort)(i + 0), data);
                    break;
                default:
                    break;
            }
        }

        #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[] MakeWriteRegData(ushort DevID, ushort WriteAddr, ushort WriteLen, ref ushort[] WriteData)
        {
            byte[] wData = null;

            if (WriteLen > 0)
            {

            }

            return wData;
        }

        private string MakeCheckSum(byte[] sData, int len)
        {
            string result = "";
            int checksum = 0;

            for (int i = 0; i < len; i++)
            {
                checksum += sData[i + 1];
            }

            checksum = ~checksum + 1;

            result = String.Format("{0:X2}{1:X2}", (byte)(checksum >> 8), (byte)checksum);

            return result;
        }

        private byte[] MakeTxDataUart()
        {
            byte[] sData = null;

            rFlag2 = 0;

            if ((UartTxBuff != null) && (UartTxBuff.CheckBuff()))
            {
                TUartTRxData sBuff = UartTxBuff.GetBuff();
                if (sBuff != null)
                {
                    sData = sBuff.data;
                    rFlag = true;
                    rFlag2 = sBuff.type;
                }
            }
            else
            {
                if (UartPolling && AutoUartTx)
                {
                    ushort sCmd;
                    switch (addr_ch)
                    {
                        case 0:
                            addr_ch = 1;
                            RequestRegAddr = 0x0000;        //Byul Init 0x0000
                            RequestRegLen = 0x0040;
                            sCmd = csSerialCommFunction.READ_HOLDING_REG;
                            rFlag = true;
                            break;
                        case 1:
                            addr_ch = 2;
                            RequestRegAddr = 0x0040;
                            RequestRegLen = 0x0040;
                            sCmd = csSerialCommFunction.READ_HOLDING_REG;
                            rFlag = true;
                            break;
                        case 2:
                            addr_ch = 3;
                            RequestRegAddr = 0x0080;
                            RequestRegLen = 0x0020 + 7;
                            sCmd = csSerialCommFunction.READ_HOLDING_REG;
                            rFlag = true;
                            break;
                        case 3:
                            if (Config.ModuleQty > 1)
                            {
                                ModuleId++;
                                if (ModuleId > Config.ModuleQty)
                                {
                                    ModuleId = 1;
                                }
                            }
                            addr_ch = 0;
                            sCmd = csSerialCommFunction.NO_CMD;
                            rFlag = false;
                            break;
                        default:
                            addr_ch = 0;
                            RequestRegAddr = 0x0000;        //Byul Init 0x0000
                            RequestRegLen = 100;
                            sCmd = csSerialCommFunction.READ_HOLDING_REG;
                            break;
                    }
                    if (sCmd == csSerialCommFunction.NO_CMD)
                    {
                        sData = null;
                    }
                    else
                    {
                        sData = csSerialCommFunction.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 uartCommThread(CancellationToken token)
        {
            int RecvTimeout = Config.RecvWaitTime;
            int TimeOutCount = 0;

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

                FlushBuff();

                byte[] txData = MakeTxDataUart();
                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-232 Thread - TX Buff Error !");
                    }
                }

                if (rFlag == true)
                {
                    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--;
                                ModuleData.CommFail = false;
                                ModuleData.ShelfCommFail = false;
                                var rc = csSerialCommFunction.SerialRxProcess(
                                            ReadBuf, RequestRegAddr, (ushort)rPosition, ref ModuleData);

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

                                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
                    {
                        WriteRegAddr = 0;
                        TimeOutCount++;
                        if (TimeOutCount >= 5)
                        {
                            TimeOutCount = 5;
                            //SystemData[0].CommFail = true;
                            if (ModuleData.ShelfCommFail == false)
                            {
                                ModuleData.Reset();
                                ModuleData.ShelfCommFail = true;
                            }
                        }
                        Thread.Sleep(100);
                    }
                    var snapshot2 = ModuleData;                  // 필요 시 DeepCopy
                    RaiseOnUpdate(snapshot2);
                }
                else
                {
                    Thread.Sleep(100);
                }   /* if (rFlag == true) */
                rPosition = 0;
                Thread.Sleep(50);
            }
        }

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