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

namespace SensorModules
{
    // This is a Singleton instantiation based on
    // C# in Depth, Third Edition Jon Skeet
    // http://csharpindepth.com/Articles/General/Singleton.aspx
    // "a singleton is a class which only allows a single instance of itself to be created, and usually gives simple access to that instance"

    public sealed class Optode1 : Aandera4330Optode
    {
        private Optode1()
            : base("OPTODE1", "COM6", 9600, Parity.None, 8, StopBits.One, 5000, 20, false, 10)
        { }

        private static Optode1 instance;
        public static Optode1 Instance
        {
            get { return instance ?? (instance = new Optode1()); }
        }

    }

    public class Aandera4330Optode
    {
        HWModules.MotherBoard motherboard = MotherBoard.Instance;

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

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

        private NativeUART IOPort;

        public Aandera4330Optode (string id, string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int readTimeout, int writeTimeout, bool useSTX, byte STX)
        {
            IOPort = new NativeUART(id, QRecordType.Optode, processMessage, portName, baudRate, parity, dataBits, stopBits, readTimeout, writeTimeout, useSTX, STX);
        }

        private static byte[] setPasskeyLow = UTF8Encoding.UTF8.GetBytes("Set Passkey(1)\r\n");
        private static byte[] setPasskeyHigh = UTF8Encoding.UTF8.GetBytes("Set Passkey(1000)\r\n");
        private static byte[] disableText = UTF8Encoding.UTF8.GetBytes("Set Enable Text(no)\r\n");
        private static byte[] enableText = UTF8Encoding.UTF8.GetBytes("Set Enable Text(yes)\r\n");
        private static byte[] disableAirSat = UTF8Encoding.UTF8.GetBytes("Set Enable AirSaturation(no)\r\n");

        private static byte[] disableRawdata = UTF8Encoding.UTF8.GetBytes("Set Enable Rawdata(no)\r\n");
        private static byte[] disableCommTimeout = UTF8Encoding.UTF8.GetBytes("Set Comm TimeOut(Always On)\r\n");
        private static byte[] disableSleep = UTF8Encoding.UTF8.GetBytes("Set Enable Sleep(no)\r\n");
        private static byte[] save = UTF8Encoding.UTF8.GetBytes("Save\r\n");

        public void init()
        {
            if (!IOPort.IsOpen) IOPort.Open();

            IOPort.DiscardInBuffer();
            IOPort.DiscardOutBuffer();

            setProperties();

            getConfig();

            doSample();
        }

        private void getConfig()
        {
            MessageQueue msgQ = MessageQueue.Instance;

            //send a crlf
            IOPort.WriteByte(10);
            IOPort.WriteByte(13);
            EngrLogger.Comment("Optode: Set passkey high");
            IOPort.Write(setPasskeyHigh, 0, setPasskeyHigh.Length);
            Thread.Sleep(250);
            msgQ.processQ();

            getAll();
            Thread.Sleep(250);
            msgQ.processQ();
        }

        private void setProperties()
        {
            MessageQueue msgQ = MessageQueue.Instance;

            //send a crlf
            IOPort.WriteByte(10);
            IOPort.WriteByte(13);
            EngrLogger.Comment("Optode: Set passkey high");
            IOPort.Write(setPasskeyHigh, 0, setPasskeyHigh.Length);
            Thread.Sleep(250);
            msgQ.processQ();

            EngrLogger.Comment("Optode: Disable text");
            IOPort.Write(disableText, 0, disableText.Length);
            Thread.Sleep(250);
            msgQ.processQ();

            EngrLogger.Comment("Optode: Disable air sat");
            IOPort.Write(disableAirSat, 0, disableAirSat.Length);
            Thread.Sleep(250);
            msgQ.processQ();

            EngrLogger.Comment("Optode: Disable comm timeout");
            IOPort.Write(disableCommTimeout, 0, disableCommTimeout.Length);
            Thread.Sleep(250);
            msgQ.processQ();

            EngrLogger.Comment("Optode: Disable sleep");
            IOPort.Write(disableSleep, 0, disableSleep.Length);
            Thread.Sleep(250);
            msgQ.processQ();
        }

        public void powerUp()
        {
            EngrLogger.writeToColumns("Powering up 4330 Optode");

            motherboard.EnableChannelPower(MotherBoard.ChannelNames.OptodeChannel);
            Thread.Sleep(3000);

            init();
        }

        public void powerDown()
        {
            EngrLogger.writeToColumns("Powering down 4330 Optode");
            motherboard.DisableChannelPower(MotherBoard.ChannelNames.OptodeChannel);
        }


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

        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 = SV.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 void wakeUp()
        {
            //Debug.Print("Sending Wake Up");
            EngrLogger.writeToColumns("Sending Optode Wakeup");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(wakeUpCmd.ToString()), txBytes, wakeUpCmd.Length);
            IOPort.Write(txBytes, 0, wakeUpCmd.Length);
        }

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

            //wakeUp();
            ////TODO P0 SHORTEN/// REMOVE ///
            //Thread.Sleep(500);
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(doSampleCmd.ToString()), txBytes, doSampleCmd.Length);
            IOPort.Write(txBytes, 0, doSampleCmd.Length);
        }

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

        private const string getConfigXMLCmd = "get ConfigXML\r\n";
        public void getConfigXML()
        {
            //Debug.Print("Sending Get ConfigXML");
            EngrLogger.writeToColumns("Sending Optode Get ConfigXML");
            //wakeUp();
            //Thread.Sleep(500);
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(getConfigXMLCmd), txBytes, getConfigXMLCmd.Length);
            IOPort.Write(txBytes, 0, getConfigXMLCmd.Length);
        }

        private const string getDataXMLCmd = "get DataXML\r\n";
        public void getDataXML()
        {
            //Debug.Print("Sending Get DataXML");
            EngrLogger.writeToColumns("Sending Optode Get DataXML");
            //wakeUp();
            //Thread.Sleep(500);
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(Encoding.UTF8.GetBytes(getDataXMLCmd), txBytes, getDataXMLCmd.Length);
            IOPort.Write(txBytes, 0, getDataXMLCmd.Length);
        }

  

        private static int msgLength = 0;
        private static readonly StringBuilder OptodeHeader = new StringBuilder("Optode Message");

        private static readonly byte[] dataHeader = UTF8Encoding.UTF8.GetBytes("4330");

        public void processMessage(QRecord qRecord)
        {
            for (int i = 0; i < qRecord.byteArray.Length; i++)
            {
                if (qRecord.byteArray[i] == 9)
                    qRecord.byteArray[i] = 44;
            }

            if(SV.Pressure == double.NaN)
                EngrLogger.writeToColumns(EngrLiterals.ColumnNums.dateTime, qRecord.timeStamp,
                                                EngrLiterals.ColumnNums.state, qRecord.state,
                                                EngrLiterals.ColumnNums.comment, OptodeHeader,
                                                EngrLiterals.ColumnNums.TOpt, qRecord.byteArray);
            else
                EngrLogger.writeToColumns(EngrLiterals.ColumnNums.dateTime, qRecord.timeStamp,
                                                EngrLiterals.ColumnNums.state, qRecord.state,
                                                EngrLiterals.ColumnNums.comment, OptodeHeader,
                                                EngrLiterals.ColumnNums.pressure, SV.Pressure,
                                                EngrLiterals.ColumnNums.TOpt, qRecord.byteArray);

            //check for expectedResponse anywhere in the SBE41 response message
            msgLength = qRecord.byteArray.Length;

            if (Aandera4330Optode.waitingForResponse)
            {
                if (msgLength > Aandera4330Optode.expectedResponseLength)
                {
                    for (int j = 0; j < msgLength - Aandera4330Optode.expectedResponseLength; j++)
                    {
                        for (int k = 0; k < Aandera4330Optode.expectedResponseLength; k++)
                        {
                            if (qRecord.byteArray[j + k] != Aandera4330Optode.expectedResponse[k])
                            {
                                Aandera4330Optode.gotResponse = false;
                                break;
                            }
                        }
                    }
                    Aandera4330Optode.gotResponse = true;
                }
            }
            if (SciLogger.sampling(SciLogger.Instruments.Optode) && !SciLogger.posted(SciLogger.Instruments.Optode))
            {
                if ( (GHI.Utilities.Arrays.Contains(qRecord.byteArray, 0, 8, dataHeader, 0, dataHeader.Length)) > -1)
                {
                    SciLogger.postSample(SciLogger.Instruments.Optode, qRecord.byteArray);
                    sbTemp.Clear();
                    sbTemp.Append("Optode post: ");
                    sbTemp.Append(SafeEncoding.GetChars(qRecord.byteArray));
                    EngrLogger.writeToColumns(sbTemp);
                }
            }
        }

    }
}
