using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

using System.Threading;
using System.Windows.Forms;

using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using static DevExpress.Utils.Drawing.Helpers.NativeMethods;

namespace LFP_Manager.Threads
{
    class csValueCanThread
    {
        #region DLL FUNCTION

        #endregion

        #region VARIABLES

        int TxThreadPeriod = 10;
        int RxThreadPeriod = 1;

        public delegate void invokeDelegate();

        CommConfig Config;
        DeviceSystemData SystemData;

        int SystemId = 0;
        int DCP = 0;

        Thread canCommTx = null;
        Thread canCommRx = null;
        //string Msg = "";

        // ValueCAN Variables
        IntPtr m_hObject;                   // handle for device
        NeoDevice ndNeoToOpen;
        byte[] bNetwork = new byte[255];    // List of hardware IDs

        int iOpenDeviceType;                // Storage for the device type that is open

        ////////////////////////////////////////
        bool can_bOpen = false;

        bool CanTxThreadEnd = true;
        bool CanRxThreadEnd = true;

        bool CanPolling = false;
        bool AutoCanTx = true;

        // Message Queue
        icsSpyMessage[] stMessages = new icsSpyMessage[20000];  // TempSpace for messages

        const int RX_QUEUE_MAX = 512;
        icsSpyMessage[] RxMsgQueue = new icsSpyMessage[RX_QUEUE_MAX];    // RX Message Queue
        int RxQueueFront = 0;
        int RxQueueEnd = 0;

        void RxQueuePut(ref icsSpyMessage rxMsg)
        {
            RxMsgQueue[RxQueueFront++] = rxMsg;
            RxQueueFront %= RX_QUEUE_MAX;
        }

        bool RxQueueGet(ref icsSpyMessage rxMsg)
        {
            if (RxQueueFront != RxQueueEnd)
            {
                rxMsg = RxMsgQueue[RxQueueEnd++];
                RxQueueEnd %= RX_QUEUE_MAX;

                return true;
            }
            return false;
        }

        public event CanDataUpdate OnUpdate = null;
        public event CanDataPrint OnPrint = null;
        public event CanDataRecv OnDataRecv = null;

        private object lockObject = new object();
        #endregion

        #region CONSTRUCTORS
        public csValueCanThread(int sId, CommConfig aConfig, ref DeviceSystemData aSystemData)
        {
            SystemId = sId;
            Config = aConfig;
            SystemData = aSystemData;

            canCommTx = new Thread(canTxThread);
            canCommRx = new Thread(canRxThread);
        }

        public void disposeThread()
        {
            CanTxThreadEnd = true;
            if (canCommTx != null)
            {
                if (canCommTx.IsAlive)
                {
                    canCommTx.Abort();
                }
                canCommTx = null;
            }
            CanRxThreadEnd = true;
            if (canCommRx != null)
            {
                if (canCommRx.IsAlive)
                {
                    canCommRx.Abort();
                }
                canCommRx = null;
            }

            //close the port
            int iNumberOfErrors = 0;
            int iResult = icsNeoDll.icsneoClosePort(m_hObject, ref iNumberOfErrors);
            if (iResult == 1)
            {
                //MessageBox.Show("Port Closed OK!");
            }
            else
            {
                throw new Exception(String.Format("ValueCAN - Problem ClosingPort"));
            }

            //Clear device type and open flag
            iOpenDeviceType = 0;
            can_bOpen = false;
        }

        public int CloseValueCAN()
        {
            int iNumberOfErrors = 0;
            int iResult = icsNeoDll.icsneoClosePort(m_hObject, ref iNumberOfErrors);
            if (iResult == 1)
            {
                //MessageBox.Show("Port Closed OK!");
            }
            else
            {
                throw new Exception(String.Format("ValueCAN - Problem ClosingPort"));
            }

            return iResult;
        }

        public int ReOpenValueCAN()
        {
            int iNumberOfErrors = 0;
            int iResult = icsNeoDll.icsneoClosePort(m_hObject, ref iNumberOfErrors);
            if (iResult != 1) throw new Exception(String.Format("ValueCAN - Problem ClosingPort"));

            iResult = icsNeoDll.icsneoOpenNeoDevice(ref ndNeoToOpen, ref m_hObject, ref bNetwork[0], 1, 0);
            if (iResult != 1) throw new Exception(String.Format("ValueCAN - Problem Opening Port"));

            return iResult;
        }

        public int OpenValueCan()
        {
            int iResult;
            NeoDeviceEx[] ndNeoToOpenex = new NeoDeviceEx[16];	//Struct holding detected hardware information
            int iNumberOfDevices;   //Number of hardware devices to look for 

            OptionsNeoEx neoDeviceOption = new OptionsNeoEx();

            //Set the number of devices to find, for this example look for 16.  This example will only work with the first.
            iNumberOfDevices = 15;

            iResult = icsNeoDll.icsneoFindDevices(ref ndNeoToOpenex[0], ref iNumberOfDevices, 0, 0, ref neoDeviceOption, 0);
            if (iResult == 0)
            {
                throw new Exception(String.Format("ValueCAN - Problem finding devices"));
            }

            if (iNumberOfDevices < 1)
            {
                throw new Exception(String.Format("ValueCAN - No devices found"));
            }

            ndNeoToOpen = ndNeoToOpenex[0].neoDevice;
            //Open the first found device
            iResult = icsNeoDll.icsneoOpenNeoDevice(ref ndNeoToOpen, ref m_hObject, ref bNetwork[0], 1, 0);
            if (iResult == 1)
            {
                //MessageBox.Show("Port Opened OK!");
            }
            else
            {
                throw new Exception(String.Format("ValueCAN - Problem Opening Port"));
            }
            //Set the device type for later use
            iOpenDeviceType = ndNeoToOpen.DeviceType;

            return 1;
        }

        private void ConfigBitRate(CommConfig aConfig)
        {
            //Get the network name index to set the baud rate of 
            int iNetworkID = (int)eNETWORK_ID.NETID_HSCAN;
            int iBitRateToUse = csCanConstData.BaudRate.BaudRateInts[aConfig.CanBaudrate];

            //Set the bit rate
            int iResult = icsNeoDll.icsneoSetBitRate(m_hObject, iBitRateToUse, iNetworkID);
            if (iResult != 1)
            {
                throw new Exception(String.Format("ValueCAN - Problem setting bit rate"));
            }
            else
            {
                //MessageBox.Show("Bit Rate Set");
            }
        }

        private void ConfigFDRate(CommConfig aConfig)
        {
            //Get the network name index to set the baud rate of 
            int iNetworkID = (int)eNETWORK_ID.NETID_HSCAN;
            int iBitRateToUse = 10000000;

            //Set the bit rate
            int iResult = icsNeoDll.icsneoSetFDBitRate(m_hObject, iBitRateToUse, iNetworkID);
            if (iResult != 1)
            {
                throw new Exception(String.Format("ValueCAN - Problem setting FD bit rate"));
            }
            else
            {
                //MessageBox.Show("FD Bit Rate Set");
            }
        }

        private void GetSetting(CommConfig aConfig)
        {
            SVCAN3Settings VcanReadSettings = new SVCAN3Settings();
            SFireSettings FireReadSettings = new SFireSettings();
            SFire2Settings Fire2ReadSettings = new SFire2Settings();
            SRADGalaxySettings RadGalaxyReadSettings = new SRADGalaxySettings();
            SVCANRFSettings VcanRFReadSettings = new SVCANRFSettings();
            SVCAN412Settings Vcan412ReadSettings = new SVCAN412Settings();
            SRADPlutoSettingsPack RADPlutoSettings = new SRADPlutoSettingsPack();

            CAN_SETTINGS HSCanSettings = new CAN_SETTINGS();
            int iNumberOfBytes = 0;
            int iResult;

            //Get the settigns of the connected hardware
            switch (iOpenDeviceType)
            {
                case (int)eHardwareTypes.NEODEVICE_FIRE:          //FIRE
                                                                  //Get the settings
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(FireReadSettings);
                    iResult = icsNeoDll.icsneoGetFireSettings(m_hObject, ref FireReadSettings, iNumberOfBytes);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading FIRE configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = FireReadSettings.can1;
                    break;

                case (int)eHardwareTypes.NEODEVICE_VCAN3:            //Vcan3
                                                                     //Get the setting
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(VcanReadSettings);
                    iResult = icsNeoDll.icsneoGetVCAN3Settings(m_hObject, ref VcanReadSettings, iNumberOfBytes);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading VCAN3 configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = VcanReadSettings.Can1;
                    break;

                case (int)eHardwareTypes.NEODEVICE_FIRE2:            //FIRE2
                                                                     //Get the setting
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(Fire2ReadSettings);
                    iResult = icsNeoDll.icsneoGetFire2Settings(m_hObject, ref Fire2ReadSettings, iNumberOfBytes);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading FIRE2 configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = Fire2ReadSettings.can1;
                    break;

                case (int)eHardwareTypes.NEODEVICE_RADGALAXY:            //RadGalaxy
                                                                         //Get the setting
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(RadGalaxyReadSettings);
                    iResult = icsNeoDll.icsneoGetRADGalaxySettings(m_hObject, ref RadGalaxyReadSettings, iNumberOfBytes);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading RADGalaxy configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = RadGalaxyReadSettings.Can1;
                    break;

                case (int)eHardwareTypes.NEODEVICE_VCANRF:           //VcanRF
                                                                     //Get the setting
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(VcanRFReadSettings);
                    iResult = icsNeoDll.icsneoGetVCANRFSettings(m_hObject, ref VcanRFReadSettings, iNumberOfBytes);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading VCANRF configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = VcanRFReadSettings.can1;
                    break;

                case (int)eHardwareTypes.NEODEVICE_VCAN42:           //Vcan4-2
                case (int)eHardwareTypes.NEODEVICE_VCAN41:           //Vcan4-1  (Same steps for both)
                                                                     //Get the setting
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(Vcan412ReadSettings);
                    iResult = icsNeoDll.icsneoGetVCAN412Settings(m_hObject, ref Vcan412ReadSettings, iNumberOfBytes);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading VCAN412 configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = Vcan412ReadSettings.can1;
                    break;
                case (int)eHardwareTypes.NEODEVICE_RADPLUTO:             //RAD Pluto
                                                                         //Get the setting
                    RADPlutoSettings.uiDevice = (UInt32)EDeviceSettingsType.DeviceRADPlutoSettingsType;
                    iNumberOfBytes = System.Runtime.InteropServices.Marshal.SizeOf(RADPlutoSettings);
                    iResult = icsNeoDll.icsneoGetDeviceSettings(m_hObject, ref RADPlutoSettings, iNumberOfBytes, 0);
                    if (iResult == 0)
                    {
                        MessageBox.Show("Problem reading RAD Pluto configuration");
                        return;
                    }
                    //Copy the HS CAN settings from the structure to sub struct
                    HSCanSettings = RADPlutoSettings.PlutoSettings.can1;
                    break;
                default:
                    //Connected hardware does not support this command
                    MessageBox.Show("Problem reading configuration, unsupported device");
                    return;
            }

            //fill text boxes with data from sub struct
            //txt3GPSeg1.Text = Convert.ToString(HSCanSettings.TqSeg1);
            //txt3GPSeg2.Text = Convert.ToString(HSCanSettings.TqSeg2);
            //txt3GPropDelay.Text = Convert.ToString(HSCanSettings.TqProp);
            //txt3GSJumpW.Text = Convert.ToString(HSCanSettings.TqSync);
            //txt3GBRP.Text = Convert.ToString(HSCanSettings.BRP);

        }

        public unsafe bool Start(CommConfig aConfig, int sId, bool aPolling)
        {
            bool result = false;

            SystemId = sId;
            CanPolling = aPolling;

            try
            {
                if (OpenValueCan() == 0)
                    throw new System.InvalidOperationException("Failed to open the device, please check if the device type and device index number are correct.");

                // Baudrate Config
                ConfigBitRate(aConfig);

                // FD Rate Config
                ConfigFDRate(aConfig);

                // Get Setting Value
                GetSetting(aConfig);

                can_bOpen = true;

                CanTxThreadEnd = false;
                CanRxThreadEnd = false;

                canCommTx.Start();
                canCommRx.Start();

                result = true;
            }
            catch (Exception ex)
            {
                System.Exception Ex = new System.Exception(ex.Message, ex);
                throw Ex;
            }
            return result;
        }
        #endregion

        #region TX FUNCTION

        private int GetErrorValueCAN()
        {
            int result = 0;
            int[] iErrors = new int[600];  //Array for Error Numbers
            int iNumberOfErrors = 0;  // Storage for number of errors

            // Read Out the errors
            int iResult = icsNeoDll.icsneoGetErrorMessages(m_hObject, ref iErrors[0], ref iNumberOfErrors);

            // Test the returned result
            if (iResult == 0)
            {
                MessageBox.Show("Problem Reading Errors");
            }
            else
            {
                if (iNumberOfErrors != 0)
                {
                    ReOpenValueCAN();
                    for (int iCount = 0; iCount < iNumberOfErrors; iCount++)
                    {
                        if (iErrors[iCount] == 44) result++;
                    }
                }
            }
            return result;
        }
        private void SendPacketByValueCAN(UInt32 ExID, byte[] Data)
        {
            Monitor.Enter(lockObject);
            try
            {
                //OnPrint?.Invoke(this, csCanCommFunction.PacketToMsg(sendobj, 1));

                long lResult;
                icsSpyMessage stMessagesTx = new icsSpyMessage();
                long lNetworkID;
                string sTempString;

                // Read the Network we will transmit on (indicated by lstNetwork ListBox)
                sTempString = "HSCAN";
                lNetworkID = icsNeoDll.GetNetworkIDfromString(ref sTempString);

                // Is this a CAN network or a J1850/ISO one?
                // load the message structure

                //Make id Extended
                stMessagesTx.StatusBitField = Convert.ToInt16(eDATA_STATUS_BITFIELD_1.SPY_STATUS_XTD_FRAME);

                stMessagesTx.ArbIDOrHeader = (int)ExID;                                // The ArbID
                stMessagesTx.NumberBytesData = (byte)Data.Length;                        // The number of Data Bytes
                if (stMessagesTx.NumberBytesData > 8) stMessagesTx.NumberBytesData = 8;     // You can only have 8 databytes with CAN
                                                                                            // Load all of the data bytes in the structure

                stMessagesTx.Data1 = Data[0];
                stMessagesTx.Data2 = Data[1];
                stMessagesTx.Data3 = Data[2];
                stMessagesTx.Data4 = Data[3];
                stMessagesTx.Data5 = Data[4];
                stMessagesTx.Data6 = Data[5];
                stMessagesTx.Data7 = Data[6];
                stMessagesTx.Data8 = Data[7];

                // Transmit the assembled message
                for (int i = 0; i < 5; i++)
                {
                    lResult = icsNeoDll.icsneoTxMessages(m_hObject, ref stMessagesTx, Convert.ToInt32(lNetworkID), 1);
                    if (lResult == 1)
                        break;
                    else
                    {
                        if (GetErrorValueCAN() == 0) break;
                        //ReOpenValueCAN();
                    }
                }
                //GetErrorValueCAN();
            }
            catch (Exception)
            {
                //throw new Exception(String.Format("CAN Transmit Exception", ex.Message));
            }
            finally
            {
                Monitor.Exit(lockObject);
            }

        }

        private unsafe void SendPacketByValueCAN(ref VCI_CAN_OBJ senObj)
        {
            long lResult;
            icsSpyMessage stMessagesTx = new icsSpyMessage();
            long lNetworkID;
            string sTempString;

            // Read the Network we will transmit on (indicated by lstNetwork ListBox)
            sTempString = "HSCAN";
            lNetworkID = icsNeoDll.GetNetworkIDfromString(ref sTempString);

            // Is this a CAN network or a J1850/ISO one?
            // load the message structure

            //Make id Extended
            stMessagesTx.StatusBitField = Convert.ToInt16(eDATA_STATUS_BITFIELD_1.SPY_STATUS_XTD_FRAME);

            stMessagesTx.ArbIDOrHeader = (int)senObj.ID;                                // The ArbID
            stMessagesTx.NumberBytesData = (byte)senObj.DataLen;                        // The number of Data Bytes
            if (stMessagesTx.NumberBytesData > 8) stMessagesTx.NumberBytesData = 8;     // You can only have 8 databytes with CAN
                                                                                        // Load all of the data bytes in the structure

            stMessagesTx.Data1 = senObj.Data[0];
            stMessagesTx.Data2 = senObj.Data[1];
            stMessagesTx.Data3 = senObj.Data[2];
            stMessagesTx.Data4 = senObj.Data[3];
            stMessagesTx.Data5 = senObj.Data[4];
            stMessagesTx.Data6 = senObj.Data[5];
            stMessagesTx.Data7 = senObj.Data[6];
            stMessagesTx.Data8 = senObj.Data[7];

            // Transmit the assembled message
            lResult = icsNeoDll.icsneoTxMessages(m_hObject, ref stMessagesTx, Convert.ToInt32(lNetworkID), 1);
            // Test the returned result
            if (lResult != 1)
            {
                MessageBox.Show(String.Format("Problem Transmitting Message - ({0})", lResult));
            }
        }

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

        unsafe public void SendDataFromApp(UInt32 header, byte[] data)
        {
            if (can_bOpen == false) return;

            VCI_CAN_OBJ sendobj = new VCI_CAN_OBJ();

            sendobj.SendType = csCanConstData.SendType.Normal;
            sendobj.ExternFlag = csCanConstData.FrameType.Extended;
            sendobj.RemoteFlag = csCanConstData.FrameFormat.Data_Frame;
            sendobj.ID = header;  // 0x1815c9c8

            int len = 8;
            sendobj.DataLen = System.Convert.ToByte(len);
            for (int i = 0; i < len; i++)
                sendobj.Data[i] = data[i];

            SendPacketByValueCAN(sendobj.ID, data);

            OnPrint?.Invoke(this, csCanCommFunction.PacketToMsg(sendobj, 1));
        }

        public void SetPolling(bool flag, int sId, ref DeviceSystemData aSystemData)
        {
            SystemId = sId;
            CanPolling = flag;
            SystemData = aSystemData;
            SystemData.mNo = SystemId;
        }

        public void SetDCP(int aDCP)
        {
            DCP = aDCP;
        }

        public void SendProcessFromApp(int sId, int mode, int flag, int dcp, ref DeviceParamData aParam, ref DeviceCalibration aCalib)
        {
            switch (mode)
            {
                case 5:         // Cell Volatge Parameter
                    SendData(sId, 5, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 23, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 7:
                    SendData(sId, 7, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 23, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 6:
                    SendData(sId, 6, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 24, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 22:
                    SendData(sId, 22, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 24, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 14:
                    SendData(sId, 14, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 99:            // All param read
                    SendData(sId, 5, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 6, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 7, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 14, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 22, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 23, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 24, flag, dcp, ref aParam, ref aCalib);
                    SendData(sId, 2, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 9:             // Cell Voltage Calibration
                    SendData(sId, 9, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 1:             // Cell Voltage Calibration
                    SendData(sId, 1, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 12:             // Cell Voltage Calibration
                    SendData(sId, 12, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 8:              // Battery Parameter Setting
                    SendData(sId, 8, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 17:             // Cell Balancing Parameter - 240531
                    SendData(sId, 17, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 21:             // System Information (Device Address)
                    SendData(sId, 21, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 13:             // Soc Calibration
                    SendData(sId, 13, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 15:             // Default Parameter
                    SendData(sId, 15, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 16:             // Default Parameter
                    if (aParam.DefalutParamAll == 1)
                        SendData(0xFF, 16, flag, dcp, ref aParam, ref aCalib);
                    else
                        SendData(sId, 16, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 2:              // Cell Voltage Difference Parameter
                    SendData(sId, 2, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 25:
                    SendData(sId, 25, flag, dcp, ref aParam, ref aCalib);
                    break;

                // Inventory Data Control
                case 900:       // Manufacturer Cmd
                    SendData(sId, 900, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 901:       // Serial Number #1 Cmd
                    SendData(sId, 901, flag, dcp, ref aParam, ref aCalib);
                    break;
                case 902:       // Serial Number #2 Cmd
                    SendData(sId, 902, flag, dcp, ref aParam, ref aCalib);
                    break;
            }
        }

        unsafe void SendData(int sId, int mode, int flag, int dcp, ref DeviceParamData aParam, ref DeviceCalibration aCalib)
        {
            if (can_bOpen == false) return;

            VCI_CAN_OBJ sendobj = MakeTxData(sId, mode, flag, dcp, ref aParam, ref aCalib);

            byte[] data = new byte[8];
            for (int i = 0; i < 8; i++) data[i] = sendobj.Data[i];

            SendPacketByValueCAN(sendobj.ID, data);
        }

        private UInt32 MakeID(int sId, int mode)
        {
            UInt32 result = 0;
            PACKET_HEADER hdr = new PACKET_HEADER();

            hdr.Index = 6;                                  // Fixed 6
            hdr.R = 0;                                      // Fixed 0
            hdr.DP = 0;                                     // Fixed 0
            hdr.PF = (byte)mode;                            // Command
            if (sId == 0xFF) hdr.PS = (byte)(sId);          // PDU SPECIFIC
            else hdr.PS = (byte)(sId + 200);    // PDU SPECIFIC
            hdr.SA = 200;                                   // SOURCE ADDRESS

            result = csCanCommFunction.CovertHtoP(hdr);
            return result;
        }

        private UInt32 MakeInvID(int sId, int mode)
        {
            UInt32 result = 0;
            PACKET_HEADER hdr = new PACKET_HEADER();

            hdr.Index = 1;                                  // Fixed 1 for Inventory Data
            hdr.R = 0;                                      // Fixed 0
            hdr.DP = 0;                                     // Fixed 0
            hdr.PF = (byte)((mode % 900) + 1);              // Command
            if (sId == 0xFF) hdr.PS = (byte)(sId);          // PDU SPECIFIC
            else hdr.PS = (byte)(sId + 200);                // PDU SPECIFIC
            hdr.SA = 200;                                   // SOURCE ADDRESS

            result = csCanCommFunction.CovertHtoP(hdr);
            return result;
        }

        unsafe private VCI_CAN_OBJ MakeTxData(int sId, int mode, int flag, int dcp, ref DeviceParamData aParam, ref DeviceCalibration aCalib)
        {
            VCI_CAN_OBJ sendobj = new VCI_CAN_OBJ();

            sendobj.SendType = csCanConstData.SendType.Normal;
            sendobj.RemoteFlag = csCanConstData.FrameFormat.Data_Frame;
            sendobj.ExternFlag = csCanConstData.FrameType.Extended;
            if (mode < 900)
                sendobj.ID = MakeID(sId, mode);
            else
                sendobj.ID = MakeInvID(sId, mode);
            sendobj.DataLen = (byte)(8);

            byte[] aData = csCanCommFunction.MakeCanData(mode, flag, dcp, ref aParam, ref aCalib);
            for (int i = 0; i < 8; i++)
                { sendobj.Data[i] = aData[i]; }

            return sendobj;
        }

        #endregion

        #region COMM TX THREAD

        int current_cmd = 0;
        int RxSystemId = 0;

        public int GetCommSystemId()
        {
            return RxSystemId;
        }

        unsafe private void canTxThread()
        {
            int txTick = 0;

            while (CanTxThreadEnd == false)
            {
                txTick++;
                if (txTick >= TxThreadPeriod)
                {
                    if (CanPolling && AutoCanTx)
                    {
                        int flag = 0;

                        TxProcess(ref flag);
                        RxSystemId = SystemId;
                        SendData(SystemId, current_cmd, flag, DCP, ref SystemData.ParamData, ref SystemData.CalibriationData);
                        //Thread.Sleep(1);
                    }
                    txTick %= TxThreadPeriod;
                }
                GetMessageBuff();

                Thread.Sleep(1);
            }
        }

        void TxProcess(ref int aflag)
        {
            switch (current_cmd)
            {
                case 20: current_cmd = 900; break;
                case 900: current_cmd = 901; break;
                case 901: current_cmd = 902; break;
                case 902: current_cmd = 1; break;
                case 1: current_cmd = 19; break;
                case 19: current_cmd = 3; break;
                case 3: current_cmd = 4; break;
                case 4: current_cmd = 11; break;
                case 11: current_cmd = 16; break;
                case 16: current_cmd = 20; break;
                default: current_cmd = 20; break;
            }
        }
        #endregion

        #region COMM RX THREAD
        unsafe private void canRxThread()
        {
            while (CanRxThreadEnd == false)
            {
                DateTime aTime = DateTime.Now;

                if (GetMessageProcess())
                {
                    // Received data
                    SystemData.ShelfCommFail = false;
                    SystemData.LastRxTime = DateTime.Now;
                }
                else
                {
                    TimeSpan dTime = DateTime.Now - SystemData.LastRxTime;
                    if (SystemData.ShelfCommFail == false)
                    {
                        if (dTime.TotalSeconds > 10)
                        {
                            SystemData.ShelfCommFail = true;
                            DataInit();
                            OnUpdate?.Invoke(this, ref SystemData);
                        }
                    }
                }
                Thread.Sleep(RxThreadPeriod);
            }
        }

        unsafe private int GetMessageBuff()
        {
            int result = 0;
            int lNumberOfMessages = 1;
            int lNumberOfErrors = 0;
            //double dTime = 0;

            // read the messages from the driver
            int lResult = icsNeoDll.icsneoGetMessages(m_hObject, ref stMessages[0], ref lNumberOfMessages, ref lNumberOfErrors);

            // was the read successful?
            if (lResult == 1)
            {
                for (int i = 0; i < lNumberOfMessages; i++)
                {
                    // Calculate the messages timestamp
                    //lResult = icsNeoDll.icsneoGetTimeStampForMsg(m_hObject, ref stMessages[i], ref dTime);
                    if ((stMessages[i].StatusBitField & Convert.ToInt32(eDATA_STATUS_BITFIELD_1.SPY_STATUS_TX_MSG)) == 0)
                    {
                        // Rx Data
                        if (stMessages[i].ArbIDOrHeader != 0)
                            RxQueuePut(ref stMessages[i]);
                    }
                }
            }
            return result;
        }

        private bool GetMessageProcess()
        {
            bool result = false;
            icsSpyMessage rxMsg = new icsSpyMessage();

            if (RxQueueGet(ref rxMsg))
            {
                switch (rxMsg.Protocol)
                {
                    case (int)ePROTOCOL.SPY_PROTOCOL_CAN:
                        {
                            UInt32 exID = (UInt32)rxMsg.ArbIDOrHeader;
                            byte[] rData = new byte[8];

                            rData[0] = rxMsg.Data1;
                            rData[1] = rxMsg.Data2;
                            rData[2] = rxMsg.Data3;
                            rData[3] = rxMsg.Data4;
                            rData[4] = rxMsg.Data5;
                            rData[5] = rxMsg.Data6;
                            rData[6] = rxMsg.Data7;
                            rData[7] = rxMsg.Data8;

                            OnPrint?.Invoke(this, csCanCommFunction.PacketToMsg(exID, rData, 0));

                            PACKET_HEADER pHeader = csCanCommFunction.CovertPtoH(exID);
                            switch (pHeader.Index)
                            {
                                case 0x01:                  // Inventory Data Packet
                                    if ((pHeader.SA == (200 + SystemId)))
                                    {
                                        result = true;

                                        RxSystemId = pHeader.SA - 200;
                                        csCanCommFunction.CanInvRxProcess(RxSystemId, exID, rData, ref SystemData, DateTime.Now);
                                        OnUpdate?.Invoke(this, ref SystemData);
                                    }
                                    break;
                                case 0x04:                  // Firmware Update Packet
                                    OnDataRecv?.Invoke(exID, rData);
                                    break;
                                case 0x06:                  // Normal Packet
                                    if (pHeader.SA == (200 + SystemId))
                                    {
                                        result = true;

                                        RxSystemId = pHeader.SA - 200;
                                        csCanCommFunction.CanRxProcess(ref Config, RxSystemId, exID, rData, ref SystemData, DateTime.Now);
                                        OnUpdate?.Invoke(this, ref SystemData);
                                    }
                                    break;
                            }
                        }
                        break;
                }
            }
            return result;
        }

        #endregion

        #region DATA INIT

        private void DataInit()
        {
            SystemData.ShelfCommFail = true;

            for (int j = 0; j < csConstData.SystemInfo.MAX_MODULE_CELL_SIZE; j++)
            {
                SystemData.ValueData.CellVoltage[j] = 0;
            }
            for (int j = 0; j < csConstData.SystemInfo.MAX_MODULE_TEMP_SIZE; j++)
            {
                SystemData.ValueData.CellTemperature[j] = 0;
            }
            SystemData.ValueData.voltageOfPack = 0;
            SystemData.ValueData.current = 0;
            SystemData.ValueData.rSOC = 0;
            SystemData.AvgData.maxCellVoltage = 0;
            SystemData.AvgData.minCellVoltage = 0;
            SystemData.AvgData.avgCellVoltage = 0;
            SystemData.AvgData.maxTemp = 0;
            SystemData.AvgData.minTemp = 0;
            SystemData.AvgData.avgTemp = 0;
            SystemData.heatbeat = 0;
            SystemData.StatusData.warning = 0;
            SystemData.StatusData.protect = 0;

            for (int j = 0; j < SystemData.Information.pcb_sn.Length; j++)
            {
                SystemData.Information.pcb_sn[j] = 0;
            }
            for (int j = 0; j < SystemData.Information.module_sn.Length; j++)
            {
                SystemData.Information.module_sn[j] = 0;
            }

            SystemData.CalibriationData.Battery.Capacity = 0;
            SystemData.CalibriationData.Current.ChaAndDchSelect = 0;
        }

        #endregion
    }
}
