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

using SWModules;

namespace HWModules
{
    public sealed class BTConsole
    {
        public NativeUART IOPort;

        private MotherBoard motherboard = MotherBoard.Instance;

        private BTConsole()
        {
            IOPort = new NativeUART("BTconsole", QRecordType.BTConsole, processMessage, "COM4", 115200, Parity.None, 8, StopBits.One, 5000, 20, true, 36);
        }

        private static BTConsole instance;

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

        ~BTConsole()
        {
            Debug.Print("CLOSING BTCONSOLE");
        }

        public PressureTable DescendTable { get; set; }

        public bool IsOpen
        {
            get { return IOPort.IsOpen; }
        }


        public void init()
        {
            if (!IOPort.IsOpen) {
                IOPort.Open();
            }
            IOPort.DiscardInBuffer();
            IOPort.DiscardOutBuffer();
            Thread.Sleep(3000);
            //IOPort.Write(setCommandResponse,0,setCommandResponse.Length);


        }

        public void powerUp()
        {
            // Check for power before changing
            if (motherboard.QueryChannelPower(MotherBoard.ChannelNames.BluetoothChannel))
                return;
            motherboard.EnableChannelPower(MotherBoard.ChannelNames.BluetoothChannel);
            EngrLogger.SendConsoleLine += SendLine;
            init();
            EngrLogger.writeToColumns("Powering up BT Module");

        }

        public void powerDown()
        {
            // Check for power before changing
            if (!motherboard.QueryChannelPower(MotherBoard.ChannelNames.BluetoothChannel))
                return;
            EngrLogger.writeToColumns("Powering down BT Module");
            EngrLogger.SendConsoleLine -= SendLine;
            motherboard.DisableChannelPower(MotherBoard.ChannelNames.BluetoothChannel);

        }

        private static StringBuilder sbTemp = new StringBuilder(2048);

        public void SendLine(StringBuilder sbLineToSend)
        {
            IOPort.Write(UTF8Encoding.UTF8.GetBytes(sbLineToSend.ToString()), 0, sbLineToSend.Length);
        }

        public void SendLine(byte[] outBytes)
        {
            IOPort.Write(outBytes, 0, outBytes.Length);
        }

        public void CloseConsolePort()
        {
            IOPort.Close();
        }

        private static double simPressure = 0.0;

        public static bool DirectoryValid { get; set; }

        public static int uploadEngrFileNum = -1;

        // Working Arrays
        private static readonly StringBuilder sbConsoleCommand = new StringBuilder(32);
        private static StringBuilder sbCommand = new StringBuilder(64);
        private static StringBuilder sbInBytes = new StringBuilder(256);
        private static byte[] commandBytes = new byte[64];

        // Command to disable command response
        private static readonly byte[] setCommandResponse = UTF8Encoding.UTF8.GetBytes("ATS10=0\r\n");

        public void processConsoleCommand(byte[] consoleCommandMessage)
        {
            const byte byteToken = 44; // comma
            int tokenIndex = -1;
            double argValue = -1;

            int crIndex = Array.IndexOf(consoleCommandMessage, 13);

            //Ignore command if it doesn't have a CR
            if (crIndex == 0) {

                sbTemp.Clear();
                sbTemp.Append("Received non-CR terminated Console Command: ");
                sbTemp.Append(SafeEncoding.GetChars(consoleCommandMessage, 0,
                    (Array.IndexOf(consoleCommandMessage, 0) - 1)));
                EngrLogger.writeToColumns(sbTemp);
                return;
            }

            sbTemp.Clear().Append("Processing Console Command: ");
            sbTemp.Append(SafeEncoding.GetChars(consoleCommandMessage, 0, crIndex));
            EngrLogger.writeToColumns(sbTemp);

            //Check if the command has an argument
            tokenIndex = Array.IndexOf(consoleCommandMessage, byteToken);

            //If it has an argument, process it here
            if (tokenIndex > 0) {
                sbTemp.Clear();
                sbTemp.Append(SafeEncoding.GetChars(consoleCommandMessage, tokenIndex + 1, (crIndex - tokenIndex - 1)));
                try {
                    argValue = double.Parse(sbTemp.ToString());
                    //sbTemp.Clear().Append("Console arg value = ");
                    //sbTemp.Append(argValue);
                    //EngrLogger.writeToColumns(sbTemp);
                }
                catch {
                    sbTemp.Clear().Append("[ERROR] parsing console command argument: ");
                    sbTemp.Append(SafeEncoding.GetChars(consoleCommandMessage, 0, (crIndex - 1)));
                    EngrLogger.writeToColumns(sbTemp);
                    return;
                }

                sbTemp.Clear();
                sbTemp.Append(SafeEncoding.GetChars(consoleCommandMessage, 1, tokenIndex - 1));
                switch (sbTemp.ToString().ToUpper()) {
                    case "SIMP":
                        simPressure = argValue;
                        sbTemp.Clear().Append("Sim P = ").Append(simPressure.ToString());
                        break;
                    case "UPENGR":
                        SV.UploadEngrFile = true;
                        uploadEngrFileNum = (int)argValue;
                        break;
                    case "MAXPROFILES":
                        if (((int)argValue > 0) && ((int)argValue < 200)) {
                            sbTemp.Clear().Append("Setting Max Number of Profiles to: ").Append(((float)argValue).ToString());
                            EngrLogger.writeToColumns(sbTemp);
                            SV.MaxProfiles = (int)argValue;
                        }
                        else
                            EngrLogger.writeToColumns("MaxProfiles value out of range");
                        break;
                    case "MISSIONTIMEOUT":
                        if (((int)argValue > 0) && ((int)argValue < 120)) {
                            SV.MissionTimeoutTS = new TimeSpan(0, (int)argValue, 0, 0);
                            SV.MissionTimedOut = false;
                            sbTemp.Clear().Append("Set MissionTimeout hours to: ").Append(SV.MissionTimeoutTS.ToString());
                            EngrLogger.writeToColumns(sbTemp.ToString());
                        }
                        else
                            EngrLogger.writeToColumns("MissionTimeout value out of range");
                        break;
                    case "PARKTIME":
                        if (((int)argValue > 0) && ((int)argValue < (2 * 24 * 3600))) 
                        {
                            DescendTable.parkTime = new TimeSpan(0, 0, (int)argValue, 0);
                            sbTemp.Clear().Append("Park time minutes to: ").Append(DescendTable.parkTime.ToString());
                            EngrLogger.writeToColumns(sbTemp);
                            
                        }
                        else
                            EngrLogger.writeToColumns("ParkTime value out of range");
                        break;
                    case "PARKPRESSURE":
                        if (((float)argValue > 4) && ((float)argValue < 300)) {
                            sbTemp.Clear().Append("Setting Park Pressure to: ").Append(((float)argValue).ToString());
                            EngrLogger.writeToColumns(sbTemp);
                            DescendTable.pressure = (float)argValue;
                        }
                        else
                            EngrLogger.writeToColumns("ParkPressure value out of range");
                        break;
                }
            }
            else  //process commands without an argument
            {
                sbTemp.Clear();
                sbTemp.Append(SafeEncoding.GetChars(consoleCommandMessage, 1, (crIndex - 1)));
                switch (sbTemp.ToString().ToUpper()) {
                    case "GETDIR":
                        EngrLogger.getDirectory();
                        DirectoryValid = true;
                        break;
                    case "GO":
                        SV.SurfaceOpsGo = true;
                        break;
                    case "RECOVERYTRUE":
                        SV.ForceRecoveryState = true;                        
                        SV.ForceRecoveryReason.Clear();
                        SV.ForceRecoveryReason.Append("[ERROR] RECOVERYTRUE cmd from console");
                        EngrLogger.Comment(SV.ForceRecoveryReason.ToString());
                        break;
                    case "RECOVERYFALSE":
                        SV.ForceRecoveryState = false;
                        SV.ForceRecoveryReason.Clear();
                        SV.ForceRecoveryReason.Append("Console set SV.ForceRecoveryState false");
                        EngrLogger.Comment(SV.ForceRecoveryReason.ToString());
                        break;
                    case "STARTDOG":
                        sbTemp.Clear().Append("Starting Watchdog");
                        EngrLogger.writeToColumns(sbTemp);
                        WatchDog.startWatchdog();
                        break;
                    case "FAKEMTSBD":
                        NAL_A3LAR.fakeMTSBD();
                        break;
                    case "SURFACEOPS":
                        sbTemp.Clear().Append("Changing State to SurfaceOps");
                        EngrLogger.writeToColumns(sbTemp);
                        SV.CurrentState = CPFStates.surfaceOps;
                        SV.DoStateEntry = true;
                        break;
                    case "EXIT":
                        sbTemp.Clear().Append("Console Exit Requested");
                        EngrLogger.writeToColumns(sbTemp);
                        SV.DoStateEntry = true;
                        SV.CurrentState = CPFStates.exit;
                        break;
                    default:
                        sbTemp.Clear();
                        sbTemp.Append("Invalid command, try again\r\n");
                        SendLine(sbTemp);
                        break;
                }
            }

            sbConsoleCommand.Clear();
            sbConsoleCommand.Append("NONE");
        }

        public void processMessage(QRecord qRecord)
        {
            Debug.Print("Processing...");
            processConsoleCommand(qRecord.byteArray);
        }

    }
}
