//#define USB_STORAGE          //enable if using USB memory

using System;
using System.Text;
using System.IO;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.IO;

using GHI.Processor;
using GHI.IO.Storage;

#if(USB_STORAGE)
using GHI.Usb;
using GHI.Usb.Host;
#endif

namespace CPF
{
    public static class EngrLogger
    {

        private static StringBuilder columnNames = new StringBuilder();

        private static SDCard sdCard;
        public static VolumeInfo sdVol;
        private static FileStream logFile;

        private static StringBuilder sbTemp = new StringBuilder(4096);
        private static StringBuilder sbWrite = new StringBuilder(4096);
        private static StringBuilder sbPrefix = new StringBuilder(4096);

        private static object loggerLock = new object();

        private static byte[] writeArray = new byte[4096];

        private static DateTime timeNow;
        private static char token = '\t';

        public static bool fileOpen { get; private set; }
        private static bool SDMounted = false;
        private static bool USBMounted = false;

        public enum ColumnNums : int
        {
            //First the prefix for all records
            dateTime = 0,
            state,
            comment,
            //Then the verbosity level 0 stuff
            pressure,
            bellowsPosition,
            batteryBusVolts,
            //CTD Stuff
            CTDTemp, CTDSalinity,
            //PTH Stuff
            LPS331Pressure, LPS331Temperature, SHT21Humidity, SHT21Temperature,
            //Science data
            FLBB,
            TOpt, TPhase, RPhase,
            NO3,
            pH,
            //CTD stuff
            SBE41Pressure, SBE41LPFPressure, SBE41LPFVelocity, SBE41DiffVelocity, SBE41Slope, CTDSamplePeriod,
            //PID stuff
            PID_SP, PID_PV, PID_y1, PID_y2, PID_I, PID_v, PID_u, PID_uCPS, PID_h,
            //ADC Stuff
            batteryBusAmps, Amps12V, Amps33V,
            debug,
            //GPS Stuff
            GPS,
            //Iridium Modem Stuff
            IModem,
            lastColumn,
        }

        private static StringBuilder[] columnName = new StringBuilder[(int)ColumnNums.lastColumn];

#if(USB_STORAGE)
        private static AutoResetEvent usbInsertEvent = new AutoResetEvent(false);
        private static MassStorage usbStorage;
#endif

        private static Register resetControllerStatus = new Register(0xFFFFFE04);

        public static bool init()
        {
            for (int i = 0; i < (int)ColumnNums.lastColumn; i++)
                columnName[i] = new StringBuilder(32);

            columnName[(int)ColumnNums.dateTime].Append("$\tDate\tTime");
            columnName[(int)ColumnNums.state].Append("State");
            columnName[(int)ColumnNums.comment].Append("Comment");
            columnName[(int)ColumnNums.pressure].Append("Pressure");
            columnName[(int)ColumnNums.bellowsPosition].Append("Bel Pos");
            columnName[(int)ColumnNums.batteryBusVolts].Append("Batt Volt");
            columnName[(int)ColumnNums.GPS].Append("GPS");
            columnName[(int)ColumnNums.IModem].Append("Irid Modem");
            columnName[(int)ColumnNums.LPS331Pressure].Append("LPS331 Press");
            columnName[(int)ColumnNums.LPS331Temperature].Append("LPS311 Temp");
            columnName[(int)ColumnNums.SHT21Humidity].Append("SHT21 Humid");
            columnName[(int)ColumnNums.SHT21Temperature].Append("SHT21 Temp");
            columnName[(int)ColumnNums.CTDTemp].Append("CTD Temp");
            columnName[(int)ColumnNums.CTDSalinity].Append("CTD Sal");
            columnName[(int)ColumnNums.FLBB].Append("FLBB");
            columnName[(int)ColumnNums.TOpt].Append("Topt");
            columnName[(int)ColumnNums.TPhase].Append("Tphase");
            columnName[(int)ColumnNums.RPhase].Append("Rphase");
            columnName[(int)ColumnNums.NO3].Append("NO3");
            columnName[(int)ColumnNums.pH].Append("pH");
            columnName[(int)ColumnNums.SBE41Pressure].Append("Raw Press");
            columnName[(int)ColumnNums.SBE41LPFPressure].Append("LPF Press");
            columnName[(int)ColumnNums.SBE41LPFVelocity].Append("LPF Vel");
            columnName[(int)ColumnNums.SBE41DiffVelocity].Append("Diff Vel");
            columnName[(int)ColumnNums.SBE41Slope].Append("Slope of LPF Press.");
            columnName[(int)ColumnNums.CTDSamplePeriod].Append("CTD Sample Period");
            columnName[(int)ColumnNums.PID_SP].Append("PID SP");
            columnName[(int)ColumnNums.PID_PV].Append("PID PV");
            columnName[(int)ColumnNums.PID_y1].Append("PID Y1");
            columnName[(int)ColumnNums.PID_y2].Append("PID Y2");
            columnName[(int)ColumnNums.PID_I].Append("PID I");
            columnName[(int)ColumnNums.PID_v].Append("PID v");
            columnName[(int)ColumnNums.PID_u].Append("PID u");
            columnName[(int)ColumnNums.PID_uCPS].Append("PID uCPS");
            columnName[(int)ColumnNums.PID_h].Append("PID h");
            columnName[(int)ColumnNums.batteryBusAmps].Append("Batt Amps");
            columnName[(int)ColumnNums.Amps12V].Append("12V Amps");
            columnName[(int)ColumnNums.Amps33V].Append("3.3V Amps");
            columnName[(int)ColumnNums.debug].Append("Debug");

            columnNames.Clear();
            for (int i = 0; i < (int)ColumnNums.lastColumn; i++)
            {
                columnNames.Append(columnName[i]);
                columnNames.Append("\t");
            }
            columnNames.Append("\r\n");

            for (int i = 0; i < sbFileNames.Length; i++)
                sbFileNames[i] = new StringBuilder(128);

#if(USB_STORAGE)
            try
            {
                RemovableMedia.Insert += new InsertEventHandler(usbRemovableMedia_Insert);
                RemovableMedia.Eject += new EjectEventHandler(usbRemovableMedia_Eject);

                Controller.MassStorageConnected += (sender, massStorage) =>
                {
                    usbStorage = massStorage;
                    usbStorage.Mount();  // fires the insert event when finished
                };
                Controller.Start();

                usbInsertEvent.WaitOne(); // yield here until mounting and initializing is finished

                sdVol = new VolumeInfo("USB");
                USBMounted = true;
            }
            catch
            {
                USBMounted = false;
                sbTemp.Clear();
                sbTemp.Append("\r\n***Exception mounting USB card\r\n");
                Debug.Print(sbTemp.ToString());
                Program.btConsole.SendLine(sbTemp);
            }
#else
            try
            {
                sdCard = new SDCard();
                sdCard.Mount();
                sdVol = new VolumeInfo("SD");
                SDMounted = true;
            }
            catch
            {
                SDMounted = false;
                sbTemp.Clear();
                sbTemp.Append("\r\n***Exception mounting SD card\r\n");
                Debug.Print(sbTemp.ToString());
                Program.btConsole.SendLine(sbTemp);
            }
#endif

            if (SDMounted || USBMounted)
            {
                openFile();

                uint resetControllerStatusValue = resetControllerStatus.Value;
                sbTemp.Clear();
                sbTemp.Append("Reset Controller Status Register Value = ");
                sbTemp.Append(resetControllerStatusValue.ToString());
                writeToColumns(sbTemp);

                sbTemp.Clear();
                sbTemp.Append("File Name = ");
                sbTemp.Append(fileName.ToString());
                writeToColumns(sbTemp);

                sbTemp.Clear();
                sbTemp.Append("Volume Total Size:     ");
                sbTemp.Append(sdVol.TotalSize.ToString());
                writeToColumns(sbTemp);

                sbTemp.Clear();
                sbTemp.Append("Volume Free Space:     ");
                sbTemp.Append(sdVol.TotalFreeSpace.ToString());
                writeToColumns(sbTemp);
            }

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID K = ");
            sbTemp.Append(configFile.KPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID Ti = ");
            sbTemp.Append(configFile.TiPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID Td = ");
            sbTemp.Append(configFile.TdPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID Tf = ");
            sbTemp.Append(configFile.TfPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID Tt = ");
            sbTemp.Append(configFile.TtPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID B = ");
            sbTemp.Append(configFile.BPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID uLow = ");
            sbTemp.Append(configFile.uLowPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID uHigh = ");
            sbTemp.Append(configFile.uHighPlatVel.ToString());
            writeToColumns(sbTemp);

            sbTemp.Clear();
            sbTemp.Append("PlatVel PID h = ");
            sbTemp.Append(configFile.hPlatVel.ToString());
            writeToColumns(sbTemp);

            if (SDMounted || USBMounted)
                return (true);
            else
                return (false);
        }

        private static int lastValueIndex = 0;
        private static int columnNum = 0;
        private static int valueNum = 0;
        private static StringBuilder sbTemp2 = new StringBuilder(512);

        //If only one entry is in value array passed to his method, it gets treated as a comment and this method prepends date, time and state
        //All CR/LF are removed from the data until just before writing to SD card
        public static void writeToColumns(params object[] values)
        {
            lock (loggerLock)
            {
                timeNow = DateTime.Now;
                sbWrite.Clear();
                sbWrite.Append("$");
                sbWrite.Append(token);

                if (values.Length == 1)
                {
                    sbWrite.Append(timeNow.ToString("dd-MMM-yyyy"));
                    sbWrite.Append(token);
                    sbWrite.Append(timeNow.ToString("HH:mm:ss.fff"));
                    sbWrite.Append(token);
                    sbWrite.Append(Program.cpfState[(int)Program.CurrentState].stateName);
                    sbWrite.Append(token);
                    sbWrite.Append(values[0]);
                }
                else
                {
                    lastValueIndex = values.Length - 1;

                    for (columnNum = 0; columnNum < (int)ColumnNums.lastColumn; columnNum++)
                    {
                        for (valueNum = 0; valueNum < lastValueIndex; valueNum = valueNum + 2)
                        {
                            if ((int)values[valueNum] == columnNum)
                            {
                                //TODO need to do a better job formatting values to minimize length
                                if (columnNum == (int)ColumnNums.dateTime)
                                {
                                    timeNow = (DateTime)values[valueNum + 1];
                                    sbWrite.Append(timeNow.ToString("dd-MMM-yyyy"));
                                    sbWrite.Append(token);
                                    sbWrite.Append(timeNow.ToString("HH:mm:ss.fff"));
                                }
                                else if (columnNum == (int)ColumnNums.state)
                                    sbWrite.Append(Program.cpfState[(int)Program.CurrentState].stateName);
                                else if (columnNum == (int)ColumnNums.comment)
                                    sbWrite.Append(values[valueNum + 1]);
                                else if ((columnNum == (int)ColumnNums.CTDSalinity) || (columnNum == (int)ColumnNums.TOpt)
                                        || (columnNum == (int)ColumnNums.IModem) || (columnNum == (int)ColumnNums.GPS)
                                        || (columnNum == (int)ColumnNums.FLBB))
                                {
                                    sbTemp.Clear();
                                    try
                                    {
                                        sbTemp.Append(UTF8Encoding.UTF8.GetChars((byte[])values[valueNum + 1]));
                                        sbTemp.Replace('\r', ' ');
                                        sbTemp.Replace('\n', ' ');
                                    }
                                    catch
                                    {
                                        sbTemp2.Clear();
                                        sbTemp2.Append("EngrLogger /r/n .Replace error location 1 string: ");
                                        sbTemp2.Append(sbTemp);
                                        EngrLogger.writeToColumns(sbTemp2);
                                    }
                                    sbWrite.Append(sbTemp);
                                }
                                else
                                    sbWrite.Append(((double)values[valueNum + 1]).ToString("f6"));
                                break;
                            }
                        }
                        sbWrite.Append(token);
                    }
                }

                if (SDMounted || USBMounted)
                {
                    try
                    {                    
                    sbWrite.Replace('\r', ' ');
                    sbWrite.Replace('\n', ' ');
                    }
                    catch
                    {
                        sbTemp.Clear();
                        sbTemp.Append("EngrLogger /r/n .Replace error location 2 string: ");
                        sbTemp.Append(sbWrite);
                        EngrLogger.writeToColumns(sbTemp);
                    }
                    sbWrite.Append("\r\n");

                    Array.Clear(writeArray, 0, writeArray.Length);
                    try
                    {
                        Array.Copy(UTF8Encoding.UTF8.GetBytes(sbWrite.ToString()), writeArray, sbWrite.Length);
                        logFile.Write(writeArray, 0, sbWrite.Length);
                    }
                    catch
                    {
                        sbTemp.Clear();
                        sbTemp.Append("EngrLogger /r/n .Replace error location 3 string: ");
                        sbTemp.Append(sbWrite);
                        EngrLogger.writeToColumns(sbTemp);
                    }
                }
                else
                {
                    sbTemp.Clear();
                    sbTemp.Append("No SD: ");
                    sbTemp.Append(sbWrite);
                    sbWrite.Clear();
                    sbWrite.Append(sbTemp);
                }
                Program.btConsole.SendLine(sbWrite);
            }
            //Clear any terminating CR or LF since Debug.Print adds a CR/LF and TerraTerm should be set up to add a CR/LF
            try
            {
                sbWrite.Replace('\r', (char)32);
                sbWrite.Replace('\n', (char)32);
            }
            catch
            {
                sbTemp.Clear();
                sbTemp.Append("EngrLogger /r/n .Replace error location 3 string: ");
                sbTemp.Append(sbWrite);
                EngrLogger.writeToColumns(sbTemp);
            }
            Debug.Print(sbWrite.ToString());
        }

        private static void printVolInfo()
        {
            sbTemp.Clear();
            sbTemp.Append("Volume Total Size:     ");
            sbTemp.Append(sdVol.TotalSize.ToString());
            writeToColumns(ColumnNums.comment, sbTemp);

            sbTemp.Clear();
            sbTemp.Append("Volume Free Space:     ");
            sbTemp.Append(sdVol.TotalFreeSpace.ToString());
            writeToColumns(ColumnNums.comment, sbTemp);
        }

        private const int numFilesToGet = 20;
        public static int numFilesOnDisk = 0;
        public static StringBuilder[] sbFileNames = new StringBuilder[numFilesToGet];

        public static void getDirectory()
        {
            String[] fileNames = Directory.GetFiles(sdVol.RootDirectory);

            sbTemp.Clear();
            sbTemp.Append("Processing GETDIR Console message");
            EngrLogger.writeToColumns(sbTemp);

            numFilesOnDisk = Directory.GetFiles(sdVol.RootDirectory).Length;

            int fileID = numFilesToGet - 1;

            int lastFile = numFilesOnDisk - numFilesToGet;
            if (lastFile < 0)
                lastFile = 0;

            if (numFilesOnDisk > 0)
            {
                for (int i = lastFile; i < numFilesOnDisk; i++)
                {
                    try
                    {
                        fileID = numFilesOnDisk - (i + 1);
                        FileInfo fileInfo = new FileInfo(fileNames[i]);
                        sbFileNames[fileID].Clear();
                        sbFileNames[fileID].Append(fileNames[i]);
                        sbTemp.Clear();
                        sbTemp.Append("File ID = " + fileID.ToString()
                                    + " File Name = " + fileInfo.Name
                                     + "  Creation Date = " + fileInfo.CreationTime.ToString()
                                    + "  Length = " + fileInfo.Length.ToString()
                                    + "  FileNum = " + i.ToString());
                        Debug.Print(sbTemp.ToString());
                        sbTemp.AppendLine();
                        Program.btConsole.SendLine(sbTemp);

                        fileID--;
                    }
                    catch
                    {
                        EngrLogger.writeToColumns("Error in getDir get FileInfo");
                    }
                }
            }
            else
            {
                sbTemp.Clear();
                sbTemp.Append("No files on disk!");
                Debug.Print(sbTemp.ToString());
                sbTemp.AppendLine();
                Program.btConsole.SendLine(sbTemp);
            }
        }

        public static void closeFile()
        {
            if (fileOpen)
                logFile.Close();

            fileOpen = false;
        }

        public static StringBuilder fileName = new StringBuilder(128);

        public static void openFile()
        {
            if (fileOpen)
                closeFile();

            timeNow = DateTime.Now;

            fileName.Clear();
            fileName.Append(sdVol.RootDirectory);
            fileName.Append("\\");
            fileName.Append(timeNow.Year.ToString());
            fileName.Append("-");
            fileName.Append(timeNow.Month.ToString());
            fileName.Append("-");
            fileName.Append(timeNow.Day.ToString());
            fileName.Append("T");
            fileName.Append(timeNow.Hour.ToString());
            fileName.Append("-");
            fileName.Append(timeNow.Minute.ToString());
            fileName.Append("-");
            fileName.Append(timeNow.Second.ToString());

            logFile = new FileStream(fileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);
            fileOpen = true;
            Thread.Sleep(1000);
            lock (loggerLock) { logFile.Write(UTF8Encoding.UTF8.GetBytes(columnNames.ToString()), 0, columnNames.Length); }
            Program.btConsole.SendLine(columnNames);
            Debug.Print(columnNames.ToString());
        }

        public static void unMount()
        {
            if (SDMounted)
                sdCard.Unmount();
#if(USB_STORAGE)
            if (USBMounted)
                usbStorage.Unmount();
#endif
        }

        public static StringBuilder getPrefix(StringBuilder comment)
        {
            timeNow = DateTime.Now;

            sbPrefix.Clear();
            sbPrefix.Append(timeNow.ToString("MMM dd, yyyy"));
            sbPrefix.Append(token);
            sbPrefix.Append(timeNow.ToString("HH:mm:ss.fff"));
            sbPrefix.Append(token);
            sbPrefix.Append(Program.cpfState[(int)Program.CurrentState].stateName);
            sbPrefix.Append(token);
            sbPrefix.Append(comment);

            return (sbPrefix);
        }

        // This event is fired by unmount
        static void usbRemovableMedia_Eject(object sender, MediaEventArgs e)
        {
            Debug.Print("USB unmounted, eject event fired");
        }

        static void usbRemovableMedia_Insert(object sender, MediaEventArgs e)
        {
            Debug.Print("Insert event fired; USB Storage mount is finished.");
            //if (e.Volume.IsFormatted)
            //{
            //    rootDirectory = e.Volume.RootDirectory;

            //    Debug.Print("Available folders:");
            //    string[] strs = Directory.GetDirectories(e.Volume.RootDirectory);
            //    for (int i = 0; i < strs.Length; i++)
            //        Debug.Print(strs[i]);

            //    Debug.Print("Available files:");
            //    strs = Directory.GetFiles(e.Volume.RootDirectory);
            //    for (int i = 0; i < strs.Length; i++)
            //        Debug.Print(strs[i]);
            //}
            //else
            //{
            //    Debug.Print("Media is not formatted. Formatting...");
            //    e.Volume.Format("FAT", 0);
            //    rootDirectory = e.Volume.RootDirectory;
            //}
#if(USB_STORAGE)
            usbInsertEvent.Set(); // proceed with other processing
#endif
        }
    }
}
