using System;
using System.IO.Ports;
using System.Text;
using Microsoft.SPOT;
using SWModules;

namespace HWModules
{
    public class ExarUart : I2CSerialPort
    {
        private MessageQueue messageQ;
        private QRecord msgQEntry;
        private StringBuilder portNameSB = new StringBuilder(5);
        private bool _handlerAttached;
        private byte STX;
        private string qID;
        private bool _useSTX;


        public ExarUart(string id, QRecordType type, ProcessMessage processMessage, string portName, int baudRate, Parity parity,
            int dataBits, StopBits stopBits, int readTimeout, int writeTimeout, bool useSTX, byte inSTX)
            : base(portName, baudRate, dataBits, parity, stopBits)
        {
            _storedTimeout = readTimeout;
            //WriteTimeout = writeTimeout;
            STX = inSTX;
            qID = id;
            _useSTX = useSTX;
            portNameSB.Append(portName);

            msgQEntry = new QRecord(MessageQueue.width, processMessage);
            msgQEntry.type = type;
            AttachDefaultDataHandler();
            ErrorReceived += errorReceived;

            messageQ = MessageQueue.Instance;
        }

        /// <summary>
        /// Attaches the default message handler for serial data into the message queue
        /// </summary>
        public void AttachDefaultDataHandler()
        {
            if (!_handlerAttached)
            {
                if (_useSTX) DataReceived += dataReceivedSTX;
                else DataReceived += dataReceivedNoSTX;
                _handlerAttached = true;
            }
        }

        /// <summary>
        /// Releases the native uart handler for users that need direct port access for non message
        /// queue interactions. 
        /// </summary>
        public void ReleaseDefaultDataHandler()
        {
            if (_handlerAttached)
            {
                if (_useSTX) DataReceived -= dataReceivedSTX;
                else DataReceived -= dataReceivedNoSTX;
                _handlerAttached = false;
            }
        }

               private const int bufferLength = 2056;
        private const byte ETX1 = 10;
        private const byte ETX2 = 13;
        private byte[] bytesRead = new byte[bufferLength];
        private byte[] messageBytes = new byte[bufferLength];
        private int bytesToRead;
        private int messageIndex = 0;
        private static readonly string bufferOverrunMsg = "[ERROR] BUFFER OVERRUN: ";

        /// <summary>
        /// Default handler function for data received on the serial port, puts data on the 
        /// MessageQueue. No terminator character is required for push into the Queue.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void dataReceivedNoSTX(object sender, EventArgs e)     //TODO P2 need several unit tests
        {
            ReadPort();
            for (int i = 0; i < bytesToRead; i++)
            {
                messageBytes[messageIndex] = bytesRead[i];
                if (messageBytes[messageIndex] == ETX1 || messageBytes[messageIndex] == ETX2)
                {
                    //Fix for empty message, don't enqueue!
                    if (messageIndex != 0)
                    {
                        sbTemp.Clear();
                        sbTemp.Append(SafeEncoding.GetChars(messageBytes));
                        EnqueueMessage();
                    }
                }
                else
                    messageIndex = messageIndex + 1;
              
                //TODO P1 need to check messageIndex against bufferLength in all drivers and need to unit test this block and the one in the STX event handler
                //Make sure messageIndex doesn't exceed bufferLength
                if (CheckForOverrun()) break;
            }

            if (BytesToRead != 0) dataReceivedNoSTX(this, EventArgs.Empty);

            if (messageIndex != 0) EnqueueMessage();
        }

        /// <summary>
        /// Default handler function for data received on the serial port, putting data on the
        /// MessageQueue. A terminator property must be set to use this function. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void dataReceivedSTX(object sender, EventArgs e)   //TODO P2 need a unit test
        {
            ReadPort();

            for (int i = 0; i < bytesToRead; i++)
            {
                messageBytes[messageIndex++] = bytesRead[i];
                if (messageIndex-1 == 0)      //Check to make sure first character is = STX
                {
                    if (messageBytes[0] != STX)  //only move on to byte index 1 after we get a STX
                        messageIndex = 0;
                }
                else
                {
                    
                    if (messageBytes[messageIndex - 1] == ETX1)
                    {
                        EnqueueMessage();
                    }
                }

                //TODO P1 need to check messageIndex against bufferLength in all drivers
                //Make sure messageIndex doesn't exceed bufferLength
                if (CheckForOverrun()) break;
            }
            if (BytesToRead != 0) dataReceivedSTX(this, EventArgs.Empty);
            if (messageIndex != 0) EnqueueMessage();
        }

        /// <summary>
        /// Override of the I2CSerialPort open function to automatically set the time that was 
        /// stored in the constructor.
        /// </summary>
        /// <returns>true on success</returns>
        public override bool Open()
        {
            bool retval = base.Open();
            TimeOut = _storedTimeout;
            //Set the timeout now that the bridge is open
            return retval;
        }

        private bool CheckForOverrun()
        {
            if (messageIndex >= bufferLength - 2)
            {
                LogBufferOverrun();
                return true;
            }
            return false;
        }

        private void ReadPort()
        {
            bytesToRead = BytesToRead;

            try
            {
                Read(bytesRead, 0, bytesToRead);
            }
            catch
            {
                LogReadError();
            }
        }

        private void EnqueueMessage()
        {
            msgQEntry.Update();
            msgQEntry.errorCode = 0;

            Array.Clear(msgQEntry.byteArray, 0, msgQEntry.byteArray.Length);
            msgQEntry.length = messageIndex + 1;
            Array.Copy(messageBytes, msgQEntry.byteArray, msgQEntry.length);
            messageQ.Enqueue(msgQEntry);

            messageIndex = 0;
            Array.Clear(messageBytes, 0, messageBytes.Length);
        }

        private void LogReadError()
        {
            sbTemp.Clear();
            sbTemp.Append("ERROR: Read at port: ");
            sbTemp.Append(portNameSB);
            sbTemp.Append(" " + qID);
            EngrLogger.writeToColumns(sbTemp.ToString()); //TODO P2 probably needs an error handler      
        }

        private void LogBufferOverrun()
        {
            msgQEntry.Update();
            msgQEntry.errorCode = -1;
            sbTemp.Clear();
            sbTemp.Append(bufferOverrunMsg);
            sbTemp.Append(" ");
            sbTemp.Append(portNameSB);
            sbTemp.Append(" ");
            sbTemp.Append(qID);
            sbTemp.Append(" ");
            sbTemp.Append(SafeEncoding.GetChars(messageBytes));
            EngrLogger.writeToColumns(sbTemp);

            messageIndex = 0;
            Array.Clear(messageBytes, 0, messageBytes.Length);
        }

        StringBuilder sbTemp = new StringBuilder(64);
        private int _storedTimeout;

        public void errorReceived(object sender, EventArgs e)
        { 
            sbTemp.Clear();
            sbTemp.Append("Native UART Error Received, at port: ");
            sbTemp.Append(portNameSB);
            EngrLogger.writeToColumns(sbTemp);   //TODO P2 Probably needs an error handler
        }
    }
}