using ProjectBase.Data.Logs;
using System;
using System.Text;
/// <summary>
/// Inclusion of PEAK PCAN-Basic namespace
/// </summary>
using TPCANHandle = System.UInt16;
using TPCANBitrateFD = System.String;
using TPCANTimestampFD = System.UInt64;

namespace SIASUN.Autopilot.Device
{
    public class CanHelper : ICanHelper
    {

        #region Delegates
        /// <summary>
        /// Read-Delegate Handler
        /// </summary>
        //private delegate void ReadDelegateHandler();
        #endregion
        #region Members
        /// <summary>
        /// Saves the desired connection mode
        /// </summary>
        private bool m_IsFD = false;
        /// <summary>
        /// Saves the handle of a PCAN hardware
        /// </summary>
        private TPCANHandle m_PcanHandle;
        /// <summary>
        /// Saves the baudrate register for a conenction
        /// </summary>
        private TPCANBaudrate m_Baudrate;
        /// <summary>
        /// Saves the type of a non-plug-and-play hardware
        /// </summary>
        private TPCANType m_HwType;
        /// <summary>
        /// Stores the status of received messages for its display
        /// </summary>
        private System.Collections.ArrayList m_LastMsgsList;
        /// <summary>
        /// Read Delegate for calling the function "ReadMessages"
        /// </summary>
        //private ReadDelegateHandler m_ReadDelegate;
        /// <summary>
        /// Receive-Event
        /// </summary>
        private System.Threading.AutoResetEvent m_ReceiveEvent;
        /// <summary>
        /// Thread for message reading (using events)
        /// </summary>
        private System.Threading.Thread m_ReadThread;
        /// <summary>
        /// Handles of non plug and play PCAN-Hardware
        /// </summary>
        private TPCANHandle[] m_NonPnPHandles;

        /// <summary>
        /// MessageType is extend or standard
        /// </summary>
        private bool bExtend = false;

        private bool bFD = false;

        private bool bBRS = false;

        private bool bRemote = false;

        public CanHelper()
        {
            // Creates the list for received messages
            //
            //m_LastMsgsList = new System.Collections.ArrayList();
            // Creates the delegate used for message reading
            //
            //m_ReadDelegate = new ReadDelegateHandler(ReadMessages);
            // Creates the event used for signalize incomming messages 
            //
            m_ReceiveEvent = new System.Threading.AutoResetEvent(false);
        }


        #endregion
        /// <summary>
        /// 初始化can总线
        /// </summary>
        public void Initialize() 
        {            
            try
            { 
                string IOCan = System.Configuration.ConfigurationManager.AppSettings["IO_CAN"];
                string Interrupt = System.Configuration.ConfigurationManager.AppSettings["Interrupt_CAN"];
                setBaudrate();
                setCANHandle();
                setHardWareType();
                TPCANStatus stsResult;
                // Connects a selected PCAN-Basic channel
                stsResult = PCANBasic.Initialize(
                    m_PcanHandle,
                    m_Baudrate,
                    m_HwType,
                    Convert.ToUInt32(IOCan, 16),
                    Convert.ToUInt16(Interrupt));

                if (stsResult != TPCANStatus.PCAN_ERROR_OK)
                    LogHelper.log.Error(GetFormatedError(stsResult));
                else
                    // Prepares the PCAN-Basic's PCAN-Trace file
                    //
                    ConfigureTraceFile();

                // Sets the connection status of the main-form
                //
                SetConnectionStatus(stsResult == TPCANStatus.PCAN_ERROR_OK);
            }
            catch (Exception ex)
            {
                LogHelper.log.Error("can总线初始化失败。", ex);
            }
        }

        /// <summary>
        /// Activates/deaactivates the different controls of the main-form according
        /// with the current connection status
        /// </summary>
        /// <param name="bConnected">Current status. True if connected, false otherwise</param>
        private void SetConnectionStatus(bool bConnected)
        {
            if (!bConnected)
            {
                LogHelper.log.Debug("can总线未连接成功");
            }
            else
            {
                // Create and start the tread to read CAN Message using SetRcvEvent()
                //
                System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(this.CANReadThreadFunc);
                m_ReadThread = new System.Threading.Thread(threadDelegate);
                m_ReadThread.IsBackground = true;
                m_ReadThread.Start();
            }
        }

        /// <summary>
        /// Thread-Function used for reading PCAN-Basic messages
        /// </summary>
        private void CANReadThreadFunc()
        {
            UInt32 iBuffer;
            TPCANStatus stsResult;

            iBuffer = Convert.ToUInt32(m_ReceiveEvent.SafeWaitHandle.DangerousGetHandle().ToInt32());
            // Sets the handle of the Receive-Event.
            //
            stsResult = PCANBasic.SetValue(m_PcanHandle, TPCANParameter.PCAN_RECEIVE_EVENT, ref iBuffer, sizeof(UInt32));

            if (stsResult != TPCANStatus.PCAN_ERROR_OK)
            {
                LogHelper.log.Error(GetFormatedError(stsResult));
                return;
            }

            // While this mode is selected
            while (true)
            {
                // Waiting for Receive-Event
                // 
                if (m_ReceiveEvent.WaitOne(50))
                    ReadMessages();
            }
        }

        /// <summary>
        /// Function for reading PCAN-Basic messages
        /// </summary>
        private void ReadMessages()
        {
            TPCANStatus stsResult;

            // We read at least one time the queue looking for messages.
            // If a message is found, we look again trying to find more.
            // If the queue is empty or an error occurr, we get out from
            // the dowhile statement.	
            do
            {
                stsResult = m_IsFD ? ReadMessageFD() : ReadMessage();
                if (stsResult == TPCANStatus.PCAN_ERROR_ILLOPERATION)
                    break;
            } while (!Convert.ToBoolean(stsResult & TPCANStatus.PCAN_ERROR_QRCVEMPTY));
        }


        /// <summary>
        /// Function for reading messages on FD devices
        /// </summary>
        /// <returns>A TPCANStatus error code</returns>
        private TPCANStatus ReadMessageFD()
        {
            TPCANMsgFD CANMsg;
            TPCANTimestampFD CANTimeStamp;
            TPCANStatus stsResult;

            // We execute the "Read" function of the PCANBasic                
            //
            stsResult = PCANBasic.ReadFD(m_PcanHandle, out CANMsg, out CANTimeStamp);
            if (stsResult != TPCANStatus.PCAN_ERROR_QRCVEMPTY)
                // We process the received message
                //
                ProcessMessage(CANMsg, CANTimeStamp);

            return stsResult;
        }

        /// <summary>
        /// Function for reading CAN messages on normal CAN devices
        /// </summary>
        /// <returns>A TPCANStatus error code</returns>
        private TPCANStatus ReadMessage()
        {
            TPCANMsg CANMsg;
            TPCANTimestamp CANTimeStamp;
            TPCANStatus stsResult;

            // We execute the "Read" function of the PCANBasic                
            //
            stsResult = PCANBasic.Read(m_PcanHandle, out CANMsg, out CANTimeStamp);
            if (stsResult != TPCANStatus.PCAN_ERROR_QRCVEMPTY)
                // We process the received message
                //
                ProcessMessage(CANMsg, CANTimeStamp);

            return stsResult;
        }

        /// <summary>
        /// Processes a received message, in order to convert to bll 
        /// </summary>
        /// <param name="theMsg">The received PCAN-Basic message</param>
        /// <returns>True if the message must be created, false if it must be modified</returns>
        private void ProcessMessage(TPCANMsg theMsg, TPCANTimestamp itsTimeStamp)
        {
            TPCANMsgFD newMsg;
            TPCANTimestampFD newTimestamp;

            newMsg = new TPCANMsgFD();
            newMsg.DATA = new byte[64];
            newMsg.ID = theMsg.ID;
            newMsg.DLC = theMsg.LEN;
            for (int i = 0; i < ((theMsg.LEN > 8) ? 8 : theMsg.LEN); i++)
                newMsg.DATA[i] = theMsg.DATA[i];
            newMsg.MSGTYPE = theMsg.MSGTYPE;

            newTimestamp = Convert.ToUInt64(itsTimeStamp.micros + 1000 * itsTimeStamp.millis + 0x100000000 * 1000 * itsTimeStamp.millis_overflow);
            ProcessMessage(newMsg, newTimestamp);
        }

        /// <summary>
        /// Processes a received message, in order to show it in the Message-ListView
        /// </summary>
        /// <param name="theMsg">The received PCAN-Basic message</param>
        /// <returns>True if the message must be created, false if it must be modified</returns>
        private void ProcessMessage(TPCANMsgFD theMsg, TPCANTimestampFD itsTimeStamp) 
        {
            MessageStatus msgStsCurrentMsg;
            msgStsCurrentMsg = new MessageStatus(theMsg, itsTimeStamp);
            // 拆包
            Package_Data_Msg<object> rp = new Package_Data_Msg<object>();
            object up = rp.UnPackage(msgStsCurrentMsg.IdString, msgStsCurrentMsg.CANMsg.DATA);
            // 将事件加入委托链
            EventManage.Instance.RaiseEvent(msgStsCurrentMsg.IdString, rp.Message);
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void disConnect()
        {
            // Abort Read Thread if it exists
            //
            if (m_ReadThread != null)
            {
                m_ReadThread.Abort();
                m_ReadThread.Join();
                m_ReadThread = null;
            }
            // Releases the used PCAN-Basic channel
            //
            PCANBasic.Uninitialize(m_PcanHandle);
        }

        /// <summary>
        /// 设置波特率
        /// </summary>
        private void setBaudrate()
        {
            int baudRate = int.Parse(System.Configuration.ConfigurationManager.AppSettings["Baudrate_CAN"]);
            switch (baudRate)
            {
                case 0:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_1M;
                    break;
                case 1:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_800K;
                    break;
                case 2:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_500K;
                    break;
                case 3:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_250K;
                    break;
                case 4:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_125K;
                    break;
                case 5:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_100K;
                    break;
                case 6:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_95K;
                    break;
                case 7:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_83K;
                    break;
                case 8:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_50K;
                    break;
                case 9:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_47K;
                    break;
                case 10:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_33K;
                    break;
                case 11:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_20K;
                    break;
                case 12:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_10K;
                    break;
                case 13:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_5K;
                    break;
                default:
                    m_Baudrate = TPCANBaudrate.PCAN_BAUD_500K;
                    break;

            }
        }



        /// <summary>
        /// 设置CAN总线句柄
        /// </summary>
        private void setCANHandle() 
        {
            int handle = int.Parse(System.Configuration.ConfigurationManager.AppSettings["TPHandle_CAN"]);
            switch (handle)
            {
                case 0:
                    m_PcanHandle = PCANBasic.PCAN_NONEBUS;
                    break;
                case 1:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS1;
                    break;
                case 2:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS2;
                    break;
                case 3:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS3;
                    break;
                case 4:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS4;
                    break;
                case 5:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS5;
                    break;
                case 6:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS6;
                    break;
                case 7:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS7;
                    break;
                case 8:
                    m_PcanHandle = PCANBasic.PCAN_ISABUS8;
                    break;
                case 9:
                    m_PcanHandle = PCANBasic.PCAN_DNGBUS1;
                    break;
                case 10:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS1;
                    break;
                case 11:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS2;
                    break;
                case 12:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS3;
                    break;
                case 13:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS4;
                    break;
                case 14:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS5;
                    break;
                case 15:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS6;
                    break;
                case 16:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS7;
                    break;
                case 17:
                    m_PcanHandle = PCANBasic.PCAN_PCIBUS8;
                    break;
                case 18:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS1;
                    break;
                case 19:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS2;
                    break;
                case 20:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS3;
                    break;
                case 21:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS4;
                    break;
                case 22:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS5;
                    break;
                case 23:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS6;
                    break;
                case 24:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS7;
                    break;
                case 25:
                    m_PcanHandle = PCANBasic.PCAN_USBBUS8;
                    break;
                case 26:
                    m_PcanHandle = PCANBasic.PCAN_PCCBUS1;
                    break;
                case 27:
                    m_PcanHandle = PCANBasic.PCAN_PCCBUS2;
                    break;
                default:
                    m_PcanHandle = PCANBasic.PCAN_PCCBUS2;
                    break;
            }
        }


        private void setHardWareType() 
        {
            int hwType = int.Parse(System.Configuration.ConfigurationManager.AppSettings["HwType_CAN"]);
            switch (hwType)
            {
                case 0:
                    m_HwType = TPCANType.PCAN_TYPE_ISA;
                    break;
                case 1:
                    m_HwType = TPCANType.PCAN_TYPE_ISA_SJA;
                    break;
                case 2:
                    m_HwType = TPCANType.PCAN_TYPE_ISA_PHYTEC;
                    break;
                case 3:
                    m_HwType = TPCANType.PCAN_TYPE_DNG;
                    break;
                case 4:
                    m_HwType = TPCANType.PCAN_TYPE_DNG_EPP;
                    break;
                case 5:
                    m_HwType = TPCANType.PCAN_TYPE_DNG_SJA;
                    break;
                case 6:
                    m_HwType = TPCANType.PCAN_TYPE_DNG_SJA_EPP;
                    break;
                default:
                    m_HwType = TPCANType.PCAN_TYPE_DNG_SJA_EPP;
                    break;
            }
        }

 

        /// <summary>
        /// 写入数据包
        /// </summary>
        /// <param name="data"></param>
        public void Write(byte[] data, string id)  
        {
            TPCANStatus stsResult;

            // Send the message
            //
            stsResult = m_IsFD ? WriteFrameFD(data, id) : WriteFrame(data, id);

            // The message was successfully sent
            //
            if (stsResult == TPCANStatus.PCAN_ERROR_OK)
                LogHelper.log.Debug("Message was successfully SENT");
            // An error occurred.  We show the error.
            //			
            else
                LogHelper.log.Error(GetFormatedError(stsResult));
        }

        /// <summary>
        /// 写入数据帧
        /// </summary>
        /// <param name="data">标准数据</param>
        /// <param name="id">id</param>
        /// <returns></returns>
        private TPCANStatus WriteFrame(byte[] data, string id)
        {
            TPCANMsg CANMsg;

            // We create a TPCANMsg message structure 
            //
            CANMsg = new TPCANMsg();
            CANMsg.DATA = new byte[8];

            // We configurate the Message.  The ID,
            // Length of the Data, Message Type
            // and the data
            //
            CANMsg.ID = Convert.ToUInt32(id, 16);
            CANMsg.LEN = Convert.ToByte(data.Length);
            CANMsg.MSGTYPE = bExtend ? TPCANMessageType.PCAN_MESSAGE_EXTENDED : TPCANMessageType.PCAN_MESSAGE_STANDARD;
            // If a remote frame will be sent, the data bytes are not important.
            //
            if (bRemote)
                CANMsg.MSGTYPE |= TPCANMessageType.PCAN_MESSAGE_RTR;
            else
            {
                // We get so much data as the Len of the message
                //
                for (int i = 0; i < GetLengthFromDLC(CANMsg.LEN, true); i++)
                {
                    CANMsg.DATA[i] = data[i];
                }
            }

            // The message is sent to the configured hardware
            //
            return PCANBasic.Write(m_PcanHandle, ref CANMsg);
        }

        /// <summary>
        /// FD类型写入数据帧
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="id"></param>
        /// <returns></returns>
        private TPCANStatus WriteFrameFD(byte[] data, string id)
        {
            TPCANMsgFD CANMsg;
            int iLength;

            // We create a TPCANMsgFD message structure 
            //
            CANMsg = new TPCANMsgFD();
            CANMsg.DATA = new byte[64];

            // We configurate the Message.  The ID,
            // Length of the Data, Message Type 
            // and the data
            //
            CANMsg.ID = Convert.ToUInt32(id, 16);
            CANMsg.DLC = Convert.ToByte(data.Length);
            CANMsg.MSGTYPE = bExtend ? TPCANMessageType.PCAN_MESSAGE_EXTENDED : TPCANMessageType.PCAN_MESSAGE_STANDARD;
            CANMsg.MSGTYPE |= bFD ? TPCANMessageType.PCAN_MESSAGE_FD : TPCANMessageType.PCAN_MESSAGE_STANDARD;
            CANMsg.MSGTYPE |= bBRS ? TPCANMessageType.PCAN_MESSAGE_BRS : TPCANMessageType.PCAN_MESSAGE_STANDARD;

            // If a remote frame will be sent, the data bytes are not important.
            //
            if (bRemote)
                CANMsg.MSGTYPE |= TPCANMessageType.PCAN_MESSAGE_RTR;
            else
            {
                // We get so much data as the Len of the message
                //
                iLength = GetLengthFromDLC(CANMsg.DLC, (CANMsg.MSGTYPE & TPCANMessageType.PCAN_MESSAGE_FD) == 0);
                for (int i = 0; i < iLength; i++)
                {
                    CANMsg.DATA[i] = data[i];
                }
            }

            // The message is sent to the configured hardware
            //
            return PCANBasic.WriteFD(m_PcanHandle, ref CANMsg);
        }

        /// <summary>
        /// Configures the PCAN-Trace file for a PCAN-Basic Channel
        /// </summary>
        private void ConfigureTraceFile()
        {
            UInt32 iBuffer;
            TPCANStatus stsResult;

            // Configure the maximum size of a trace file to 5 megabytes
            //
            iBuffer = 5;
            stsResult = PCANBasic.SetValue(m_PcanHandle, TPCANParameter.PCAN_TRACE_SIZE, ref iBuffer, sizeof(UInt32));
            if (stsResult != TPCANStatus.PCAN_ERROR_OK)
                LogHelper.log.Error(GetFormatedError(stsResult));

            // Configure the way how trace files are created: 
            // * Standard name is used
            // * Existing file is ovewritten, 
            // * Only one file is created.
            // * Recording stopts when the file size reaches 5 megabytes.
            //
            iBuffer = PCANBasic.TRACE_FILE_SINGLE | PCANBasic.TRACE_FILE_OVERWRITE;
            stsResult = PCANBasic.SetValue(m_PcanHandle, TPCANParameter.PCAN_TRACE_CONFIGURE, ref iBuffer, sizeof(UInt32));
            if (stsResult != TPCANStatus.PCAN_ERROR_OK)
                LogHelper.log.Error(GetFormatedError(stsResult));
        }


        /// <summary>
        /// Help Function used to get an error as text
        /// </summary>
        /// <param name="error">Error code to be translated</param>
        /// <returns>A text with the translated error</returns>
        private string GetFormatedError(TPCANStatus error)
        {
            StringBuilder strTemp;

            // Creates a buffer big enough for a error-text
            //
            strTemp = new StringBuilder(256);
            // Gets the text using the GetErrorText API function
            // If the function success, the translated error is returned. If it fails,
            // a text describing the current error is returned.
            //
            if (PCANBasic.GetErrorText(error, 0, strTemp) != TPCANStatus.PCAN_ERROR_OK)
                return string.Format("An error occurred. Error-code's text ({0:X}) couldn't be retrieved", error);
            else
                return strTemp.ToString();
        }

        #region Help functions
        /// <summary>
        /// Convert a CAN DLC value into the actual data length of the CAN/CAN-FD frame.
        /// </summary>
        /// <param name="dlc">A value between 0 and 15 (CAN and FD DLC range)</param>
        /// <param name="isSTD">A value indicating if the msg is a standard CAN (FD Flag not checked)</param>
        /// <returns>The length represented by the DLC</returns>
        private int GetLengthFromDLC(int dlc, bool isSTD)
        {
            if (dlc <= 8)
                return dlc;

            if (isSTD)
                return 8;

            switch (dlc)
            {
                case 9: return 12;
                case 10: return 16;
                case 11: return 20;
                case 12: return 24;
                case 13: return 32;
                case 14: return 48;
                case 15: return 64;
                default: return dlc;
            }
        }
        #endregion help functions
    }
}
