using System;
using System.Text;
using System.Threading;
using GHI.Processor;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SensorModules;
using SWModules;

namespace HWModules
{
    public sealed class GPS1 : gpsUBlox
    {
       private GPS1()
            : base(new I2CDevice.Configuration(0x42, 20), 6)
        {}

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

    }

    public class gpsUBlox : I2CPortBase
    {
        public struct GPSPosition
        {
            public StringBuilder latSB;
            public StringBuilder lonSB;
            public int numSats;
            public StringBuilder gpsTimeOfFix;
            public DateTime cpfDateTimeOfFix;
            public TimeSpan gpsTTFF;
            public StringBuilder rawMessage;
        }
        public GPSPosition latestPosition = new GPSPosition();

        public gpsUBlox(I2CDevice.Configuration config, int slotNum)
            : base(config, slotNum)
        {
            latestPosition.latSB = new StringBuilder(16);
            latestPosition.lonSB = new StringBuilder(16);
            latestPosition.numSats = -1;
            latestPosition.gpsTimeOfFix = new StringBuilder(16);
            latestPosition.gpsTTFF = new TimeSpan();
            latestPosition.rawMessage = new StringBuilder(256);
            latestSVStatus.rawMessage = new StringBuilder(512);
        }

        private MotherBoard motherboard = MotherBoard.Instance;
        private static DateTime gpsPowerupTime = new DateTime(1900, 1, 1);

        public void powerUp()
        {
            EngrLogger.writeToColumns("Powering up GPS");
            motherboard.EnableChannelPower(MotherBoard.ChannelNames.GPSChannel);
            gpsPowerupTime = DateTime.Now;
        }

        public void powerDown()
        {
            EngrLogger.writeToColumns("Powering down GPS");
            motherboard.DisableChannelPower(MotherBoard.ChannelNames.GPSChannel);
        }

        private static DateTime pubx04DateTime = new DateTime();
        private static StringBuilder pubx04ReturnSB = new StringBuilder(128);
        private static byte[] pubx04ReturnBytes = new byte[128];
        private static readonly byte[] commandPUBX04 = UTF8Encoding.UTF8.GetBytes("$PUBX,04*37\r\n");
        private static readonly byte[] bytesPUBX04 = UTF8Encoding.UTF8.GetBytes("UBX,04");
        private static TimeSpan GPSminusRTCTimeSpan = new TimeSpan();
        private static TimeSpan GPSminusLocalTimeSpan = new TimeSpan();
        private static TimeSpan RTCminusLocalTimeSpan = new TimeSpan();
        private static TimeSpan nanTimeSpan = new TimeSpan(long.MaxValue);

      
        public void synchToGPSTime()
        {
            GPSminusRTCTimeSpan = nanTimeSpan;
            GPSminusLocalTimeSpan = nanTimeSpan;

            for (int i = 0; i < 3; i++)
            {
                sbTemp.Clear();
                sbTemp.Append("Response to GPS PUBX04 message");
                Array.Clear(pubx04ReturnBytes, 0, pubx04ReturnBytes.Length);
                pubx04ReturnSB.Clear();
                pubx04ReturnBytes = sendI2CCmd(commandPUBX04);

                EngrLogger.writeToColumns(EngrLiterals.ColumnNums.dateTime, DateTime.Now,
                            EngrLiterals.ColumnNums.state, SV.CurrentState,
                            EngrLiterals.ColumnNums.comment, sbTemp,
                            EngrLiterals.ColumnNums.pressure, SV.Pressure,
                            EngrLiterals.ColumnNums.GPS, pubx04ReturnBytes);

                if (GHI.Utilities.Arrays.Contains(pubx04ReturnBytes, bytesPUBX04) >= 0)
                {
                    pubx04ReturnSB.Append(SafeEncoding.GetChars(pubx04ReturnBytes));
                    int hour = (pubx04ReturnSB[7] - 48) * 10 + (pubx04ReturnSB[8] - 48);
                    int minute = (pubx04ReturnSB[9] - 48) * 10 + (pubx04ReturnSB[10] - 48);
                    int second = (pubx04ReturnSB[11] - 48) * 10 + (pubx04ReturnSB[12] - 48);
                    int day = (pubx04ReturnSB[17] - 48) * 10 + (pubx04ReturnSB[18] - 48);
                    int month = (pubx04ReturnSB[19] - 48) * 10 + (pubx04ReturnSB[20] - 48);
                    int year = 2000 + (pubx04ReturnSB[21] - 48) * 10 + (pubx04ReturnSB[22] - 48);

                    //TODO P3 It sure would be nice to figure out a way to do this without using "new".  But DateTime is immutable
                    if (year >= 2018)
                    {
                        pubx04DateTime = new DateTime(year, month, day, hour, minute, second);
                        GPSminusLocalTimeSpan = pubx04DateTime - DateTime.Now;
                        Utility.SetLocalTime(pubx04DateTime);
                        sbTemp.Clear();
                        sbTemp.Append(DateTime.Now.ToString("yyyy-MMM-ddTHH:mm:ssZ"));
                        sbTemp.Append(Literals.tabChar);
                        sbTemp.Append("GPS minus Local Time = ");
                        sbTemp.Append(GPSminusLocalTimeSpan.ToString());
                        SciLogger.appendToMsgFooter(sbTemp);

                        GPSminusRTCTimeSpan = pubx04DateTime - RealTimeClock.GetDateTime();
                        RealTimeClock.SetDateTime(pubx04DateTime);
                        RealTimeClock.GetDateTime();
                        sbTemp.Clear();
                        sbTemp.Append(DateTime.Now.ToString("yyyy-MMM-ddTHH:mm:ssZ"));
                        sbTemp.Append(Literals.tabChar);
                        sbTemp.Append("GPS minus RTC Time = ");
                        sbTemp.Append(GPSminusRTCTimeSpan.ToString());
                        SciLogger.appendToMsgFooter(sbTemp);
                        sbTemp.Clear();
                        sbTemp.Append("Attempting GPS time synch, try number: ").Append(i);
                        sbTemp.Append(";  Set system time to: ").Append(pubx04DateTime);
                        EngrLogger.writeToColumns(1, sbTemp);
                    }
                    else
                    {
                        RTCminusLocalTimeSpan = RealTimeClock.GetDateTime() - DateTime.Now;
                        Utility.SetLocalTime(RealTimeClock.GetDateTime());
                        sbTemp.Clear();
                        sbTemp.Append(DateTime.Now.ToString("yyyy-MMM-ddTHH:mm:ssZ"));
                        sbTemp.Append(Literals.tabChar);
                        sbTemp.Append("RTC minus Local Time = ");
                        sbTemp.Append(RTCminusLocalTimeSpan.ToString());
                        SciLogger.appendToMsgFooter(sbTemp);
                        sbTemp.Clear().Append("Time from GPS <2018; Adjust from RTC: ");
                        sbTemp.Append(RealTimeClock.GetDateTime());
                        sbTemp.Append(" + ").Append(RTCminusLocalTimeSpan);
                        EngrLogger.writeToColumns(1, sbTemp);
                    }

                    break;
                }
                else
                {
                    Utility.SetLocalTime(RealTimeClock.GetDateTime());

                    pubx04ReturnSB.Clear();
                    pubx04ReturnSB.Append(SafeEncoding.GetChars(pubx04ReturnBytes));
                    //Debug.Print("Try Number: " + i.ToString() + ";  [ERROR] synching to GPS time with PUBX,04");
                    //Debug.Print(pubx04ReturnSB.ToString());
                }
            }
        }

        private static DateTime gpsTryTime = new DateTime(1900, 1, 1);
        private static TimeSpan gpsTryTimeSpan = new TimeSpan(0, 0, 30);
        private static int gpsTryNum = 0;
        private static gpsUBlox.GPSPosition gpsPosition = new gpsUBlox.GPSPosition();

        public int getGPSPosition(int maxNumTries) //TODO P2 consider using UBX-NAV-PVT message
        {
            if ((DateTime.Now - gpsTryTime) >= gpsTryTimeSpan)
            {
                sbTemp.Clear();
                sbTemp.Append("Trying GPS get position try num: ");
                sbTemp.Append(gpsTryNum.ToString());
                sbTemp.Append(" of ");
                sbTemp.Append(maxNumTries.ToString());
                gpsPosition = getPUBX00Position();
                if (gpsPosition.numSats >= 4)      
                {
                    gpsPosition.gpsTTFF = DateTime.Now - gpsPowerupTime;
                    EngrLogger.writeToColumns("Succesful GPS getPosition");
                    return (gpsPosition.numSats);
                }
                else
                {
                    gpsTryTime = DateTime.Now;
                    gpsTryNum++;
                    return (-1);
                }
            }
            else
            {
                if (gpsTryNum >= maxNumTries)
                {
                    EngrLogger.writeToColumns("GPS get position exceeded max number of tries");
                    gpsTryNum = 0;
                    return (-2);
                }
                else
                    return (0);
            }
        }

        private static StringBuilder pubx00ReturnSB = new StringBuilder(128);
        private static byte[] pubx00ReturnBytes = new byte[128];
        private static readonly byte[] commandPUBX00 = UTF8Encoding.UTF8.GetBytes("$PUBX,00*33\r\n");
        private static readonly byte[] bytesPUBX00 = UTF8Encoding.UTF8.GetBytes("UBX,00");

        public GPSPosition getPUBX00Position()
        {
            for (int i = 0; i < 3; i++)
            {
                pubx00ReturnBytes = sendI2CCmd(commandPUBX00);

                EngrLogger.writeToColumns(EngrLiterals.ColumnNums.dateTime, DateTime.Now,
                EngrLiterals.ColumnNums.state, SV.CurrentState,
                EngrLiterals.ColumnNums.comment, sbTemp,
                EngrLiterals.ColumnNums.pressure, SV.Pressure,
                EngrLiterals.ColumnNums.GPS, pubx00ReturnBytes);

                pubx00ReturnSB.Clear();
                pubx00ReturnSB.Append(SafeEncoding.GetChars(pubx00ReturnBytes));
                //Debug.Print("PUBX,00 Return: " + pubx00ReturnSB.ToString());
                if (GHI.Utilities.Arrays.Contains(pubx00ReturnBytes, bytesPUBX00) >= 0)
                {
                    sbTemp.Clear();
                    parsePUBX00(pubx00ReturnBytes, ref latestPosition);
                    break;
                }
                else
                {
                    sbTemp.Clear();
                    sbTemp.Append("[ERROR] Try Number: ");
                    sbTemp.Append(i.ToString());
                    sbTemp.Append(";  getting GPS Position with PUBX,00");
                    EngrLogger.writeToColumns(sbTemp);
                }
            }

            sbTemp.Clear();
            sbTemp.Append("Got Position: lat = ");
            sbTemp.Append(latestPosition.latSB);
            sbTemp.Append("  lon = ");
            sbTemp.Append(latestPosition.lonSB);
            sbTemp.Append("   num sats = ");
            sbTemp.Append(latestPosition.numSats.ToString());
            EngrLogger.writeToColumns(sbTemp);

            return (latestPosition);
        }
 
        public struct SVStatus
        {
            public StringBuilder rawMessage;
        }
        public SVStatus latestSVStatus = new SVStatus();

        private static StringBuilder pubx03ReturnSB = new StringBuilder(512);
        private static byte[] pubx03ReturnBytes = new byte[512];
        private static readonly byte[] commandPUBX03 = UTF8Encoding.UTF8.GetBytes("$PUBX,03*30\r\n");
        private static readonly byte[] bytesPUBX03 = UTF8Encoding.UTF8.GetBytes("UBX,03");

        public SVStatus getSatStatus()
        {
            Array.Clear(pubx03ReturnBytes, 0, pubx03ReturnBytes.Length);
            for (int i = 0; i < 3; i++)
            {
                pubx03ReturnBytes = sendI2CCmd(commandPUBX03);
                pubx03ReturnSB.Clear();
                pubx03ReturnSB.Append(SafeEncoding.GetChars(pubx03ReturnBytes));
                //Debug.Print("PUBX,03 Return: " + pubx03ReturnSB.ToString());
                if (GHI.Utilities.Arrays.Contains(pubx03ReturnBytes, bytesPUBX03) >= 0)
                {
                    sbTemp.Clear();
                    sbTemp.Append("GPS Sat Status: ");
                    sbTemp.Append(pubx03ReturnSB);
                    EngrLogger.writeToColumns(sbTemp);
                    break;
                }
                else
                {
                    sbTemp.Clear();
                    sbTemp.Append("Try Number: ");
                    sbTemp.Append(i.ToString());
                    sbTemp.Append(";  Error getting GPS Sat Status with PUBX,03");
                    EngrLogger.writeToColumns(sbTemp);
                }
            }
            latestSVStatus.rawMessage.Clear();
            latestSVStatus.rawMessage.Append(SafeEncoding.GetChars(pubx03ReturnBytes, 0, 256));

            return (latestSVStatus);
        }

        private static StringBuilder navorbReturnSB = new StringBuilder(512);
        private static byte[] navOrbReturnBytes = new byte[512];
        private static readonly byte[] commandNavOrb = { 0xB5, 0x62, 0x01, 0X34, 0x00, 0x00, 0x35, 0xA0 };
        private static readonly byte[] bytesNavOrb = { 0x01, 0x34 };

        public byte[] getNavOrb()
        {
            Array.Clear(navOrbReturnBytes, 0, navOrbReturnBytes.Length);
            for (int i = 0; i < 3; i++)
            {
                Array.Clear(navOrbReturnBytes, 0, navOrbReturnBytes.Length);
                navOrbReturnBytes = sendI2CCmd(commandNavOrb);
                navorbReturnSB.Clear();
                navorbReturnSB.Append(SafeEncoding.GetChars(navOrbReturnBytes));
                //Debug.Print("PUBX,03 Return: " + pubx03ReturnSB.ToString());
                if (GHI.Utilities.Arrays.Contains(navOrbReturnBytes, bytesNavOrb) >= 0)
                {
                    sbTemp.Clear();
                    sbTemp.Append("GPS Sat Status: ");
                    sbTemp.Append(pubx03ReturnSB);
                    EngrLogger.writeToColumns(sbTemp);
                    break;
                }
                else
                {
                    sbTemp.Clear();
                    sbTemp.Append("Try Number: ");
                    sbTemp.Append(i.ToString());
                    sbTemp.Append(";  Error getting GPS Sat Status with PUBX,03");
                    EngrLogger.writeToColumns(sbTemp);
                }
            }
            latestSVStatus.rawMessage.Clear();
            latestSVStatus.rawMessage.Append(SafeEncoding.GetChars(pubx03ReturnBytes, 0, 256));

            return (navOrbReturnBytes);
        }

        private static readonly byte[] forceColdstartCmd = { 0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x02, 0x00, 0xC8, 0x8F };
        private static byte[] coldstartReturnBytes = new byte[64];
 
        public byte[] forceColdstart()
        {
            Array.Clear(coldstartReturnBytes, 0, coldstartReturnBytes.Length);
            coldstartReturnBytes = sendI2CCmd(forceColdstartCmd);
            return (coldstartReturnBytes);
        }

        public static byte[] latBytes = new byte[16];
        public static byte[] lonBytes = new byte[16];
        public static byte[] numSatsBytes = new byte[16];


        private static byte[] fieldBytes = new byte[16];
        private static byte[] decMinBytes = new byte[16];
        private static byte[] degMinBytes = new byte[16];

        public static StringBuilder positionSB = new StringBuilder(64);

        //TODO P1 need to make sure position fields have something in them even if we didn't get a GPS fix
        public void parsePUBX00(byte[] inBytes, ref GPSPosition position)
        {
            try
            {
                Array.Clear(fieldBytes, 0, fieldBytes.Length);
                Utils.getField(inBytes, comma, 2, ref fieldBytes);
                position.gpsTimeOfFix.Clear();
                position.gpsTimeOfFix.Append(SafeEncoding.GetChars(fieldBytes));
                position.cpfDateTimeOfFix = DateTime.Now;

                Array.Clear(fieldBytes, 0, fieldBytes.Length);
                Utils.getField(inBytes, comma, 3, ref fieldBytes);
                position.latSB.Clear();
                position.latSB.Append(SafeEncoding.GetChars(fieldBytes));
                position.latSB.Append(Literals.tabChar);

                Array.Clear(fieldBytes, 0, fieldBytes.Length);
                Utils.getField(inBytes, comma, 4, ref fieldBytes);
                position.latSB.Append(SafeEncoding.GetChars(fieldBytes));
                 
                Array.Clear(fieldBytes, 0, fieldBytes.Length);
                Utils.getField(inBytes, comma, 5, ref fieldBytes);
                position.lonSB.Clear();
                position.lonSB.Append(SafeEncoding.GetChars(fieldBytes));
                position.lonSB.Append(Literals.tabChar);

                Array.Clear(fieldBytes, 0, fieldBytes.Length);
                Utils.getField(inBytes, comma, 6, ref fieldBytes);
                position.lonSB.Append(SafeEncoding.GetChars(fieldBytes));

                Array.Clear(fieldBytes, 0, fieldBytes.Length);
                Utils.getField(inBytes, comma, 18, ref fieldBytes);
                position.numSats = bytesToInt32(fieldBytes);
          
                position.rawMessage.Clear();
                position.rawMessage.Append(SafeEncoding.GetChars(inBytes, 0, latestPosition.rawMessage.Capacity));
            }
            catch
            {
                EngrLogger.writeToColumns("[ERROR] parsing GPS PUBX00 message");
                position.latSB.Clear();
                position.latSB.Append(Literals.nanString);
                position.lonSB.Clear();
                position.lonSB.Append(Literals.nanString);
                position.numSats = -1;

                position.rawMessage.Clear();
                position.rawMessage.Append(SafeEncoding.GetChars(inBytes, 0, latestPosition.rawMessage.Capacity));
            }
        }

        private int bytesToInt32(byte[] inBytes)
        {
            int returnValue = 0;
            double power = 1;
            int numBytes = 1;

            bool didOneCalc = false;

            if (Array.IndexOf(inBytes, 0) > 3)
                numBytes = 4;
            else
                numBytes = Array.IndexOf(inBytes, 0);

            for (int i = numBytes - 1; i > -1; i--)
            {
                if ((inBytes[i] >= 48) && (inBytes[i] <= 57))
                {
                    returnValue = returnValue + (int)((inBytes[i] - 48) * power);
                    power = power * 10;
                    didOneCalc = true;
                }
            }

            if (didOneCalc)
                return (returnValue);
            else
                return (-1);
        }

        private const byte dPoint = (byte)'.';
        private const byte comma = (byte)',';
        private const byte nullByte = (byte)'\0';
        private const byte north = (byte)'N';
        private const byte south = (byte)'S';
        private const byte east = (byte)'E';
        private const byte west = (byte)'W';

        private static I2CDevice.I2CTransaction[] cmdXaction = new I2CDevice.I2CTransaction[1];
        private static I2CDevice.I2CTransaction[] numBytesXAction = new I2CDevice.I2CTransaction[2];
        private static I2CDevice.I2CTransaction[] readFFXAction = new I2CDevice.I2CTransaction[2];

        private readonly byte[] numBytesAddr = new byte[2] { 0xFD, 0xFE };
        private static byte[] numBytesValue = new byte[2];
        private static byte[] dummyReadArray = new byte[2];
        private static byte[] evk7Bytes = new byte[256];
        private readonly byte[] ffAddr = new byte[1] { 0xFF };
        private static byte[] ffValue = new byte[1];

        private static StringBuilder sbTemp = new StringBuilder(128);

        private byte[] sendI2CCmd(byte[] command)
        {
            cmdXaction[0] = CreateWriteTransaction(command);
            //cmdXaction[1] = CreateReadTransaction(dummyReadArray);

            if (Execute(cmdXaction, 2000) == 0)
                EngrLogger.writeToColumns("[ERROR] Timeout error gpsUBlox sendI2CCmd method first Execute command");

            //Create I2C transaction to read number of bytes available
            int evk7BytesIndex = 0;
            int numBytes = 0;

            numBytesXAction[0] = CreateWriteTransaction(numBytesAddr);
            numBytesXAction[1] = CreateReadTransaction(numBytesValue);

            Array.Clear(numBytesValue, 0, numBytesValue.Length);
            Array.Clear(evk7Bytes, 0, evk7Bytes.Length);

            for (int i = 0; i < 10; i++)
            {
                if (Execute(numBytesXAction, 2000) == 0)
                {
                    EngrLogger.writeToColumns("[ERROR] Timeout gpsUBlox sendI2CCmd method second Execute command");
                }
                else
                {
                    numBytes = (256 * (int)numBytesValue[0]) + numBytesValue[1];
                    //Debug.Print("Register value: " + numBytesValue[0].ToString() + " " + numBytesValue[1]);
                    if ((numBytes > 0) && (numBytes < 0xFFFF))
                        break;
                }
                Thread.Sleep(200);
            }

            //Read address FF until message is complete
            if ((numBytes > 0) && (numBytes < 0xFFFF))
            {
                // Create I2C transaction to get buffer FF value
                readFFXAction[0] = CreateWriteTransaction(ffAddr);
                readFFXAction[1] = CreateReadTransaction(ffValue);

                evk7BytesIndex = 0;
                for (int i = 0; i < numBytes; i++)
                {
                   
                    if (Execute(readFFXAction, 2000) == 0)
                    {
                        EngrLogger.writeToColumns("[ERROR] Timeout error gpsUBlox sendI2CCmd method third Execute command");
                    }
                    else
                    {
                        //Debug.Print("FF Value = " + ffValue[0].ToString() + "  " + ((char)ffValue[0]).ToString());
                        if (evk7BytesIndex == 0)
                        {
                            if (ffValue[0] == 85)   //Wait for "U" start of text character.  The $P gets lost somewhere
                            {
                                evk7Bytes[evk7BytesIndex] = ffValue[0];
                                evk7BytesIndex = evk7BytesIndex + 1;
                            }
                        }
                        else
                        {
                            if (ffValue[0] == 10)
                            {
                                evk7Bytes[evk7BytesIndex] = ffValue[0];
                                evk7BytesIndex = evk7BytesIndex + 1;
                                break;
                            }
                            else
                            {
                                evk7Bytes[evk7BytesIndex] = ffValue[0];
                                evk7BytesIndex = evk7BytesIndex + 1;
                            }
                        }
                        if (evk7BytesIndex >= evk7Bytes.Length)
                            break;
                    }
                }
            }
            return (evk7Bytes);
        }
    }
}
