using System;
using Microsoft.SPOT;

using System.Threading;
using System.IO.Ports;
using System.Text;

using Microsoft.SPOT.Hardware;

namespace CPF
{
    public static class Optode
    {
        public static SerialPort optodePort = new SerialPort("COM6", 9600, Parity.None, 8, StopBits.One);

        private static byte[] txBytes = new byte[64];

        private static int messageIndex = 0;
        private static int numPortBytes = 0;

        private static StructQueue.qStruct qS = new StructQueue.qStruct();

        //MEASUREMENT	4330	1532	
        //   O2Concentration[uM]	2.644894E+02	AirSaturation[%]	9.722411E+01	
        //    Temperature[Deg.C]	2.217678E+01	CalPhase[Deg]	    2.919595E+01	
        //    TCPhase[Deg]	    2.919595E+01    C1RPh[Deg]	        3.770208E+01	
        //    C2RPh[Deg]	        8.506126E+00	C1Amp[mV]	        8.645895E+02	
        //    C2Amp[mV]	        9.500998E+02	RawTemp[mV]	        1.244538E+02

        public enum OptodeSampleFields : int
        {
            O2Conc = 0,
            AirSat,
            TOpt,
            CalPhase,
            TCPhase,
            C1RPh,
            C2RPh,
            C1Amp,
            C2Amp,
            RawTemp
        }

        public static void init()
        {
            qS.byteArray = new byte[StructQueue.byteArrayWidth];

            optodePort.ReadTimeout = 5000;
            optodePort.Open();

            optodePort.DiscardInBuffer();
            optodePort.DiscardOutBuffer();
            optodePort.DataReceived += new SerialDataReceivedEventHandler(optodeDataReceived);

            wakeUp();
            Thread.Sleep(2000);
        }

        public enum commands : int
        {
            none = 0,
            getAll
        }

        public static bool waitingForResponse = false;
        public static bool gotResponse = false;
        private static readonly StringBuilder getAllResponse = new StringBuilder("#\r");
        public static byte[] expectedResponse = new byte[64];
        public static int expectedResponseLength = 0;
        private static int numRetries = 0;
        private static readonly int maxNumRetries = 3;

        private static TimeSpan wfrStartTime = new TimeSpan();
        private static readonly TimeSpan wfrTimeout = new TimeSpan(0, 0, 30);

        private static readonly string wfrFail = "Optode waitingForResponse expended all retries";

        private static StringBuilder sbTemp = new StringBuilder(128);
        public static int waitForResponse(commands commandNum)
        {
            if (waitingForResponse)
            //Do this after the first call to this method
            {
                if (gotResponse)
                //success
                {
                    waitingForResponse = false;
                    return (1);
                }
                else if ((Microsoft.SPOT.Hardware.Utility.GetMachineTime() - wfrStartTime) > wfrTimeout)
                //if timeout has expired, try again up to the max number of retries
                {
                    if (numRetries > maxNumRetries)
                    {
                        Debug.Print(wfrFail);

                        Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(wfrFail), qS.byteArray, wfrFail.Length);
                        qS.timeStamp = DateTime.Now;
                        qS.state = Program.CurrentState;
                        qS.qRecordType = StructQueue.QRecordType.Optode;
                        Program.sQueue.Enqueue(qS); 

                        return (-1);
                    }
                    else
                    {
                        waitingForResponse = false;
                        sbTemp.Clear();
                        sbTemp.Append("Optode waitingForResponse try number ");
                        sbTemp.Append(numRetries);
                        Debug.Print(sbTemp.ToString());
                        return (0);
                    }
                }
                else
                    return (0);
            }
            else
            //Do this the first time the method is called and after a timeout
            {
                switch (commandNum)
                {
                    case commands.getAll:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(getAllResponse.ToString()), expectedResponse, getAllResponse.Length);
                        expectedResponseLength = getAllResponse.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending getAll command");
                        getAll();
                        break;
                }
                wfrStartTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime();
                numRetries = numRetries + 1;
                gotResponse = false;
                waitingForResponse = true;
                return (0);
            }
        }

        private static StringBuilder wakeUpCmd = new StringBuilder("\r");
        public static void wakeUp()
        {
            //Debug.Print("Sending Wake Up");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(wakeUpCmd.ToString()), txBytes, wakeUpCmd.Length);
            optodePort.Write(txBytes, 0, wakeUpCmd.Length);
        }

        private const string doSampleCmd = "Do Sample\r\n";
        private const string doSampleHeader = "Sending Optode Do Sample";
        public static void doSample()
        {
            //Debug.Print("Sending Optode wakeup and Do Sample");
            Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(doSampleHeader), qS.byteArray, doSampleHeader.Length);
            qS.state = Program.CurrentState;
            qS.qRecordType = StructQueue.QRecordType.Optode;
            qS.timeStamp = DateTime.Now;
            Program.sQueue.Enqueue(qS); 

            wakeUp();
            Thread.Sleep(500);
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(doSampleCmd.ToString()), txBytes, doSampleCmd.Length);
            optodePort.Write(txBytes, 0, doSampleCmd.Length);
        }

        private const string getAllCmd = "get all\r\n";
        public static void getAll()
        {
            //Debug.Print("Sending Get All");
			wakeUp();
			Thread.Sleep(500);
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(getAllCmd), txBytes, getAllCmd.Length);
            optodePort.Write(txBytes, 0, getAllCmd.Length);
        }

        private const int ehBufferSize = 2048;
        private static byte[] portBytes = new byte[ehBufferSize];
        private static byte[] messageBytes = new byte[ehBufferSize];
        private static byte[] errorHeader = new byte[5] { (byte)69, (byte)114, (byte)114, (byte)111, (byte)114 };
        private static byte LF = 10;            //End of text = LF

        private static void optodeDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            numPortBytes = optodePort.BytesToRead;

            if ((numPortBytes + messageIndex + 50) < ehBufferSize)
            {
                try
                {
                    optodePort.Read(portBytes, 0, numPortBytes);
                }
                catch
                {
                    Debug.Print("Optode Port Read Exception");          //Probably should do something smarter here        
                }

                for (int i = 0; i < numPortBytes; i++)
                {
                    if (portBytes[i] != LF)
                    {
                        messageBytes[messageIndex] = portBytes[i];
                        messageIndex = messageIndex + 1;
                    }
                    else
                    {
                        Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
                        Array.Copy(messageBytes, qS.byteArray, (messageIndex - 1));
                        qS.timeStamp = DateTime.Now;
                        qS.state = Program.CurrentState;
                        qS.qRecordType = StructQueue.QRecordType.Optode;
                        Program.sQueue.Enqueue(qS);

                        messageIndex = 0;
                    }
                }
            }
            else
            {
                ErrorHandler.incrOptodeEHErrors();

                Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
                Array.Copy(errorHeader, qS.byteArray, errorHeader.Length);
                Array.Copy(messageBytes, qS.byteArray, (messageIndex - 1));
                qS.timeStamp = DateTime.Now;
                qS.state = Program.CurrentState;
                qS.qRecordType = StructQueue.QRecordType.Optode;
                Program.sQueue.Enqueue(qS);
            }
        }
    }
}
