using System;
using System.Text;
using System.Threading;
using CPF.Profile_States;
using HWModules;
using SWModules;
using Microsoft.SPOT;

namespace CPF.Surface_States
{
    public class Recovery : Profile
    {
        public Recovery(string name)
        {
            stateName.Clear();
            stateName.Append(name);

            ECTimeout = int.MaxValue;     //Recovery mode should never timeout
        }

        private enum Action : int
        {

            initialDelay = 1,
            sendSBDD2,
            sendCREG,
            readGpsI2C,
            sendCSQ,
            buildSBDWTMsg,
            sendSBDWTCommand,
            loadSBDWTBuffer,
            sendSBDIX,
            waitForNextTX,
            lastAction,
            sendSBDRT,
            size = lastAction
        }
        private static Action currentAction;


        private static DateTime TXStartTime;
        private static TimeSpan TXPeriod = new TimeSpan(0, 15, 0);
        private static TimeSpan initialDelayPeriod = new TimeSpan(0, 5, 0);

        private static double desiredBellowsPosition = MissionConfig.RMSetBellowsPosition;
        private static TimeSpan lastCheckBellowsTime;

        public override int doStateEntryActions()
        {
            basicStateEntry();
            currentAction = (Action)1;
            sbdiTryNum = 0;
            sbdrtTryNum = 0;
            sendAckSBD = SbdReply.OK;


            // Power Down non-essential devices
            flbb1.powerDown();
            msc1.powerDown();
            optode1.powerDown();
            radiometer1.powerDown();

            // Power up essential recovery devices

            btConsole.powerUp();
            gps1.powerUp();
            iModem1.powerUp();
            motherBoard.EnableChannelPower(MotherBoard.ChannelNames.PositionSensorChannel);

            TXStartTime = DateTime.Now;

            //TODO P2 need to double check that 0.2 is the right offset
            if (SV.ProfileNum == 0)
                desiredBellowsPosition = MissionConfig.SOSetBellowsPosition;
            else
                desiredBellowsPosition = MissionConfig.RMSetBellowsPosition;

            sbTemp.Clear();
            sbTemp.Append("Recovery state desired bellows position = ");
            sbTemp.Append(desiredBellowsPosition.ToString("f2"));
            EngrLogger.writeToColumns(sbTemp);

            lastCheckBellowsTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime();

            return (1);
        }

        public static int sbdiTryNum = 0;
        public static int sbdrtTryNum = 0;
        private static int wfrReturn = 0;
        private int numSats = 0;

        public override int doStateActions()
        {
            switch (currentAction)
            {
                case (Action.initialDelay):
                    //Note: there is no check of any kind (e.g. make sure we're at the surface) before sending SBD to ensure SBDs get sent.  
                    //Since most errors that force ecovery will happen when submerged, this delay may prevent a few SBDs from getting transmitted while submerged
                    if (SV.ProfileNum == 0)
                        nextAction = (int)Action.sendSBDD2;
                    else
                    {
                        if ((DateTime.Now - TXStartTime) > initialDelayPeriod)
                            nextAction = (int)Action.sendSBDD2;
                    }

                    break;
                case (Action.sendSBDD2):
                    checkSystemData();
                    if (iModem1.waitForResponse(NAL_A3LAR.commands.sbdd2) != 0)
                        nextAction = (int)Action.sendCREG;

                    break;
                case (Action.sendCREG):
                    if (iModem1.waitForResponse(NAL_A3LAR.commands.creg) != 0)
                        nextAction = (int)Action.readGpsI2C;

                    break;
                case (Action.readGpsI2C):
                    numSats = gps1.getGPSPosition(2);
                    if ((numSats >= 4) || (numSats == -2)
                    ) //numSats >= 4 is a succesful GPS position, numSats == 2 is unsuccesful and all retries expended
                        nextAction = (int)Action.sendCSQ;
                    break;
                case (Action.sendCSQ):
                    if (iModem1.waitForResponse(NAL_A3LAR.commands.csq) != 0)
                        nextAction = (int)Action.buildSBDWTMsg;

                    break;
                case (Action.buildSBDWTMsg):
                    buildSBDMsg();
                    iModem1.SBDWTMsg = sbdwtMessage;
                    sbdiTryNum = 0;
                    nextAction = (int)Action.sendSBDWTCommand;
                    break;
                case (Action.sendSBDWTCommand):
                    //Send the SBDWT<cr> command first
                    //This 2 step method allows for the full 1920 byte message to be sent
                    if (iModem1.waitForResponse(NAL_A3LAR.commands.sendSBDWT) != 0)
                        nextAction = (int)Action.loadSBDWTBuffer;
                    break;
                case (Action.loadSBDWTBuffer):
                    //Now load the sbdwt buffer with the message
                    //This 2 step method allows for the full 1920 byte message to be sent
                    if (iModem1.waitForResponse(NAL_A3LAR.commands.loadSBDWTBuffer) != 0)
                        nextAction = (int)Action.sendSBDIX;
                    break;
                case (Action.sendSBDIX):
                    wfrReturn = iModem1.waitForResponse(NAL_A3LAR.commands.sbdix);
                    if (wfrReturn == 0) break;

                    sbdiTryNum++;
                    // wfr = something
                    sbTemp.Clear();
                    sbTemp.Append("IModem SBDIX try num: ");
                    sbTemp.Append(sbdiTryNum);
                    EngrLogger.writeToColumns(sbTemp);

                    // Give up after x tries, log the max attempts
                    if (sbdiTryNum >= MAX_SBDIX_ATTEMPTS)
                    {

                        sbTemp.Clear();
                        sbTemp.Append("[ERROR] Exiting Action.sendSBDIX: sbdiTryNum:");
                        sbTemp.Append(sbdiTryNum);
                        sbTemp.Append(" MOStatus:");
                        sbTemp.Append(iModem1.sbdixMOStatus);
                        sbTemp.Append(" MTQueued");
                        sbTemp.Append(iModem1.sbdixMTQueued);
                        EngrLogger.writeToColumns(sbTemp);
                        nextAction = (int)Action.waitForNextTX;
                        break;
                    }

                    sendAckSBD = SbdReply.OK;
                    if (wfrReturn > 0 && iModem1.MobileOriginationSuccess() && iModem1.sbdixMTStatus == (int)NAL_A3LAR.SbdixMtStat.SbdReceived)
                    {
                        nextAction = (int)Action.sendSBDRT;
                        break;
                    }
                    else if (wfrReturn > 0 && iModem1.MobileOriginationSuccess() && iModem1.sbdixMTStatus == (int)NAL_A3LAR.SbdixMtStat.NoneAvailable)
                    {
                        nextAction = (int)Action.waitForNextTX;
                        break;
                    }

                    //try again
                    nextAction = (int)Action.sendSBDIX;
                    break;
                case Action.sendSBDRT:
                    wfrReturn = iModem1.waitForResponse(NAL_A3LAR.commands.sbdrt);
                    if (wfrReturn == 0) break;
                    else if (wfrReturn < 0)
                    {
                        //send a NAK
                        sendAckSBD = SbdReply.NAK;
                        if (++sbdrtTryNum > MAX_SBDIX_ATTEMPTS)
                            nextAction = (int)Action.waitForNextTX;
                        else
                            nextAction = (int)Action.sendSBDD2;
                        break;
                    }
                    else
                    {
                        sendAckSBD = SbdReply.ACK;
                        nextAction = (int)Action.sendSBDD2;
                    }
                    break;

                case Action.waitForNextTX:
                    //TODO P2 maybe should power down Iridium, bluetooth and GPS during this time
                    if ((DateTime.Now - TXStartTime) > TXPeriod)
                    {
                        TXStartTime = DateTime.Now;
                        sbdrtTryNum = 0;
                        sbdiTryNum = 0;
                        nextAction = (int)Action.sendSBDD2;
                    }
                    else
                        //TODO P1 see if characted arriving on modem port will get processed while while Thread.Sleeping
                        Thread.Sleep(1000);  //TODO P1 this value needs to be increased for production deployments
                    break;
                case (Action.lastAction):       //TODO P3: probably don't need this, default case will take care of it
                    //should never get here
                    currentAction = Action.lastAction;

                    break;
                default:
                    break;
            }

            currentAction = (Action)checkActionReturn(nextAction, (int)currentAction);

            return (1);
        }

        private readonly TimeSpan checkBellowsPeriod = new TimeSpan(0, 0, 5);
        private static bool pumpIsRunning = false;

        private void checkForRunPump(double bellowsPosition)
        {
            if ((bellowsPosition < 0.0) || (bellowsPosition > 100.0))
            {
                sbTemp.Clear();
                sbTemp.Append("checkForRunPump bellows position out of bounds: ");
                sbTemp.Append(bellowsPosition.ToString());
                EngrLogger.Comment(sbTemp.ToString());
                return;
            }

            if (bellowsPosition > (desiredBellowsPosition + 0.2))
            {
                buoyancyEngine.increaseBuoyancy(200000, false);
                sbTemp.Clear();
                sbTemp.Append("Recovery mode increasing buoyancy. Bellows Position = ");
                sbTemp.Append(bellowsPosition.ToString());
                EngrLogger.writeToColumns(sbTemp);
                pumpIsRunning = true;
            }
            else if (bellowsPosition <= desiredBellowsPosition)
            {
                buoyancyEngine.stopMotor();
                if (pumpIsRunning)
                {
                    pumpIsRunning = false;
                    sbTemp.Clear();
                    sbTemp.Append("Recovery mode stopping pump. Bellows Position = ");
                    sbTemp.Append(bellowsPosition.ToString());
                    EngrLogger.writeToColumns(sbTemp);
                }
            }
        }

        public override CPFStates checkEvents()
        {
            if ((Microsoft.SPOT.Hardware.Utility.GetMachineTime() - lastCheckBellowsTime) > checkBellowsPeriod)
            {
                checkForRunPump(buoyancyEngine.getBellowsPositionStringPot());
                lastCheckBellowsTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime();
            }

            if (SV.SurfaceOpsGo && !SV.ForceRecoveryState)
            {
                // Check we can write files before leaving this state
                if (FileSystemCheck.CheckSDCard() == false)
                {
                    EngrLogger.Comment("SD Card file write issue, remaining in recovery mode.");
                    SV.ForceRecoveryReason.Clear();
                    SV.ForceRecoveryReason.Append("file write issue after $GO");
                    return (CPFStates.recovery);
                }

                SV.SurfaceOpsGo = false;

                if (SV.ProfileNum == 0)
                    return (CPFStates.preMissionDelay);
                else
                    return (CPFStates.initProfile);

            }
            return (CPFStates.recovery);
        }

        public override int doExitActions(bool timedOut)
        {
            if (timedOut)
            { }
            else
            {
                basicStateExit(false);
            }

            return (1);
        }

        public override CPFStates doTimeoutAction()
        {
            return (CPFStates.recovery);
        }
    }
}
