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

using Microsoft.SPOT;

namespace HWModules
{
    public class NativeUART : SerialPort
    {
        private MessageQueue messageQ;
        private QRecord msgQEntry;
        private string qID;
        private StringBuilder portNameSB = new StringBuilder(8);
        
        private byte STX;
        private bool UseSTX;
        private bool handlerAttached;

        private byte ETX = (byte)'\n';

        public NativeUART(string id, QRecordType type, ProcessMessage processMessage, string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int readTimeout, int writeTimeout, bool useSTX, byte inSTX, byte newETX = (byte)'\n')
            : base(portName, baudRate, parity, dataBits, stopBits)
        {
            //Property Assignment
            ReadTimeout = readTimeout;
            WriteTimeout = writeTimeout;
            UseSTX = useSTX;
            STX = inSTX;
            ETX = newETX;
            qID = id;
            portNameSB.Append(portName);
            msgQEntry = new QRecord(MessageQueue.width, processMessage);
            msgQEntry.type = type;
            //Attach Handlers
            AttachDefaultDataHandler();
            ErrorReceived += errorReceived;

            //Grab Singleton
            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 = 2048;
        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>
        /// 
        private static Type thisType;

        public void dataReceivedNoSTX(object sender, SerialDataReceivedEventArgs e)     //TODO P2 need several unit tests
        {
            ReadPort();
      
            for (int i = 0; i < bytesToRead; i++)
            {
                messageBytes[messageIndex] = bytesRead[i];
                //sbTemp.Clear();
                //sbTemp.Append("RX: ");
                //sbTemp.Append(SafeEncoding.GetChars(messageBytes));
                //Debug.Print(sbTemp.ToString());
                if (messageBytes[messageIndex] == ETX)
                {

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

        /// <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, SerialDataReceivedEventArgs e)   //TODO P2 need a unit test
        {
            ReadPort();

            for (int i = 0; i < bytesToRead; i++)
            {
                messageBytes[messageIndex] = bytesRead[i];
                if (messageIndex == 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 = messageIndex + 1;
                }
                else
                {
                    //TODO P0 make sure this is really wrong 
                    //if (messageBytes[messageIndex - 1] == ETX)
                    if (messageBytes[messageIndex] == ETX)
                    {
                        EnqueueMessage();
                    }
                    else
                    {
                        messageIndex = messageIndex + 1;
                        //sbTemp.Clear();
                        //sbTemp.Append(SafeEncoding.GetChars(messageBytes));
                        //Debug.Print(sbTemp.ToString());
                    }
                }

                //TODO P1 need to check messageIndex against bufferLength in all drivers
                //Make sure messageIndex doesn't exceed bufferLength
                if (CheckForOverrun()) break;
            }
        }

        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 Error 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, 0, 128));
            EngrLogger.writeToColumns(sbTemp);

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

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