
using System;
using Microsoft.SPOT;

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

using GHI.Utilities;

namespace CPF
{
    public static class IModem
    {
        public enum ReturnCodes : int
        {
            none = 0,
            CSQ,
            SBDWT,
            SBDIX,
        }
        public static int returnCode;

        public static SerialPort iModemPort = new SerialPort("COM2", 19200, Parity.None, 8, StopBits.One);

        public static void init()
        {
            Array.Clear(messageBytes, 0, messageBytes.Length);
            Array.Clear(bytesRead, 0, bytesRead.Length);

            qS.byteArray = new byte[StructQueue.byteArrayWidth];

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

            iModemPort.DiscardInBuffer();
            iModemPort.DiscardOutBuffer();
            iModemPort.DataReceived += new SerialDataReceivedEventHandler(iModemDataReceived);

            sendD0();
            Thread.Sleep(1000);
            sendK0();
            Thread.Sleep(1000);
            sendSBDD2();
            Thread.Sleep(1000);
            sendGSN();
            Thread.Sleep(1000);
            sendSBDC();
            Thread.Sleep(1000);
            sendNoEcho();
        }

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

        //Reset the MOMSN (mobile origniated message sequence number
        private const string a3laSBDC = "AT+SBDC\r";
        public static int sendSBDC()
        {
            //Debug.Print(DateTime.Now + " Sending SBDC");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(a3laSBDC), txBytes, a3laSBDC.Length);
            iModemPort.Write(txBytes, 0, a3laSBDC.Length);

            return (1);
        }

        //Get the IMEI number
        private const string a3laGSN = "AT+GSN\r";
        public static int sendGSN()
        {
            //Debug.Print(DateTime.Now + " Sending GSN");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(a3laGSN), txBytes, a3laGSN.Length);
            iModemPort.Write(txBytes, 0, a3laGSN.Length);

            return (1);
        }

        private const string a3laCSQ = "AT+CSQ\r";
        public static int sendCSQ()
        {
            //Debug.Print(DateTime.Now + " Sending CSQ");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(a3laCSQ), txBytes, a3laCSQ.Length);
            iModemPort.Write(txBytes, 0, a3laCSQ.Length);

            return (1);
        }

        private const string a3laD0 = "AT&D0\r";
        public static void sendD0()
        {
            //Debug.Print(DateTime.Now + " Sending D0");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(a3laD0), txBytes, a3laD0.Length);
            iModemPort.Write(txBytes, 0, a3laD0.Length);
        }

        private const string noEcho = "En=0\r";
        public static void sendNoEcho()
        {
            //Debug.Print(DateTime.Now + " Sending No Echo");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(noEcho), txBytes, noEcho.Length);
            iModemPort.Write(txBytes, 0, noEcho.Length);
        }

        private const string a3laK0 = "AT&K0\r";
        public static void sendK0()
        {
            //Debug.Print(DateTime.Now + " Sending K0");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(a3laK0), txBytes, a3laK0.Length);
            iModemPort.Write(txBytes, 0, a3laK0.Length);
        }

        private static readonly StringBuilder sbTemp = new StringBuilder(1024);
        //public static readonly StringBuilder sbdwtMsg = new StringBuilder(1024);

        private static StringBuilder sbdwtMsg = new StringBuilder(1024);
        public static StringBuilder SBDWTMsg
        {
            get { return sbdwtMsg; }
            set { sbdwtMsg = value; }
        }

        public static int loadSBDWT()
        {
            iModemPort.Write(UTF8Encoding.UTF8.GetBytes(sbdwtMsg.ToString()), 0, sbdwtMsg.Length);

            sbTemp.Clear();
            sbTemp.Append("Loaded SBDWT Buffer");
            EngrLogger.writeToColumns(sbTemp);

            return (1);
        }

        private static readonly StringBuilder sbSBDWT = new StringBuilder("AT+SBDWT\r");
        public static void sendSBDWT()
        {
            //Debug.Print(DateTime.Now + " Sending SBDWT");
            iModemPort.Write(UTF8Encoding.UTF8.GetBytes(sbSBDWT.ToString()), 0, sbSBDWT.Length);

            sbTemp.Clear();
            sbTemp.Append("Sent SBDWT Command");
            EngrLogger.writeToColumns(sbTemp);
        }

        private static readonly StringBuilder sbSBDRT = new StringBuilder("AT+SBDRT\r");
        public static void sendSBDRT()
        {
            //Debug.Print(DateTime.Now + " Sending SBDRT");
            iModemPort.Write(UTF8Encoding.UTF8.GetBytes(sbSBDRT.ToString()), 0, sbSBDRT.Length);

            sbTemp.Clear();
            sbTemp.Append("Sent SBDRT Command");
            EngrLogger.writeToColumns(sbTemp);
        }

        private static readonly StringBuilder sbSBDD2 = new StringBuilder("AT+SBDD2\r");
        public static int sendSBDD2()
        {
            //Debug.Print(DateTime.Now + " Sending SBDD2");
            iModemPort.Write(UTF8Encoding.UTF8.GetBytes(sbSBDD2.ToString()), 0, sbSBDD2.Length);

            return (1);
        }

        private static readonly StringBuilder sbSBDI = new StringBuilder("AT+SBDI\r");
        public static int sendSBDI()
        {
            //Debug.Print(DateTime.Now + " Sending SBDI");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbSBDI.ToString()), txBytes, sbSBDI.Length);
            iModemPort.Write(txBytes, 0, sbSBDI.Length);

            return (1);
        }

        private static readonly StringBuilder sbSBDIX = new StringBuilder("AT+SBDIX\r");
        public static void sendSBDIX()
        {
            //Debug.Print(DateTime.Now + " Sending SBDIX");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbSBDIX.ToString()), txBytes, sbSBDIX.Length);
            iModemPort.Write(txBytes, 0, sbSBDIX.Length);
        }

        private static readonly StringBuilder sbCREG = new StringBuilder("AT+CREG?\r");
        public static void sendCREG()
        {
            //Debug.Print(DateTime.Now + " Sending CREG?");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbCREG.ToString()), txBytes, sbCREG.Length);
            iModemPort.Write(txBytes, 0, sbCREG.Length);
        }

        private static readonly StringBuilder sbPAPUBX00 = new StringBuilder("AT+PA=A\r");
        public static void sendPAPUBX00()
        {
            //Debug.Print(DateTime.Now + " Sending PA=A");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbPAPUBX00.ToString()), txBytes, sbPAPUBX00.Length);
            iModemPort.Write(txBytes, 0, sbPAPUBX00.Length);
        }

        private static readonly StringBuilder sbPAPUBX04 = new StringBuilder("AT+PA=C\r");
        public static void sendPAPUBX04()
        {
            //Debug.Print(DateTime.Now + " Sending PA=C");
            Array.Clear(txBytes, 0, txBytes.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbPAPUBX04.ToString()), txBytes, sbPAPUBX04.Length);
            iModemPort.Write(txBytes, 0, sbPAPUBX04.Length);
        }

        public static void parseSQ(byte[] inBytes)
        {
            int colonIndex = -1;
            //if ((inBytes[0] == 43) && (inBytes[1] == 67) && (inBytes[2] == 83) && (inBytes[3] == 81))
            //    SignalQuality = (int)inBytes[5] - 48; 
            
            colonIndex = Array.IndexOf(inBytes, (byte)':');
            if (colonIndex >= 0)
                SignalQuality = (int)inBytes[colonIndex + 1] - 48;
            else
                SignalQuality = -1;
        }

        private static byte LF = 10;
        private static byte[] bytesRead = new byte[2048];
        public static byte[] messageBytes = new byte[2048];
        private static int bytesToRead;
        private static int messageIndex = 0;
        private static StructQueue.qStruct qS = new StructQueue.qStruct();

        //Singnal quality property moved from Program class to IModem class
        //in  CPF_43 this var was set to -1 on state and substate entry, and exit, but was only every recorded
        //in EngrLog - never used to evaluate or determine action.
        private static int signalQuality = -1;
        public static int SignalQuality
        {
            get { return signalQuality; }
            set { signalQuality = value; }
        }

        private static void iModemDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            bytesToRead = iModemPort.BytesToRead;

            try
            {
                iModemPort.Read(bytesRead, 0, bytesToRead);
            }
            catch
            {
                Debug.Print("Iridium Modem Read Exception");          //TODO do something smarter here
            }

            for (int i = 0; i < bytesToRead; i++)
            {
                messageBytes[messageIndex] = bytesRead[i];
                if (messageBytes[messageIndex] == LF)
                {
                    qS.timeStamp = DateTime.Now;
                    qS.state = Program.CurrentState;

                    sbTemp.Clear();
                    sbTemp.Append(UTF8Encoding.UTF8.GetChars(messageBytes, 0, messageIndex - 1));
                    //Debug.Print(DateTime.Now + " iModem Message = " + sbTemp.ToString());

                    Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
                    Array.Copy(messageBytes, qS.byteArray, messageIndex + 1);
                    qS.qRecordType = StructQueue.QRecordType.IModem;
                    Program.sQueue.Enqueue(qS);

                    messageIndex = 0;
                    Array.Clear(messageBytes, 0, messageBytes.Length);
                    returnCode = 0;
                }
                else
                {
                    messageIndex = messageIndex + 1;
                }
            }
        }

        public enum commands : int
        {
            none = 0,
            d0,
            k0,
            sbdd2,
            creg,
            csq,
            sendSBDWT,
            loadSBDWT,
            sbdi,
            sbdix,
            papubx00,
            papubx04
        }

        public static bool waitingForResponse = false;
        public static bool gotResponse = false;
        private static readonly StringBuilder d0Response = new StringBuilder("AT&D0");
        private static readonly StringBuilder k0Response = new StringBuilder("AT&K0");
        private static readonly StringBuilder sbdd2Response = new StringBuilder("AT+SBDD2");
        private static readonly StringBuilder cregResponse = new StringBuilder("+CREG:000,001");
        private static readonly StringBuilder csqResponse = new StringBuilder("+CSQ:");
        private static readonly StringBuilder sendSBDWTResponse = new StringBuilder("READY");
        private static readonly StringBuilder loadSBDWTResponse = new StringBuilder("OK");
        private static readonly StringBuilder sbdiResponse = new StringBuilder("+SBDI:");
        private static readonly StringBuilder sbdixResponse0 = new StringBuilder("+SBDIX: 0,");
        private static readonly StringBuilder sbdixResponse1 = new StringBuilder("+SBDIX: 1,");
        private static readonly StringBuilder sbdixResponse2 = new StringBuilder("+SBDIX: 2,");
        private static readonly StringBuilder sbdixResponse3 = new StringBuilder("+SBDIX: 3,");
        private static readonly StringBuilder sbdixResponse4 = new StringBuilder("+SBDIX: 4,");
        private static readonly StringBuilder[] sbdixResponses = new StringBuilder[]{ sbdixResponse0, sbdixResponse1, sbdixResponse2, sbdixResponse3, sbdixResponse4, };
        private static readonly StringBuilder papubx00Response = new StringBuilder("$PUBX,00");
        private static readonly StringBuilder papubx04Response = new StringBuilder("$PUBX,04");
        public static int sbdixMTQueued = -1;
        public static int sbdixMTStatus = -1;
        public static int sbdixMOStatus = -1;

        private static readonly StringBuilder expectedResponseSB = new StringBuilder(64);
        private static byte[] expectedResponse = new byte[64];

        private static int expectedResponseLength = 0;
        private static int numRetries = 0;
        private static readonly int maxNumRetries = 3;

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

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

        private static commands currentCommand = commands.none;

        public static int waitForResponse(commands commandNum)
        {
            if (waitingForResponse)
            //Do this after the first call to this method
            {
                if (gotResponse)
                //success
                {
                    waitingForResponse = false;
                    numRetries = 0;
                    currentCommand = commands.none;
                    //Debug.Print("Got IModem Response");
                    return (1);
                }
                else if ((DateTime.Now - wfrStartTime) > wfrTimeout)
                //if timeout has expired, try again up to the max number of retries
                {
                    if (numRetries > maxNumRetries)
                    {
                        waitingForResponse = false;
                        numRetries = 0;
                        gotResponse = false;
                        currentCommand = commands.none;
       
                        EngrLogger.writeToColumns(wfrFail);
   
                        return (-1);
                    }
                    else
                    {
                        waitingForResponse = false;
                        sbTemp.Clear();
                        sbTemp.Append("IModem waitingForResponse try number ");
                        sbTemp.Append(numRetries);

                        return (-2);
                    }
                }
                else
                    return (0);
            }
            else
            //Do this the first time the method is called and after a timeout
            {
                currentCommand = commandNum;
                Array.Clear(expectedResponse, 0, expectedResponse.Length);
                switch (commandNum)
                {
                    case commands.d0:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(d0Response.ToString()), expectedResponse, d0Response.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(d0Response);
                        expectedResponseLength = d0Response.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending ds command");
                        sendD0();
                        break;
                    case commands.k0:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(k0Response.ToString()), expectedResponse, k0Response.Length);
                         expectedResponseSB.Clear();
                        expectedResponseSB.Append(k0Response);
                       expectedResponseLength = k0Response.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending ds command");
                        sendK0();
                        break;
                    case commands.sbdd2:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(sbdd2Response.ToString()), expectedResponse, sbdd2Response.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(sbdd2Response);
                        expectedResponseLength = sbdd2Response.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 CR/LF");
                        sendSBDD2();
                        break;
                    case commands.creg:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(cregResponse.ToString()), expectedResponse, cregResponse.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(cregResponse);
                        expectedResponseLength = cregResponse.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 creg");
                        sendCREG();
                        break;
                    case commands.csq:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(csqResponse.ToString()), expectedResponse, csqResponse.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(csqResponse);
                        expectedResponseLength = csqResponse.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 csq");
                        sendCSQ();
                        break;
                    case commands.sendSBDWT:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(sendSBDWTResponse.ToString()), expectedResponse, sendSBDWTResponse.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(sendSBDWTResponse);
                        expectedResponseLength = sendSBDWTResponse.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 sbdwt command");

                        sendSBDWT();
                        break;
                    case commands.loadSBDWT:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(loadSBDWTResponse.ToString()), expectedResponse, loadSBDWTResponse.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(loadSBDWTResponse);
                        expectedResponseLength = loadSBDWTResponse.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 sbdwt command");

                        loadSBDWT();
                        break;
                    case commands.sbdi:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(sbdiResponse.ToString()), expectedResponse, sbdiResponse.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(sbdiResponse);
                        expectedResponseLength = sbdiResponse.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 csq");    
                        sendSBDI();
                        break;
                    case commands.sbdix:
                         //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 csq");
                        sbdixMTQueued = -1;
                        sendSBDIX();
                        break;
                    case commands.papubx00:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(papubx00Response.ToString()), expectedResponse, papubx00Response.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(papubx00Response);
                        expectedResponseLength = papubx00Response.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 csq");
                        sendPAPUBX00();
                        break;
                    case commands.papubx04:
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(papubx04Response.ToString()), expectedResponse, papubx04Response.Length);
                        expectedResponseSB.Clear();
                        expectedResponseSB.Append(papubx04Response);
                        expectedResponseLength = papubx04Response.Length;
                        //Debug.Print(DateTime.Now.TimeOfDay.ToString() + " Sending SBE41 csq");
                        sendPAPUBX04();
                        break;
                }
                wfrStartTime = DateTime.Now;
                numRetries = numRetries + 1;
                gotResponse = false;
                waitingForResponse = true;
                return (0);
            }
        }

        private static StringBuilder sbResponse = new StringBuilder(256);
        private static StringBuilder sbExpectedResponse = new StringBuilder(256);
        private static byte[] returnField = new byte[16];
        private static byte[] returnField2 = new byte[16];
        private const byte commaByte = (byte)',';
        private const byte colonByte = (byte)':';

        public static void checkResponse(byte[] response)
        {
            int msgLength = response.Length;

            //check for expectedResponse anywhere in the IModem response message
            if (IModem.waitingForResponse)
            {
                if (msgLength > expectedResponseLength)
                {
                    //start at the beginning of the qStructure byte array and test for the right character sequence
                    //then start at the 2nd character in the qStrucutre byte array and so on
                    if (currentCommand != commands.sbdix)
                    {
                        sbExpectedResponse.Clear();
                        sbExpectedResponse.Append("Looking for IModem-->");
                        sbExpectedResponse.Append(UTF8Encoding.UTF8.GetChars(expectedResponse));
                        sbExpectedResponse.AppendLine("<--");
                        //Debug.Print(sbExpectedResponse.ToString());

                        sbResponse.Clear();
                        sbResponse.Append("Got-->");
                        sbResponse.Append(UTF8Encoding.UTF8.GetChars(response));
                        sbResponse.AppendLine("<--");
                        //Debug.Print(sbResponse.ToString());

                        if (GHI.Utilities.Arrays.Contains(response, 0, expectedResponse, 0, expectedResponseLength) >= 0)
                        {
                            gotResponse = true;
                            //Debug.Print("Correct response received");
                            if (currentCommand == commands.csq)
                                parseSQ(response);
                        }
                    }
                    else
                    {
                        //Cycle through the acceptable sbdix responses
                        for (int i = 0; i < sbdixResponses.Length; i++)
                        {
                            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbdixResponses[i].ToString()), expectedResponse, sbdixResponses[i].Length);
                            expectedResponseSB.Clear();
                            expectedResponseSB.Append(sbdixResponses[i]);
                            expectedResponseLength = sbdixResponses[i].Length;

                            sbExpectedResponse.Clear();
                            sbExpectedResponse.Append("Looking for IModem-->");
                            sbExpectedResponse.Append(UTF8Encoding.UTF8.GetChars(expectedResponse));
                            sbExpectedResponse.AppendLine("<--");
                            //Debug.Print(sbExpectedResponse.ToString());

                            sbResponse.Clear();
                            sbResponse.Append("Got-->");
                            sbResponse.Append(UTF8Encoding.UTF8.GetChars(response));
                            sbResponse.AppendLine("<--");
                            //Debug.Print(sbResponse.ToString());

                            if (GHI.Utilities.Arrays.Contains(response, 0, expectedResponse, 0, expectedResponseLength) >= 0)
                            {
                                gotResponse = true;
                                
                                sbTemp.Clear();
                                Array.Clear(returnField, 0, returnField.Length);
                                Array.Clear(returnField2, 0, returnField2.Length);
                                Utils.getField(response, commaByte, 0, ref returnField);
                                Utils.getField(returnField, colonByte, 1, ref returnField2);
                                sbTemp.Append(UTF8Encoding.UTF8.GetChars(returnField2));
                                sbdixMOStatus = int.Parse(sbTemp.ToString());

                                sbTemp.Clear();
                                Array.Clear(returnField, 0, returnField.Length);
                                Utils.getField(response, commaByte, 2, ref returnField);
                                sbTemp.Append(UTF8Encoding.UTF8.GetChars(returnField));
                                sbdixMTStatus = int.Parse(sbTemp.ToString());

                                sbTemp.Clear();
                                Array.Clear(returnField, 0, returnField.Length);
                                Utils.getField(response, commaByte, 5, ref returnField);
                                sbTemp.Append(UTF8Encoding.UTF8.GetChars(returnField));
                                sbdixMTQueued = int.Parse(sbTemp.ToString());

                                sbTemp.Clear();
                                sbTemp.Append("SBDIX Response: ");
                                sbTemp.Append(UTF8Encoding.UTF8.GetChars(response));
                                sbTemp.Append("   sbdixMOStatus = ");
                                sbTemp.Append(sbdixMOStatus.ToString());
                                sbTemp.Append("   sbdixMTStatus = ");
                                sbTemp.Append(sbdixMTStatus.ToString());
                                sbTemp.Append("   sbdixMTQueued = ");
                                sbTemp.Append(sbdixMTQueued.ToString());
                                Debug.Print(sbTemp.ToString());
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
}
