
using System;
using Microsoft.SPOT;

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

namespace CPF.Surface_States
{
    public class SurfaceOps : StateBase
    {
        //TODO P2 this might not need to be a singleton anymore since it moved into surfaceOps

        public SurfaceOps(string name)
        {
            stateName.Clear();
            stateName.Append(name);

            //ECTimeout = 30 * 60 * 1000;     //Typical value in minutes for at sea operations
            ECTimeout = 20 * 60 * 1000;     //Typical value in minutes for quick turn around testing
        }

        public enum Action
        {
            //list sequential actions here
            getSurfaceSamples = 1,
            getMscConfigs,
            endProfile,
            sendSBDD2,
            sendCREG,
            readGpsI2C,
            sendCSQ,
            sendFileViaRUDICS,
            buildSBDWTMsg,
            sendSBDWTCommand,
            loadSBDWTBuffer,
            sendSBDIX,
            lastAction,
            sendSBDRT,
            size = lastAction,
        }
        private Action currentAction;

        private static TimeSpan oneDay = new TimeSpan(24, 0, 0);
        public override int doStateEntryActions()
        {
            basicStateEntry();
            startedNewFiles = false;
            SV.SurfaceOpsGo = false;
            sbdiTryNum = 0;
            sbdrtTryNum = 0;
            sendAckSBD = SbdReply.OK;

            //Note: GPS and Bluetooth are powered up on exit from ascend
            iModem1.powerUp();

            gps1.synchToGPSTime();

            buoyancyEngine.closeMV();

            //Polls the batteries //
            var lhe = LheBoard.Instance;
            lhe.UpdateBatteryBank();

            //Array.Clear(Program.pubxByteArray, 0, Program.pubxByteArray.Length);

            //We're skipping the first few actions to make SBD comms more reliable
            currentAction = (Action)1;
            //currentAction = Action.sendFileViaRUDICS;

            //State-specific entry items below
            sbdiTryNum = 0;

            firstGetSurfaceSamples = true;
            getSurfaceSamplesStartTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Subtract(oneDay);

            optode1.powerUp();
            radiometer1.powerUp();

            sbTemp.Clear().Append("Profile Number = ").Append(SV.ProfileNum);
            EngrLogger.writeToColumns(sbTemp);
            Thread.Sleep(1000);

            ctd1.startCommandModeTimer(MissionConfig.SBE41CommandModeSamplePeriod);

            numSurfaceSamplesGotten = 0;

            // elmo power saving
            motherBoard.DisableChannelPower((int)MotherBoard.ChannelNames.PumpMotorControllerElexChannel);
            lheBoard.EnablePumpPower(false);

            return (1);
        }

        public int sbdiTryNum = 0;
        public int sbdrtTryNum = 0;

        private static int wfrReturn = 0;
        private int numSats = 0;
        private bool waiting = false;
        private int waitCounter = 0;

        private TimeSpan getSurfaceSamplesStartTime = new TimeSpan();
        private TimeSpan getSurfaceSamplesWaitTime = new TimeSpan(0, 0, 25);
        private bool firstGetSurfaceSamples = true;
        private bool thenGetMscConfig;

        public int CurrentAction
        {
            get { return (int)currentAction; }
        }

        private static bool startedNewFiles = false;
        private static int maxSBDIXTrys = 2;

        private static int numSurfaceSamplesGotten = 0;
        private static int numSurfaceSamplesToGet = 5;

        public override int doStateActions()
        {
            switch (currentAction)
            {
                #region SurfaceOps Action Sequence
                case (Action.getSurfaceSamples):
                    if (firstGetSurfaceSamples)
                    {
                        if (numSurfaceSamplesGotten < numSurfaceSamplesToGet)
                        {
                            if ((Microsoft.SPOT.Hardware.Utility.GetMachineTime() - getSurfaceSamplesStartTime) > getSurfaceSamplesWaitTime)
                            {
                                sbTemp.Clear().Append("Starting Surface Optode and OCR Samples number ");
                                sbTemp.Append((numSurfaceSamplesGotten + 1).ToString());
                                sbTemp.Append(" of ");
                                sbTemp.Append(numSurfaceSamplesToGet.ToString());

                                EngrLogger.writeToColumns(sbTemp);
                                sbTemp.Append(Literals.tabChar);
                                sbTemp.Append(DateTime.Now.ToString("yyyy-MMM-ddTHH:mm:ssZ"));
                                SciLogger.writeToLog(sbTemp);
                                SciLogger.appendToMsgFooter(sbTemp);

                                Thread.Sleep(1000);
                                SciLogger.startNewSampleCycle(SciLogger.Instruments.Optode | SciLogger.Instruments.Radiometer | SciLogger.Instruments.Ctd);
                                numSurfaceSamplesGotten++;
                                getSurfaceSamplesStartTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime();
                            }
                        }
                        else
                        {
                            firstGetSurfaceSamples = false;
                            thenGetMscConfig = true;
                        }
                    }
                    else
                    {
                        if ((Microsoft.SPOT.Hardware.Utility.GetMachineTime() - getSurfaceSamplesStartTime) > getSurfaceSamplesWaitTime)
                        {
                            EngrLogger.writeToColumns("Surface sampling finished");

                            nextAction = (int)(thenGetMscConfig ? Action.getMscConfigs : Action.sendSBDD2);
                            if (thenGetMscConfig)
                            {
                                SciLogger.appendMscDataFileTimeStamp(DateTime.Now, true);
                                msc1.StartGetConfigsSequencer();
                            }
                            thenGetMscConfig = false;
                        }
                    }
                    break;
                case (Action.getMscConfigs):

                    if (msc1.SequenceGetConfigs(false) != 0)
                        nextAction = (int)Action.readGpsI2C;
                    break;

                case (Action.readGpsI2C):
                    numSats = gps1.getGPSPosition(2);
                    SciLogger.updateLatLon(gps1.latestPosition);
                    nextAction = (int)Action.endProfile;
                    break;

                case (Action.endProfile):
                    SciLogger.endSampleCycle();
                    startNewEngAndSciFiles();
                    startedNewFiles = true;
                    nextAction = (int)Action.sendSBDD2;
                    break;

                case (Action.sendSBDD2):
                    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.sendCSQ;

                    break;
                case (Action.sendCSQ):
                    nextAction = (int)Action.sendFileViaRUDICS;
                    break;
                case (Action.sendFileViaRUDICS):
                    //if (actionSendFilesViaRUDICS()) //Check Thread for completion
                    //{
                    //}
                    //else
                    //TODO P1 when this gets implemented if file is send successfully skip over SBD xmit else try sending SBD
                    EngrLogger.writeToColumns("Skipping over SendFilesViaRudics");
                    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.lastAction;
                        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.lastAction;
                        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.lastAction;
                        else
                            nextAction = (int)Action.sendSBDD2;
                        break;
                    }
                    else
                    {
                        sendAckSBD = SbdReply.ACK;
                        nextAction = (int)Action.sendSBDD2;
                    }
                    break;
                case (Action.lastAction):
                    //wait here until timeout, or $GO command
                    currentAction = Action.lastAction;

                    break;
                default:
                    break;
                #endregion
            }

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

            //TODOD P1 make this rigthupload logs if requested
            //uploadEngrLog();

            return (1);
        }

        public override CPFStates checkEvents()
        {
            if (SV.SurfaceOpsGo || (currentAction == Action.lastAction))
            {
                SV.SurfaceOpsGo = false;
                return (CPFStates.initProfile);
            }

            return (CPFStates.surfaceOps);
        }

        public override CPFStates doTimeoutAction()
        {
            iModem1.waitingForResponse = false;

            return (CPFStates.ABDecBuocyFast);
        }

        public override int doExitActions(bool timedOut)
        {
            // elmo power saving
            motherBoard.EnableChannelPower((int)MotherBoard.ChannelNames.PumpMotorControllerElexChannel);
            lheBoard.EnablePumpPower(true);
            buoyancyEngine.initPumpMotor();

            if (!startedNewFiles)
            {
                SciLogger.endSampleCycle();
                startNewEngAndSciFiles();
            }
            startedNewFiles = false;

            if (timedOut)
            {
                basicStateExit(true);

                //reset the current action to top of action list
                currentAction = (Action)1;

                //reset these so next time in SO they can be evaluated
                timeSynced = false;
                telemetryDone = false;
                SV.SurfaceOpsGo = false;
            }
            else
            {
                basicStateExit(false);
                SV.SurfaceOpsGo = false;
            }

            return (1);
        }

        public int handleErrors(int returnVal, int currentAction)
        {
            throw new NotImplementedException();
        }

        private static bool bRUDICSThreadRunning = false;
        private static bool bEscapeSeqReceived = false;
        private static int iRUDICSLogoutAttempts = 0;
        private const int RUDICSEXITMAXATT = 10;
        private static int resp;
        private static Thread threadRudics;
        /// <summary>
        /// Starts and watches a thread in execution for sending files via RUDICS
        /// </summary>
        /// <returns></returns>
        public bool actionSendFilesViaRUDICS()
        {
            //Start the thread our first time here
            if (!bRUDICSThreadRunning)
            {
                threadRudics = new Thread(() => RudicsThreadMethod());
                threadRudics.Start();
                bRUDICSThreadRunning = true;
                bEscapeSeqReceived = false;
                iRUDICSLogoutAttempts = 0;
            }


            //test if the thread is still runnning
            if (!threadRudics.IsAlive)
            {
                //test if we've been attempting too long
                if (iRUDICSLogoutAttempts >= RUDICSEXITMAXATT)
                    return false;

                //be safe and hangup any call
                if (!bEscapeSeqReceived)
                    resp = iModem1.waitForResponse(NAL_A3LAR.commands.escapeSeq);
                else
                    resp = iModem1.waitForResponse(NAL_A3LAR.commands.ath0);
                if (resp == 0) //still waiting
                { }
                else if (resp == 1) //got it
                {
                    if (!bEscapeSeqReceived)
                        bEscapeSeqReceived = true;
                    else//ok we got a response to ath0 
                    {
                        bEscapeSeqReceived = false;
                        bRUDICSThreadRunning = false;
                    }
                }
                else if (resp < 0) //error
                {
                    bEscapeSeqReceived = (bEscapeSeqReceived) ? false : true; // we have to do this because if the call was already dropped, the escape doesn't send an OK on reply. 
                    iRUDICSLogoutAttempts++;
                }
            }

            return (bRUDICSThreadRunning);


        }

        private object _rLock = new object();
        private bool _lastFileSentSuccess;
        private bool _lastFileReceivedSuccess;
        private string _lastFileName = null;
        private int _lastFileAttempts = 0;
        private const int MaxFileAttempts = 3;

        private void startNewEngAndSciFiles()
        {
            EngrLogger.closeEngrLogFile();
            SciLogger.closeFiles();
            EngrLogger.openNewEngFile();
            SciLogger.openNewFiles();

        }
        private void RudicsThreadMethod()
        {
            int resp = 0;
            lock (_rLock)
            {
                _lastFileSentSuccess = false;
                _lastFileReceivedSuccess = false;
            }


            IridiumModem1 iModem1 = IridiumModem1.Instance;

            IridiumPosixSession session = new IridiumPosixSession(iModem1)
            {
                Username = "rawa",
                Password = "New0rleans",
                LoginPrompt = "TODO",
                PasswordPrompt = "TODO",
                CommandPrompt = "$ "
            };

            try
            {

                //Connect
                iModem1.SelectBearerServiceType(NAL_A3LAR.CallSpeed.BPS9600V110);
                Thread.Sleep(100); // enough time for the cbst message to come in

                byte[] baRUDICSnumber = Encoding.UTF8.GetBytes("T00881600005391");
                //Debug.Print("Dialing");
                int dialAttempts = 0;
                do
                {
                    sbTemp.Clear().Append("[RUDICS] Dialing Attempt: ").Append(dialAttempts + 1);
                    EngrLogger.writeToColumns(sbTemp);
                    resp = session.Connect(baRUDICSnumber);
                } while (resp < 0 && ++dialAttempts < 10);

                if (resp < 0)
                {
                    sbTemp.Clear().Append("[RUDICS] Failed to Login, CODE ").Append(resp);
                    EngrLogger.writeToColumns(sbTemp);
                    goto done;
                }

                //Login
                resp = session.Login();
                if (resp < 0)
                {
                    sbTemp.Clear().Append("[RUDICS] Failed to Login, CODE ").Append(resp);
                    EngrLogger.writeToColumns(sbTemp);
                    goto done;
                }

                //Delete File if its there already
                // TODO?? RETRY?


                //Send File(s)
                // Try to send all of the files in queue (current queue depth is 10)

                do
                {
                    //Let's see if our last filename made it out...
                    if (_lastFileName != null)
                        _lastFileAttempts++;
                    // Check if we've tried too many times.
                    if (_lastFileAttempts > MaxFileAttempts)
                    {
                        sbTemp.Clear().Append("[RUDICS] Max Attempts reached for file: ").Append(_lastFileName);
                        EngrLogger.writeToColumns(sbTemp);
                        _lastFileName = null;
                    }

                    //Dequeue a new filename if ready
                    if (_lastFileName == null)
                    {
                        DequeueNewFileForDownload();
                    }

                    //Well we still don't have anything, so no sending necessary 
                    if (_lastFileName == null) break;

                    //See if there's anything, otherwise move on...
                    sbTemp.Clear().Append("[RUDICS] Sending file: ").Append(_lastFileName);
                    EngrLogger.writeToColumns(sbTemp);

                    resp = session.ZModemSendTransaction(_lastFileName);
                    if (resp == 1)
                    {
                        sbTemp.Clear().Append("[RUDICS] File transfer success: ").Append(_lastFileName);
                        EngrLogger.writeToColumns(sbTemp);
                        _lastFileName = null;
                        lock (_rLock) _lastFileSentSuccess = true;
                    }
                } while (resp > 0);

                // TODO REENQUEUE FAILED NAME??
                if (resp < 0)
                {
                    sbTemp.Clear().Append("[RUDICS] File Sending Failed, CODE ").Append(resp);
                    EngrLogger.writeToColumns(sbTemp);
                    goto done;
                }


                //Logout
                resp = session.Logout();

            done:
                sbTemp.Clear().Append("[RUDICS] Thread Complete, Disconnecting");
                EngrLogger.writeToColumns(sbTemp);


            }
            finally
            {
                //Shut down the session.
                session.Disconnect();
            }
        }

        protected void DequeueNewFileForDownload()
        {
            try
            {
                var fileRecord = SciLogger.DequeueFileName;
                sbTemp.Clear().Append(SafeEncoding.GetChars(fileRecord.byteArray));
                _lastFileName = sbTemp.ToString();
            }
            catch (InvalidOperationException)
            {
                _lastFileName = null;
            }
            finally
            {
                _lastFileAttempts = 0;
            }
        }


        private int sendSBDD2()
        {
            if (iModem1.sendSBDD2() > 0)
            {
                //EngrLogger.quickWTC("Sent SBDD2");
                return ((int)Action.readGpsI2C);
            }
            else //error
                return (-1);
        }

        private static readonly StringBuilder UDF = new StringBuilder("Uploading Data File name = ");

        //SurfaceOps is the only place these are used; don't need to be in SV.
        private static bool timeSynced = false;
        private static bool telemetryDone = false;

        //TODO P1 Think about whether there is a better way to do this...
        //added to allow ProcessSQ to examine currentAction. Legacy code only processed IModem q messages if
        //surfaceOps was in sendCSQ substate.
        public int Substate
        {
            get { return (int)currentAction; }
        }
    }
}
