using System;
using System.IO;
using System.Text;
using System.Threading;
using HWModules;
using Microsoft.SPOT;
using SWModules;

namespace SensorModules
{
    public static class SciLogger
    {
        private static CTD1 ctd1;
        private static FLBB1 flbb1;
        private static MSC1 msc1;
        private static Optode1 optode1;
        private static Radiometer1 radiometer1;

        public static event SendLineDelegate SendConsoleLine;

        private static FileQueue _fileQ;
        private static QRecord _qRecord;

        private static FileStream msgFile;
        private static readonly StringBuilder msgFileName = new StringBuilder(64);

        private static FileStream logFile;
        private static readonly StringBuilder logFileName = new StringBuilder(64);

        private static FileStream isusFile;
        private static readonly StringBuilder isusFileName = new StringBuilder(64);

        private static FileStream duraFile;
        private static readonly StringBuilder duraFileName = new StringBuilder(64);

        public static StringBuilder CurrentState { get; set; }

        private static byte[] colNames = UTF8Encoding.UTF8.GetBytes(
            "\r\nComment\tDateTime\tpres_dBar\ttemp_degC\tsal_ppt\tTopt_degC\tTPhase_deg\tRPhase_deg\tpH_V\tNO3_V\tFSig_counts\tBbSig_counts\tTSig_counts\tOCRCh1_uWPerCM2PerNM\tOCRCh2\tOCRCh3\tOCRCh4\r\n");

        private static StringBuilder sbTemp = new StringBuilder(2056);
        private static StringBuilder sbTemp2 = new StringBuilder(2056);

        private static readonly byte[] Crlf = {13, 10};
        private static readonly byte[] Eot = {0x3C, 0x45, 0x4F, 0x54, 0x3E, 0x0D, 0x0A}; //"<EOT>\r\n"
        private static readonly byte[] Eop = {0x3C, 0x45, 0x4F, 0x50, 0x3E, 0x20, 0x3A, 0x20,  }; //"<EOP> : "
        
        private const byte tab = (byte)Literals.tabChar;

        private static StringBuilder gpsPositionSB = new StringBuilder("Lon Lat numSats gpsFixTime TTFF:\t no fix");

        //private static bool msgFileOpen = false;

        public static void closeFiles()
        {
            clearFileQueue();
            closeIsusFile();
            closeMsgFile();
            closeDuraFile();
            closeLogFile();
            EngrLogger.sdVol.FlushAll();
        }

        public static void openNewFiles()
        {
            openNewDuraFile();
            openNewLogFile();
            openNewMsgFile();
            openNewIsusFile();
        }

        public static void openNewMsgFile()
        {
            if (ctd1 == null) {
                ctd1 = CTD1.Instance;
                flbb1 = FLBB1.Instance;
                msc1 = MSC1.Instance;
                optode1 = Optode1.Instance;
                radiometer1 = Radiometer1.Instance;
            }

            if (_fileQ == null)
                _fileQ = new FileQueue();

            closeMsgFile();

            msgFileFooter.Clear();
            msgFileFooter.Append("Profile details\r\n");
            msgFileFooter.Append("Date Time\tComment\tPres (dBar)\tBel Pos (mm)\tBus Volt\tBus Cur (mA)\r\n");

            cpData.Clear();
            cpData.Append("\r\nCP Data\r\n");

            msgFileName.Clear();
            msgFileName.Append(EngrLogger.fileNameBase);
            msgFileName.Append(".msg");

            try {
                msgFile = new FileStream(msgFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);
                if (!msgFileOpen)
                    throw new Exception("File Open Error");
            }
            catch {
                EngrLogger.writeToColumns("[ERROR] opening .msg file");
                SV.ForceRecoveryState = true;
                SV.ForceRecoveryReason.Clear();
                SV.ForceRecoveryReason.Append("Error opening msg file");
            }
        }

        /// <summary>
        /// Open a new Isus Data file, give it the same name as the current science file,
        /// but with a .isus extension. 
        /// </summary>
        public static void openNewIsusFile()
        {
            closeIsusFile();

            isusFileName.Clear().Append(EngrLogger.fileNameBase).Append(".isus");
            try
            {
                isusFile = new FileStream(isusFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);
                if (!isusFileOpen)
                    throw new Exception("File Open Error, ISUS");
                EngrLogger.Comment("Opening new .isus file: "+isusFileName);
            }
            catch
            {
                SV.ForceRecoveryState = true;
                SV.ForceRecoveryReason.Clear();
                SV.ForceRecoveryReason.Append("Error opening isus file");
                EngrLogger.Comment("[ERROR] opening .isus file.");
            }

        }
        
        /// <summary>
        /// Open a new DuraFet file, give it the same name as the current science file,
        /// but with a .dura extension
        /// </summary>
        public static void openNewDuraFile()
        {
            closeDuraFile();

            duraFileName.Clear().Append(EngrLogger.fileNameBase).Append(".dura");
            try
            {
                duraFile = new FileStream(duraFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);
                if (!duraFileOpen)
                    throw new Exception("File Open Error, DURA");
                EngrLogger.Comment("Opening new .dura file: "+duraFileName);
            }
            catch
            {
                SV.ForceRecoveryState = true;
                EngrLogger.Comment("[ERROR] opening .dura file.");
                SV.ForceRecoveryReason.Clear();
                SV.ForceRecoveryReason.Append("Error opening dura file");
            }

        }

        private static StringBuilder sbDataFileHeader = new StringBuilder(64);
        private const string FmtDateTime = "ddd MMM dd HH:mm:ss yyyy";

        /// <summary>
        /// Build the first line of either a .dura or .isus data
        /// file, which includes the profile number and start datetime
        /// as provided. 
        /// </summary>
        /// <param name="timeStamp"></param>
        public static void appendMscDataFileTimeStamp(DateTime timeStamp, bool isEndofProfile=false)
        {
            sbDataFileHeader.Clear().Append("Profile: ");
			sbDataFileHeader.Append(SV.FloatID).Append('.');
            sbDataFileHeader.Append(SV.ProfileNum.ToString("D6"));
            sbDataFileHeader.Append(Literals.spaceString);
            sbDataFileHeader.Append(timeStamp.ToString(FmtDateTime));
            sbDataFileHeader.Append(Literals.crlfString);
            if (isusFileOpen)
            {
                if (isEndofProfile) isusFile.Write(Eop,0,Eop.Length);
                isusFile.Write(SafeEncoding.UTF8.GetBytes(sbDataFileHeader.ToString()), 0, sbDataFileHeader.Length);
            }

            if (duraFileOpen)
            {
                if (isEndofProfile) duraFile.Write(Eop,0,Eop.Length);
                duraFile.Write(SafeEncoding.UTF8.GetBytes(sbDataFileHeader.ToString()), 0, sbDataFileHeader.Length);
            }
        }

        /// <summary>
        /// Close the Durafet Data File
        /// </summary>
        public static void closeDuraFile()
        {
            if (!duraFileOpen) return;

            duraFile.Write(Crlf, 0, Crlf.Length);
            duraFile.Write(Eot, 0, Eot.Length);
			duraFile.Write(Crlf, 0, Crlf.Length);
            
			EngrLogger.writeToColumns("Closing .dura File");
            EnqueueFileForXfer(duraFileName.ToString());
            
            duraFile.Close();
        }

        /// <summary>
        /// Close the Isus Data File
        /// </summary>
        public static void closeIsusFile()
        {
            if (!isusFileOpen) return;

            isusFile.Write(Crlf, 0, Crlf.Length);
            isusFile.Write(Eot, 0, Eot.Length);
            isusFile.Write(Crlf, 0, Crlf.Length);
            EngrLogger.writeToColumns("Closing .isus File");
            EnqueueFileForXfer(isusFileName.ToString());

            isusFile.Close();
        }

        /// <summary>
        /// Closes the Science file, and enqueues it for transmission. 
        /// </summary>
        public static void closeMsgFile()
        {
            if (!msgFileOpen) return;

            msgFile.Write(Crlf, 0, Crlf.Length);
            msgFile.Write(Encoding.UTF8.GetBytes(gpsPositionSB.ToString()), 0, gpsPositionSB.Length);
            msgFile.Write(Crlf, 0, Crlf.Length);
            writeMsgFooter();

            EngrLogger.writeToColumns("Closing .msg File");
            EnqueueFileForXfer(msgFileName.ToString());
            msgFile.Close();
        }

        private static bool logFileOpen { 
            get { 
                if (logFile == null) return false;
                return logFile.CanWrite;
            } 
        }

        private static bool msgFileOpen {
            get {
                if (msgFile == null) return false;
                return msgFile.CanWrite;
            }
        }
        
        private static bool isusFileOpen {
            get {
                if (isusFile == null) return false;
                return isusFile.CanWrite;
            }
        }

        private static bool duraFileOpen {
            get {
                if (duraFile == null) return false;
                return duraFile.CanWrite;
            }
        }

        

        public static void openNewLogFile()
        {
            if (logFileOpen)
                closeLogFile();

            logFileName.Clear();
            logFileName.Append(EngrLogger.fileNameBase);
            logFileName.Append(".log");
            try {
                logFile = new FileStream(logFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);
                if (!logFileOpen) {
                    sbTemp.Clear();
                    sbTemp.Append("[ERROR] opening log file");
                    EngrLogger.writeToColumns(sbTemp);
                }
            }
            catch {
                EngrLogger.writeToColumns("[ERROR] opening .log File");
            }
        }

        public static void closeLogFile()
        {
            if (logFile == null) return;
            if (!logFileOpen) return;
            
            EngrLogger.writeToColumns("Closing .log File");
            logFile.Close();
            EnqueueFileForXfer(logFileName.ToString());
        }

        /// <summary>
        /// Allow other objects to add lines to SciFile Footer
        /// </summary>

        private static StringBuilder msgFileFooter = new StringBuilder(1024);

        public static void appendToMsgFooter(StringBuilder newLine)
        {
            msgFileFooter.Append("# ");
            msgFileFooter.Append(newLine);
            msgFileFooter.Append("\r\n");
        }

        /// <summary>
        /// Write SciFile footer
        /// </summary>
        private static void writeMsgFooter()
        {
            EngrLogger.writeToColumns("Writing Footer to msg File");

            msgFile.Write(Encoding.UTF8.GetBytes(msgFileFooter.ToString()), 0, msgFileFooter.Length);
            msgFile.Flush();
        }

        /// <summary>
        /// Allow other objects to add lines to SciFile Header
        /// </summary>
        public static void appendToMsgHeader(StringBuilder newLine)
        {
            msgFileHeader.Append("# ");
            msgFileHeader.Append(newLine);
            msgFileHeader.Append(Literals.crlfString);
        }
        private static StringBuilder msgFileHeader = new StringBuilder(1024);

        /// <summary>
        /// Write out the header to file
        /// </summary>
        public static void writeMsgHeader()
        {
            EngrLogger.writeToColumns("Writing Header to msg File");

            if (msgFileHeader.Length == 0)
            {
                sbTemp.Clear().Append("CPF");
                appendToMsgHeader(sbTemp);
            }

            msgFile.Write(SafeEncoding.UTF8.GetBytes(msgFileHeader.ToString()), 0, msgFileHeader.Length);
            msgFile.Write(colNames, 0, colNames.Length);

            msgFileHeader.Clear();
            msgFile.Flush();
        }

        /// <summary>
        /// Append the cp data
        /// </summary>
        /// <param name="inBytes">byte array containing the segment of CP data to be copied into the file.</param>
        public static void appendCPData(byte[] inBytes)
        {
            sbTemp.Clear();
            sbTemp.Append(SafeEncoding.GetChars(inBytes));
            cpData.Append(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("Appending CP Data: ");
            sbTemp.Append(SafeEncoding.GetChars(inBytes));
            EngrLogger.writeToColumns(sbTemp.Replace(Literals.tabChar, Literals.commaChar));
        }

        private static StringBuilder cpData = new StringBuilder(2048);

        /// <summary>
        /// Write out all stored CP data to the file.
        /// </summary>
        public static void writeCPData()
        {
            EngrLogger.writeToColumns("Writing CP data to msg File");

            appendCPData(UTF8Encoding.UTF8.GetBytes("End of CP data\r\n\r\n"));

            msgFile.Write(UTF8Encoding.UTF8.GetBytes(cpData.ToString()), 0, cpData.Length);
            msgFile.Flush();
        }

        /// <summary>
        /// Sample types for byte array storage and output
        /// </summary>
        [Flags]
        public enum Instruments : uint
        {
            Ctd = 0x0001,
            Optode = 0x0002,
            Ph = 0x0004,
            NO3 = 0x0008,
            Flbb = 0x0010,
            Radiometer = 0x0020,
            Reserved1 = 0x0040,
            Reserved2 = 0x0080,
            Reserved3 = 0x0100,
            Reserved4 = 0x0200,
            Reserved5 = 0x0400,
            Reserved6 = 0x0800,
            Reserved8 = 0x1000,
            Reserved9 = 0x2000,
            Reserved10 = 0x4000,
            Reserved11 = 0x8000,
            AllInstruments = 0x3F, // modify when adding new instrument
            None = 0x0000,
        }

        /// <summary>
        /// Determine if an instrument should be sampled.
        /// </summary>
        /// <param name="instr"></param>
        /// <param name="instrList"></param>
        /// <returns></returns>
        public static bool sampling(Instruments instr)
        {
            return (instr & instrumentsToSample) > 0;
        }

        public static bool posted(Instruments instr)
        {
            return (instr & instrumentsPosted) > 0;
        }

        private static bool shouldPost(Instruments instr)
        {
            return ((instr & instrumentsToSample) > 0) && !posted(instr);
        }

        private static StringBuilder ctdSB = new StringBuilder(32);
        private static StringBuilder optodeSB = new StringBuilder(64);
        private static StringBuilder phSB = new StringBuilder(16);
        private static StringBuilder no3SB = new StringBuilder(16);
        private static StringBuilder flbbSB = new StringBuilder(16);
        private static StringBuilder radiometerSB = new StringBuilder(32);
        private static StringBuilder startStateSB = new StringBuilder(32);

        private static Instruments instrumentsToSample;
        private static Instruments instrumentsPosted;

        //private static bool gotCTD = false;
        //private static bool gotOptode = false;
        //private static bool gotPH = false;
        //private static bool gotNO3 = false;
        //private static bool gotFLBB = false;
        //private static bool gotRadiometer = false;

        //public static bool getCTD = false;
        //public static bool getOptode = false;
        //public static bool getPH = false;
        public static bool waitForCTDThenGetPH = false;
        //public static bool getNO3 = false;
        public static bool waitForCTDThenGetNO3 = false;
        //public static bool getFLBB = false;
        //public static bool getRadiometer = false;

        private static int missedCTD = 0;
        private static int missedOptode = 0;
        private static int missedPH = 0;
        private static int missedNO3 = 0;
        private static int missedFLBB = 0;
        private static int missedOCR = 0;

        //public static bool sampleInProgress = false;

        private static StringBuilder sampleStartTimeStamp = new StringBuilder(32);

        private static void resetSampleStrings()
        {
            ctdSB.Clear();
            ctdSB.Append("NaN\tNaN\tNaN");
            
            optodeSB.Clear();
            optodeSB.Append("NaN\tNaN\tNaN");

            phSB.Clear();
            phSB.Append("NaN");

            radiometerSB.Clear();
            radiometerSB.Append("NaN\tNaN\tNaN\tNaN");

            no3SB.Clear();
            no3SB.Append("NaN");

            flbbSB.Clear();
            flbbSB.Append("NaN\tNaN\tNaN");

        }

        //TODO P2 need to make sure SciLogger is not active in recovery mode
        public static void startNewSampleCycle(Instruments instrumentsList)
        {
            if ((SV.CurrentState == CPFStates.anchor) || (SV.CurrentState == CPFStates.park) ||
                (SV.CurrentState == CPFStates.ascend) || (SV.CurrentState == CPFStates.surfaceOps))
            {

                if (instrumentsPosted > 0)
                {
                    EngrLogger.writeToColumns("SciLogger is writing previous sample before starting new one");
                    writeSample();
                }

                instrumentsToSample = instrumentsList;
                instrumentsPosted = Instruments.None;

                EngrLogger.writeToColumns("Starting New SciLogger Sample Cycle");

                startStateSB.Clear();
                if (SV.CurrentState == CPFStates.anchor)
                    startStateSB.Append("AnchorPt:");
                else if (SV.CurrentState == CPFStates.park)
                    startStateSB.Append("ParkPt:");
                else if (SV.CurrentState == CPFStates.surfaceOps)
                    startStateSB.Append("AirCal:");
                else if (SV.CurrentState == CPFStates.ascend)
                    startStateSB.Append("AscendPt:");
                else if (SV.CurrentState == CPFStates.descend)
                    startStateSB.Append("DescendPt:");
                else
                    startStateSB.Append("UnknownPt:");

                if (sampling(Instruments.Ctd))
                {
                    ctdSB.Clear();
                    ctdSB.Append("NaN\tNaN\tNaN");
                    //Don't take a PTS sample in ascend mode because SBE41 is in CP mode and already outputting a PTS sample
                    if (!SV.InCPMode)
                    {
                        if (SV.CurrentState == CPFStates.surfaceOps)
                        {
                            ctd1.sendCRLF();
                            Thread.Sleep(1000);
                            EngrLogger.writeToColumns("Sci Logger sending FastPressure to CTD");
                            ctd1.sendFastPressure();
                        }
                        else if (ctd1.CommandModeTimerEnabled)
                        {
                            // This will flip a flag to request a PTS on the next inquiry to the CTD.
                            EngrLogger.Comment("Sci Logger requesting PTS on next sample");
                            ctd1.OnNextRequest(true);
                        }
                        else
                        {
                            ctd1.sendCRLF();
                            Thread.Sleep(1000);
                            EngrLogger.writeToColumns("Sci Logger sending PTS to CTD");
                            ctd1.sendPTS();
                        }
                    }
                }


                if (sampling(Instruments.Optode))
                {
                    optodeSB.Clear();
                    optodeSB.Append("NaN\tNaN\tNaN");
                    EngrLogger.writeToColumns("Sci Logger sending sample Optode");
                    optode1.doSample();
                }


                //TODO P2 need to create cases for pH only and NO3 only
                if (sampling(Instruments.Ph) || sampling(Instruments.NO3))
                {
                    phSB.Clear();
                    phSB.Append("NaN");
                    no3SB.Clear();
                    no3SB.Append("NaN");
                    waitForCTDThenGetPH = true;
                    waitForCTDThenGetNO3 = true;
                    EngrLogger.writeToColumns("Sci Logger set wait for CTD then sample pH and NO3 true");

                }
                else
                {
                    waitForCTDThenGetPH = false;
                    waitForCTDThenGetNO3 = false;
                }

                if (sampling(Instruments.Flbb))
                {
                    flbbSB.Clear();
                    flbbSB.Append("NaN\tNaN\tNaN");
                    EngrLogger.writeToColumns("Sci Logger sending sample FLBB");
                    flbb1.sendRunCmd();
                }

                if (sampling(Instruments.Radiometer))
                {
                    radiometerSB.Clear();
                    radiometerSB.Append("NaN\tNaN\tNaN\tNaN");
                    EngrLogger.writeToColumns("Sci Logger sending sample Radiometer");
                    radiometer1.sendPollSample();
                }

                sampleStartTimeStamp.Clear();
                sampleStartTimeStamp.Append(DateTime.Now.ToString("yyyy-MMM-ddTHH:mm:ssZ"));
            }
            else
            {
                EngrLogger.Comment("Tried to startNewSampleCycle in wrong state");
            }

            return;
        }

        public static void endSampleCycle()     //TODO P1 Check if writeSample does exactly what this does only more correctly
        {
            EngrLogger.writeToColumns("Ending SciLogger Sample Cycle");

            if (instrumentsToSample > 0) {
                EngrLogger.writeToColumns("SciLogger endSampleCycle is writing samples");
                writeSample();
                
            }

            instrumentsToSample = Instruments.None;
            instrumentsPosted = Instruments.None;
            waitForCTDThenGetPH = false;
            waitForCTDThenGetNO3 = false;

        }

        /// <summary>
        /// Write key science data to file.
        /// </summary>
        public static void writeSample()
        {
            sbTemp.Clear();
            sbTemp.Append(startStateSB).Append(Literals.tabChar);
            sbTemp.Append(sampleStartTimeStamp).Append(Literals.tabChar);
            sbTemp.Append(ctdSB).Append(Literals.tabChar);
            sbTemp.Append(optodeSB).Append(Literals.tabChar);
            sbTemp.Append(phSB).Append(Literals.tabChar);
            sbTemp.Append(no3SB).Append(Literals.tabChar);
            sbTemp.Append(flbbSB).Append(Literals.tabChar);
            sbTemp.Append(radiometerSB);
            sbTemp.Append(Literals.crlfString);

            msgFile.Write(Encoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
            msgFile.Flush();

            //This is here to check the optode temperature time response
            EngrLogger.Comment("Sending 2nd Optode.doSample to test time response");
            optode1.doSample();

            resetSampleStrings();

            //Write out to Engrlogger
            sbTemp2.Clear();
            sbTemp2.Append(sbTemp);
            sbTemp.Clear();
            sbTemp.Append("Wrote to msg file: ");
            sbTemp.Append(sbTemp2);
            EngrLogger.writeToColumns(sbTemp.Replace(Literals.tabChar, Literals.commaChar));

            //TODO P2 Eventually need to do something when missedXXX exceeds some value
            //Really need to do this inside a well executed commmand processor
            //object instantiated in each instrument

            if (sampling(Instruments.Ctd) && !posted(Instruments.Ctd)) {
                missedCTD++;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] Missed CTD message ");
                sbTemp.Append(missedCTD);
                EngrLogger.writeToColumns(sbTemp);
            }

            if (sampling(Instruments.Optode) && !posted(Instruments.Optode)) {
                missedOptode++;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] Missed Optode message ");
                sbTemp.Append(missedOptode);
                EngrLogger.writeToColumns(5, sbTemp);
            }

            if (sampling(Instruments.Ph) && !posted(Instruments.Ph)) {
                missedPH++;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] Missed PH message ");
                sbTemp.Append(missedPH);
                EngrLogger.writeToColumns(5, sbTemp);
            }

            if (sampling(Instruments.NO3) && !posted(Instruments.NO3)) {
                missedNO3++;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] Missed NO3 message ");
                sbTemp.Append(missedNO3);
                EngrLogger.writeToColumns(5, sbTemp);
            }

            if (sampling(Instruments.Flbb) && !posted(Instruments.Flbb)) {
                missedFLBB++;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] Missed FLBB message ");
                sbTemp.Append(missedFLBB);
                EngrLogger.writeToColumns(5, sbTemp);
            }

            if (sampling(Instruments.Radiometer) && !posted(Instruments.Radiometer)) {
                missedOCR++;
                sbTemp.Clear();
                sbTemp.Append("[ERROR] Missed OCR message ");
                sbTemp.Append(missedOCR);
                EngrLogger.writeToColumns(5,  sbTemp);
            }

            instrumentsToSample = Instruments.None;
            instrumentsPosted = Instruments.None;
        }

        private static float postedCTDPressure = float.MaxValue;
        private static float postedCTDTemperature = float.MaxValue;
        private static float postedCTDSalinity = float.MaxValue;
        private static DateTime postedCTDTimestamp = new DateTime(2000, 1, 1);

        public static void setCTDValues(CTD1.PTSRecord ptsRecord)
        {
            postedCTDPressure = ptsRecord.pressure;
            postedCTDTemperature = ptsRecord.temperature;
            postedCTDSalinity = ptsRecord.salinity;
            postedCTDTimestamp = ptsRecord.timeStamp;
        }

        private static byte[] returnBytes = new byte[32];

        public static void postSample(Instruments instrumentNumber, byte[] sampleBytes)
        {
            switch (instrumentNumber) {
                case Instruments.Ctd:
                    #region format CTD message
                    if (waitForCTDThenGetNO3) {
                        EngrLogger.writeToColumns("SciLogger: Setting get NO3 true");
                        waitForCTDThenGetNO3 = false;
                    }
                    if (waitForCTDThenGetPH) {
                        EngrLogger.writeToColumns("SciLogger: Setting get PH true");
                        waitForCTDThenGetPH = false;
                    }

                    if (sampling(Instruments.Ph) || sampling(Instruments.NO3)) {
                        EngrLogger.writeToColumns("SciLogger: Setting MSC CTD values");
                        msc1.SetCTDValues(Utils.dateTimeToPosixTime(postedCTDTimestamp), postedCTDTemperature,
                                            postedCTDSalinity, postedCTDPressure);
                        EngrLogger.writeToColumns("SciLogger: Starting PHandNO3 sequence");
                        msc1.startT1T2SampleSequence();
                        msc1.SequenceT1T2Samples();
                    }

                    ctdSB.Clear();
                    ctdSB.Append(SafeEncoding.GetChars(sampleBytes));
                    instrumentsPosted |= Instruments.Ctd;
                    break;
                    #endregion
                case Instruments.Flbb:
                    #region format FLBB message
                    flbbSB.Clear();

                    sbTemp.Clear();
                    sbTemp.Append(SafeEncoding.GetChars(sampleBytes));
                    //Debug.Print("FLBB post: " + sbTemp.ToString());


                    //Get FL signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 44, 3, ref returnBytes);
                        flbbSB.Append(SafeEncoding.GetChars(returnBytes));
                        instrumentsPosted |= Instruments.Flbb;
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing flbb Topt");
                    }
                    flbbSB.Append(Literals.tabChar);

                    //Get BB signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 44, 5, ref returnBytes);
                        flbbSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing flbb TCPhase");
                    }
                    flbbSB.Append(Literals.tabChar);

                    //Get Temp signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 44, 6, ref returnBytes);
                        flbbSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing flbb TCPhase");
                    }

                    //Debug.Print("new flbbSB: " + flbbSB.ToString());
                    break;
                    #endregion
                case Instruments.NO3:
                    #region format NO3 message
                    no3SB.Clear();
                    no3SB.Append(SafeEncoding.GetChars(sampleBytes));
                    instrumentsPosted |= Instruments.NO3;
                    break;
                    #endregion
                case Instruments.Radiometer:
                    #region format OCR message
                    radiometerSB.Clear();

                    sbTemp.Clear();
                    sbTemp.Append(SafeEncoding.GetChars(sampleBytes));
                    //Debug.Print("ocr post: " + sbTemp.ToString());

                    //Get CH1 signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 9, 1, ref returnBytes);
                        radiometerSB.Append(SafeEncoding.GetChars(returnBytes));
                        instrumentsPosted |= Instruments.Radiometer;
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing ocr Ch1");
                    }
                    radiometerSB.Append(Literals.tabChar);

                    //Get CH2 signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 9, 2, ref returnBytes);
                        radiometerSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing ocr Ch2");
                    }
                    radiometerSB.Append(Literals.tabChar);

                    //Get CH3 signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 9, 3, ref returnBytes);
                        radiometerSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing ocr Ch3");
                    }
                    radiometerSB.Append(Literals.tabChar);

                    //Get CH4 signal
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 9, 4, ref returnBytes);
                        radiometerSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing ocr Ch4");
                    }

                    //Debug.Print("new ocrSB: " + ocrSB.ToString());

                    break;
                    #endregion
                case Instruments.Optode:
                    #region format Optode message
                    optodeSB.Clear();

                    sbTemp.Clear();
                    sbTemp.Append(SafeEncoding.GetChars(sampleBytes));
                    //Debug.Print("Optode post: " + sbTemp.ToString());

                    //Get Topt
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 44, 3, ref returnBytes);
                        optodeSB.Append(SafeEncoding.GetChars(returnBytes));
                        instrumentsPosted |= Instruments.Optode;
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing optode Topt");
                    }
                    optodeSB.Append(Literals.tabChar);

                    //Get TCPhase
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 44, 5, ref returnBytes);
                        optodeSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing optode TCPhase");
                    }
                    optodeSB.Append(Literals.tabChar);

                    //Get C2RPhase
                    try {
                        Array.Clear(returnBytes, 0, returnBytes.Length);
                        Utils.getField(sampleBytes, 44, 7, ref returnBytes);
                        optodeSB.Append(SafeEncoding.GetChars(returnBytes));
                    }
                    catch {
                        EngrLogger.writeToColumns("[ERROR] parsing optode C2RPhase");
                    }

                    //Debug.Print("new optodeSB: " + optodeSB.ToString());

                    break;
                    #endregion
                case Instruments.Ph:
                    #region format pH message
                    phSB.Clear();
                    phSB.Append(SafeEncoding.GetChars(sampleBytes));
                    instrumentsPosted |= Instruments.Ph;
                    break;
                    #endregion
                default:
                    throw new Exception("Unknown sample type sent");
            }

            //Check to see if everything that was supposed to post posted then write the sample
            if (instrumentsToSample == instrumentsPosted) {
                EngrLogger.writeToColumns("Everything posted, writing sample");
                writeSample();
            }
        }

        private static TimeSpan isDoneStartTime = new TimeSpan();
        private static TimeSpan isDoneWaitSpan = new TimeSpan(0, 1, 0);     //This should be set to the maximum time required to sample both pH and NO3 plus some margin
        private static bool firstCheck = true;

        public static int isPHandNO3Done()
        {

            if (firstCheck) {
                isDoneStartTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime();
                firstCheck = false;
                EngrLogger.writeToColumns("SciLogger isPHandNO3Done: first check");
            }

            if (posted(Instruments.Ph) && posted(Instruments.NO3)) {
                firstCheck = true;
                EngrLogger.writeToColumns("SciLogger isPHandN03Done: got PH and NO3");
                return 1;
            }

            if ((Microsoft.SPOT.Hardware.Utility.GetMachineTime() - isDoneStartTime) > isDoneWaitSpan) {
                //TODO P2 probably need an error handler here
                EngrLogger.writeToColumns("SciLogger isPHandNO3Done: timed out");
                firstCheck = true;
                return -1;
            }

            return 0;
        }

        public static void writeMsgComment(StringBuilder inComment)
        {
            if (!msgFileOpen) return;
            sbTemp.Clear();
            sbTemp.Append("$ ");
            sbTemp.Append(inComment);
            sbTemp.Append("\r\n");
            msgFile.Write(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
            msgFile.Flush();
        }

        public static void writeToLog(StringBuilder inLine)
        {
            if (!logFileOpen) return;
            sbTemp.Clear();
            sbTemp.Append(inLine);
            sbTemp.Append("\r\n");
            logFile.Write(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
            logFile.Flush();
        }

        public static void printLastIsusDuraFiles()
        {
            var reopenDuraFile = duraFileOpen;
            var reopenIsusFile = isusFileOpen;
            
            closeDuraFile();
            closeIsusFile();

            var duraFileStream = new StreamReader(duraFileName.ToString());

            Debug.Print("DURA FILE:");
            string fileLine;
            while ((fileLine = duraFileStream.ReadLine()) != null)
                Debug.Print(fileLine);

            Debug.Print("ISUS FILE:");
            var isusFileStream = new StreamReader(isusFileName.ToString());
            while ((fileLine = isusFileStream.ReadLine()) != null)
                Debug.Print(fileLine);
        }
        /// <summary>
        /// Print the current science file to the debugger
        /// </summary>
        public static void printLastMsgFile()
        {
            StreamReader sciFileStream;
            string fileLine;
            bool reopenFile;

            if (msgFileOpen) {
                closeMsgFile();
                reopenFile = true;
            }
            else
                reopenFile = false;

            sciFileStream = new StreamReader(msgFileName.ToString());

            while ((fileLine = sciFileStream.ReadLine()) != null)
                Debug.Print(fileLine);

            sciFileStream.Close();

            if (reopenFile)
                msgFile = new FileStream(msgFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);
        }

        /// <summary>
        /// Enqueue's a filename in the FIFO queue that the rudics thread watches and attempts to upload from.  
        /// </summary>
        /// <param name="name">Name Must be full path, and valid, file should not be open.</param>
        public static void EnqueueFileForXfer(string name)
        {
            _qRecord.byteArray = Encoding.UTF8.GetBytes(name);
            _qRecord.Update();

            // Add filename to transfer buffer
            _fileQ.Enqueue(_qRecord);

            sbTemp.Clear().Append("Enqueueing File for Transfer: ").Append(name);
            EngrLogger.writeToColumns(sbTemp);
        }

        /// <summary>
        /// Returns the last file closed as a QRecord, with closing time in the date field.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown when attempting to dequeue from empty queue</exception>
        public static QRecord DequeueFileName
        {
            get { return _fileQ.Dequeue(); }
        }

        private static bool numberOfCharsInByteArray(int n, char testChar, byte[] byteArray)
        {
            int numChars = 0;
            int index = -1;

            for (int i = 0; i < n; i++) {
                index = Array.IndexOf(byteArray, (byte)testChar, index + 1);
                if (index < 0)
                    break;
                else
                    numChars++;
            }
            if (numChars >= n)
                return true;
            else
                return false;
        }

        public static void updateLatLon(gpsUBlox.GPSPosition position)
        {
            double lat = double.NaN;
            double lon = double.NaN;

            gpsPositionSB.Clear();
            gpsPositionSB.Append("# GPS fix obtained in (seconds) \t");
            gpsPositionSB.Append(position.gpsTTFF.ToString());
            gpsPositionSB.Append(Literals.crlfString);

            gpsPositionSB.Append("#Fix: \tLon\tLat\tmm/dd/yyyy\tHH:mm:sssZ\tnumSats\tnmeaLon\t\tnmeaLat\t\tgpsTimeOfFix");
            gpsPositionSB.Append(Literals.crlfString);

            gpsPositionSB.Append("Fix:");
            gpsPositionSB.Append(Literals.tabChar);
            lon = convertLonToDecDeg(position.lonSB.ToString());
            gpsPositionSB.Append(lon.ToString("f8"));

            gpsPositionSB.Append(Literals.tabChar);
            lat = convertLatToDecDeg(position.latSB.ToString());
            gpsPositionSB.Append(lat.ToString("f8"));

            gpsPositionSB.Append(Literals.tabChar);
            gpsPositionSB.Append(position.cpfDateTimeOfFix.ToString("MM/dd/yyyy\tHH:mm:ss"));

            gpsPositionSB.Append(Literals.tabChar);
            gpsPositionSB.Append(position.numSats.ToString());

            gpsPositionSB.Append(Literals.tabChar);
            gpsPositionSB.Append(position.lonSB);

            gpsPositionSB.Append(Literals.tabChar);
            gpsPositionSB.Append(position.latSB);

            gpsPositionSB.Append(Literals.tabChar);
            gpsPositionSB.Append(position.gpsTimeOfFix);
        }

        private static double convertLatToDecDeg(string sLat)
        {
            int decPtIndex = 0;
            double lat = double.NaN;
            double deg;
            double min;

            //TODO P1 need to make sure pubx always has 2 digits of minutes
            try
            {
                decPtIndex = sLat.IndexOf('.');
                min = double.Parse(sLat.Substring(decPtIndex - 2, sLat.Length - decPtIndex));
                deg = double.Parse(sLat.Substring(0, decPtIndex - 2));
                lat = deg + min / 60.0;
                if (sLat.Substring(sLat.Length - 1, 1) == "S")
                    lat = -1 * lat;
            }
            catch
            {
                lat = double.NaN;
            }

            return lat;
        }

        private static double convertLonToDecDeg(string sLon)
        {
            int decPtIndex = 0;
            double lon = double.NaN;
            double deg;
            double min;

            //TODO P1 need to make sure pubx always has 2 digits of minutes
            try
            {
                decPtIndex = sLon.IndexOf('.');
                min = double.Parse(sLon.Substring(decPtIndex - 2, sLon.Length - decPtIndex));
                deg = double.Parse(sLon.Substring(0, decPtIndex - 2));
                lon = deg + min / 60.0;
                if (sLon.Substring(sLon.Length - 1, 1) == "W")
                    lon = -1 * lon;
            }
            catch 
            {
                lon = double.NaN;
            }

            return lon;
        }

        /// <summary>
        /// Write a line to the .isus file, assumably some configuration information
        /// </summary>
        /// <param name="recordByteArray"></param>
        public static void writeToIsusFile(byte[] recordByteArray, int length=0)
        {
            if (!isusFileOpen) return;

            //Find first null
            if (length == 0) length = Array.IndexOf(recordByteArray, 0);
            if (length <= 0) return;

            var offset = (recordByteArray[0] == 0 ? 1 : 0);

            if (offset == 1) length--;
            isusFile.Write(recordByteArray, offset, length);

        }

        /// <summary>
        /// Write a line to the .dura file, assumably some configuration information
        /// </summary>
        /// <param name="recordByteArray"></param>
        public static void writeToDuraFile(byte[] recordByteArray, int length=0)
        {
            if (!duraFileOpen) return;
            if (length == 0) length = Array.IndexOf(recordByteArray, 0);
            if (length <= 0) return;

            var offset = (recordByteArray[0] == 0) ? 1 : 0;
            if (offset == 1) length--;
            duraFile.Write(recordByteArray, offset, length);
        }

        public static void WriteErrorCount(uint errorCount, MSC.SampleMode mode)
        {
            switch (mode)
            {
                case MSC.SampleMode.DuraFET:
                    if (!duraFileOpen) return;
                    if (errorCount == 0)
                        sbTemp.Clear().Append("# No error records to log.\r\n");
                    else
                        sbTemp.Clear().Append("Count of DURA errors: ").Append(errorCount).Append(Literals.crlfString);
                    duraFile.Write(SafeEncoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
                    return;
                case MSC.SampleMode.ISUS:
                    if (!isusFileOpen) return;
                    if (errorCount == 0)
                        sbTemp.Clear().Append("# No error records to log.\r\n");
                    else
                        sbTemp.Clear().Append("Count of ISUS errors: ").Append(errorCount).Append(Literals.crlfString);
                    isusFile.Write(SafeEncoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
                    return;

            }
        }

        public static void writeErrorRecord(byte[] bytes, MSC.SampleMode mode)
        {
            switch (mode)
            {
                case MSC.SampleMode.DuraFET:
                    if (!duraFileOpen) return;
                    sbTemp.Clear().Append(((UTF8Encoding) SafeEncoding.UTF8).GetChars(bytes));
                    duraFile.Write(SafeEncoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
                    return;
                case MSC.SampleMode.ISUS:
                    if (!isusFileOpen) return;
                    sbTemp.Clear().Append(((UTF8Encoding) SafeEncoding.UTF8).GetChars(bytes));
                    isusFile.Write(SafeEncoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length);
                    return;

            }
        }

        public static void clearFileQueue()
        {
            if (_fileQ == null) return;
            _fileQ.Clear();
        }
    }
}
