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;

namespace LFP_Manager.Threads
{
    #region DELEGATE
    public delegate void UartDataUpdateRS485(object sender, ref DeviceSystemData[] aSystemData);
    public delegate void UartDataRecvRS485(byte[] data);
    public delegate void UartDataPrintRS485(object sender, string msg);
    #endregion

    class CsUartThreadDelta
    {
        #region VARIABLES

        private readonly CommConfig Config;
        private DeviceSystemData[] SystemData;
        private Thread serialComm = null;
        private SerialPort sPort = null;
        private 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 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 CsUartThreadDelta(int mID, CommConfig aConfig, ref DeviceSystemData[] aSystemData)
        {
            ModuleID = mID;
            Config = aConfig;
            SystemData = aSystemData;

            UartTxBuff = new TUartTxBuff();

            serialComm = new Thread(uartCommThread);
        }

        public void disposeThread()
        {
            if (sPort != null)
            {
                if (sPort.IsOpen)
                {
                    sPort.Close();
                }
                sPort = null;
            }
            SerialPortThreadEnd = true;
            if (serialComm != null)
            {
                if (serialComm.IsAlive)
                {
                    serialComm.Abort();
                }
                serialComm = null;
            }
        }

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

            ModuleID = mID;
            UartPolling = aPolling;

            if (Open(aConfig.UartPort, 9600))          //Byul Init 9600
            {
                SerialPortThreadEnd = false;
                serialComm.Start();
                result = true;
            }
            return result;
        }

        public void Stop()
        {
            SerialPortThreadEnd = true;
            Close();
        }

        #endregion

        #region COMMPORT CONTROLS

        private void sDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            byte[] sRead = new byte[1024];
            int rLen = 0;

            try
            {
                rLen = ((SerialPort)sender).Read(sRead, 0, 1024);

                for (int i = 0; i < rLen; i++)
                {
                    PutBuff(sRead[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)
            {
                if (sPort.IsOpen)
                {
                    sPort.Close();
                }
                sPort = null;
            }
        }

        #endregion

        #region PUBLIC FUNCTION

        public void SetPolling(bool flag, int mID, ref DeviceSystemData[] aSystemData)
        {
            ModuleID = mID;
            UartPolling = flag;
            SystemData = aSystemData;
            SystemData[0].mNo = ModuleID;
        }

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

        public void SetWriteCoilReg(ushort WriteAddr, short WriteData)
        {
            wFlag = 2;
            WriteRegAddr = WriteAddr;
            WriteCoilRegData = WriteData;

            for (int i = 0; i < 500; i += 10)
            {
                Thread.Sleep(10);
                Application.DoEvents();
            }
        }
        public void SetWriteReg(ushort WriteAddr, byte[] WriteData, bool ReplyFlag, int type)
        {
            WriteRegAddr = WriteAddr;

            TUartTRxData uartTRxData = new TUartTRxData();
            uartTRxData.type = type;
            //uartTRxData.data = csSerialCommFunction.MakeWriteRegisterData((ushort)SystemId, WriteRegAddr, WriteData);
            uartTRxData.data = WriteData;
            uartTRxData.length = uartTRxData.data.Length;

            UartTxBuff?.PutBuff(uartTRxData);
        }

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

            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 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)
        {
            rBuffer[rBufStart++] = c;
            rBufStart %= BUFFER_SIZE;

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

        private int GetBuff()
        {
            int result = 0;

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

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

        #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[] 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 = 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:
                            addr_ch = 5;
                            RequestRegAddr = 100;
                            RequestRegLen = 30;
                            sCmd = CsSerialCommFunctionDelta.READ_HOLDING_REG;     // Command 0x03
                            rFlag = true;
                            break;
                        case 5:
                            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[BUFFER_SIZE];
        ushort rPosition = 0;
        bool BuffStart = false;

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

        private void uartCommThread()
        {
            int RecvTimeout = Config.RecvWaitTime;
            int getData = 0;
            byte cData = 0;
            int[] TimeOutCount = new int[csConstData.SystemInfo.MAX_MODULE_SIZE];

        StartSend:
            while (SerialPortThreadEnd == false)
            {
                if ((sPort == null) || (sPort.IsOpen == false)) continue;

                FlushBuff();
                byte[] txData = MakeTxDataDelta(false);
                if (sPort == null) return;
                if (txData != null)
                {
                    sPort.Write(txData, 0, txData.Length);
                    OnPrint?.Invoke(this, csLog.trx_data_print(txData, txData.Length, 0));
                }

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

                    RecvPacketInit();

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

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

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

                            if (rPosition >= BUFFER_SIZE) RecvPacketInit();

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

                                switch (CsSerialCommFunctionDelta.ModbusPacketFromSlaveCheck(ReadBuf, rPosition))
                                {
                                    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 = CsSerialCommFunctionDelta.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)
                                        {
                                            byte[] adata = new byte[rPosition + 1];
                                            for (int j = 0; j < adata.Length; j++)
                                            {
                                                adata[j] = ReadBuf[j];
                                            }
                                            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;
                            //SystemData[0].CommFail = true;
                            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);
            }
        }

        #endregion
    }
}
