using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using GHI.Processor;
using Microsoft.SPOT;
using Microsoft.SPOT.IO;
using SWModules;

namespace HWModules
{
    // Create a Delegate for Sending Lines (likely to the bluetooth or a console port.)
    public delegate void SendLineDelegate(StringBuilder sb);

    public static class EngrLogger
    {

        private static StringBuilder columnNames = new StringBuilder();
        private static StringBuilder columnTypes = new StringBuilder();

        //private static SDStorage sdStorage;

        public static VolumeInfo sdVol;
        private static FileStream engrLogFile;

        private static StringBuilder sbTemp = new StringBuilder(4096);
        private static StringBuilder sbWrite = 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 engFileOpen { get; private set; }
        private static bool SDMounted;

        public static event SendLineDelegate SendConsoleLine;

        //public static StateBase[] states;
        public static StringBuilder CurrentState
        {
            get { return _currentState;}
            set { _currentState.Clear().Append(value); }
        }

        private static void debugLine(StringBuilder sb) 
        {
           //This method is only required as a place holder until the desired method is attached (delegated) to the SendConsoleLine event
        }

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

        private static readonly StringBuilder _currentState = new StringBuilder(32);

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

        public static bool init(StringBuilder currentstate)
        {

            CurrentState = currentstate;

            var sdStorage = SDStorage.Instance;

            //Add default event handler for SendConsoleLine event
            SendConsoleLine += debugLine;

            //Add event handler for SafeEncoding Notifications
            SafeEncoding.Notify += EncoderNotification;

            for (int i = 0; i < (int)EngrLiterals.ColumnNums.lastColumn; i++)
            {
                columnName[i] = new StringBuilder(32);
                columnType[i] = new StringBuilder(32);
            }


            EngrLiterals.CreateEngrHeaderLines(columnName, columnType);

            columnNames.Clear();
            columnTypes.Clear();
            for (int i = 0; i < (int)EngrLiterals.ColumnNums.lastColumn; i++)
            {
                columnNames.Append(columnName[i]);
                columnNames.Append(Literals.tabChar);
                columnTypes.Append(columnType[i]);
                columnTypes.Append(Literals.tabChar);
            }
            columnNames.Append(Literals.crlfString);
            columnTypes.Append(Literals.crlfString);

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

            sdVol = new VolumeInfo("\\SD");
            SDMounted = true;
            CreateDirName();
            openNewEngFile();

            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);
            writeToColumns(sbTemp);

            printVolInfo();
            
            sbTemp.Clear();
            sbTemp.Append("PlatVel PID K = ");
            sbTemp.Append(BuoyancyEngine.PlatVel.K.ToString());
            writeToColumns(sbTemp);

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

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

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

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

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

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

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

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

            if (SDMounted)
            {
                sbTemp.Clear();
                sbTemp.Append("Engr Logger Initialized");
                EngrLogger.writeToColumns(1, sbTemp);
                return (true);
            }
            else
            {
                sbTemp.Clear();
                sbTemp.Append("***ERROR: Engr Logger Failed to Initialize");
                EngrLogger.writeToColumns(1, sbTemp);
                return (false);
            }
        }

        

        public static void BTSendLine(StringBuilder msg)
        {
            if (SendConsoleLine != null) SendConsoleLine(msg);
        }

        private static int lastValueIndex;
        private static int columnNum;
        private static int valueNum;

        public static void Comment(string msg)
        {
            writeToColumns(msg);
        }

        public static void Comment(int btVerbosity, string msg)
        {
            writeToColumns(btVerbosity, msg);
        }

        private const string FmtDate = "dd-MMM-yyyy";
        private const string FmtTime = "HH:mm:ss.fff";

        //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
        //This overload does not write to the BT module
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void writeToColumns(params object[] values)
        {
            writeToColumns(BTVerbositySetPoint, values);
        }

        //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
        //This overload does write to the BT module with appropriate verbosity setting
        //Verbosity = 0: no BT output at all
        //Verbosity = 1: Basic messages are always sent, minimum verbosity
        //Verbosity = 5: Maximum verbosity 

        private const int BTVerbositySetPoint = 1;

        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void writeToColumns(int BTVerbosity, params object[] values)
        {
            if (values == null) return;
            try
            {
                timeNow = DateTime.Now;
                sbWrite.Clear();
                sbWrite.Append("$");
                sbWrite.Append(token);

                if (values.Length == 1) //If there is only one argument in the object list, it is a comment and only a comment
                {
                    sbWrite.Append(timeNow.ToString(FmtDate));
                    sbWrite.Append(token);
                    sbWrite.Append(timeNow.ToString(FmtTime));
                    sbWrite.Append(token);
                    sbWrite.Append(CurrentState);
                    sbWrite.Append(token);
                    sbWrite.Append(values[0]);
                }
                else
                {
                    lastValueIndex = values.Length - 1;

                    try
                    {
                        for (columnNum = 0; columnNum < (int)EngrLiterals.ColumnNums.lastColumn; columnNum++)
                        {
                            for (valueNum = 0; valueNum < lastValueIndex; valueNum = valueNum + 2)
                            {
                                if ((int)values[valueNum] == columnNum)
                                {
                                    //TODO P3 need to do a better job formatting values to minimize length
                                    if (columnNum == (int)EngrLiterals.ColumnNums.dateTime)
                                    {
                                        timeNow = (DateTime)values[valueNum + 1];
                                        sbWrite.Append(timeNow.ToString(FmtDate));
                                        sbWrite.Append(token);
                                        sbWrite.Append(timeNow.ToString(FmtTime));
                                    }
                                    else if (columnNum == (int)EngrLiterals.ColumnNums.state)
                                    {
                                        sbWrite.Append(CurrentState);
                                    }
                                    else if (columnNum == (int)EngrLiterals.ColumnNums.Msc)
                                    {
                                        sbWrite.Append(values[valueNum + 1]);
                                    }
                                    else if (columnNum == (int)EngrLiterals.ColumnNums.comment)      //TODO P2 Need to do the formatting in the instrument class, this is too generic
                                        sbWrite.Append(values[valueNum + 1]);
                                    else if ((columnNum == (int)EngrLiterals.ColumnNums.CTDSalinity) || (columnNum == (int)EngrLiterals.ColumnNums.TOpt)
                                            || (columnNum == (int)EngrLiterals.ColumnNums.IModem) || (columnNum == (int)EngrLiterals.ColumnNums.GPS)
                                            || (columnNum == (int)EngrLiterals.ColumnNums.FLBB) || (columnNum == (int)EngrLiterals.ColumnNums.OCRText))
                                        sbWrite.Append(SafeEncoding.GetChars((byte[])values[valueNum + 1]));
                                    else
                                    {
                                        if (values[valueNum + 1] is double)
                                            sbWrite.Append(((double)values[valueNum + 1]).ToString("f6"));
                                        else if (values[valueNum + 1] is int)
                                            sbWrite.Append((int)values[valueNum + 1]);
                                    }
                                    break;
                                }
                            }
                            sbWrite.Append(token);
                        }
                    }
                    catch
                    {
                        sbWrite.Append(timeNow.ToString(FmtDate));
                        sbWrite.Append(token);
                        sbWrite.Append(timeNow.ToString(FmtTime));
                        sbWrite.Append(token);
                        sbWrite.Append(SV.CurrentState);
                        sbWrite.Append(token);
                        sbWrite.Append("[ERROR] putting values in columns in EngrLogger.writeToColumns");
                    }
                }

                //Replace all CRLF including possible terminating CRLF since //Debug.Print adds its own
                sbWrite.Replace('\r', ' ');
                sbWrite.Replace('\n', ' ');

                if (MissionConfig.DebuggerVerbosityLevel > 3)
                    Debug.Print(sbWrite.ToString());

                //Add in CRLF for log files
                //sbWrite.Append("\r\n");
                terminateSBWrite(); //this was an attempt to make the file smaller by not writing unused tabs at the end of every line.  It may have thrown an exception

                if (SDMounted)
                {
                    Array.Clear(writeArray, 0, writeArray.Length);
                    Array.Copy(Encoding.UTF8.GetBytes(sbWrite.ToString()), writeArray, sbWrite.Length);
                    try
                    {
                        engrLogFile.Write(writeArray, 0, sbWrite.Length);
                        if (BTVerbosity >= BTVerbositySetPoint)
                        {
                            if (SendConsoleLine != null) SendConsoleLine(sbWrite);
                        }
                    }
                    catch
                    {
                        sbTemp.Clear();
                        sbTemp.Append("[ERROR] No File Open: ");
                        sbTemp.Append(sbWrite);
                        if (SendConsoleLine != null) SendConsoleLine(sbTemp);
                        Debug.Print(sbTemp.ToString());
                    }
                }
                else
                {
                    sbTemp.Clear();
                    sbTemp.Append("No SD: ");
                    sbTemp.Append(sbWrite);
                    if (SendConsoleLine != null) SendConsoleLine(sbTemp);
                    Debug.Print(sbTemp.ToString());
                }
            }
            catch
            {
                sbTemp.Clear();
                sbTemp.Append("[ERROR] in EngrLogger.WriteToColumns ");
                sbTemp.Append(sbWrite);
                sbWrite.Clear();
                sbWrite.Append(sbTemp);
                if (SendConsoleLine != null) SendConsoleLine(sbTemp);
                Debug.Print(sbTemp.ToString());
            }
        }

        private static void terminateSBWrite()
        {
            for (int i = sbWrite.Length - 1; i > 0; i--)
            {
                if (sbWrite[i] == '\t')
                    sbWrite.Remove(i, 1);
                else
                    break;
            }
            sbWrite.Append("\r\n");
        }

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

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

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

        public static void getDirectory()
        {
            sbTemp.Clear();
            sbTemp.Append(sdVol.RootDirectory);
            sbTemp.Append("\\data\\");
            String[] fileNames = Directory.GetFiles(sbTemp.ToString());

            sbTemp.Clear();
            sbTemp.Append("Processing GETDIR Console message");
            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();
                        if (SendConsoleLine != null)
                            SendConsoleLine(sbTemp);

                    }
                    catch
                    {
                        writeToColumns("[ERROR] in getDir get FileInfo");
                    }
                }
            }
            else
            {
                sbTemp.Clear();
                sbTemp.Append("No files on disk!");
                Debug.Print(sbTemp.ToString());
                sbTemp.AppendLine();
                if (SendConsoleLine !=null) SendConsoleLine(sbTemp);
            }
        }

        public static void closeEngrLogFile()
        {
            if (engFileOpen)
            {
                engrLogFile.Flush();
                engrLogFile.Close();
                sdVol.FlushAll();
            }

            engFileOpen = false;
        }

        public static StringBuilder fileName = new StringBuilder(128);
        public static StringBuilder dirName = new StringBuilder(128);
        public static int dirNameCount = 1;
        public static StringBuilder fileNameBase = new StringBuilder(128);
        
        public static void openNewEngFile()
        {
            if (engFileOpen)
                closeEngrLogFile();

            timeNow = DateTime.Now;

            try
            {
                fileName.Clear();
                fileName.Append(dirName);
                fileName.Append(MissionConfig.PlatformName);
                fileName.Append('.');
                fileName.Append((SV.ProfileNum+1).ToString("d3"));                            
   
                fileNameBase.Clear();
                fileNameBase.Append(fileName);

                fileName.Append(".eng");
                engrLogFile = new FileStream(fileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write);

                if (!engrLogFile.CanWrite) { throw new IOException("Cannot Open ENG File:" + fileName); }

                Thread.Sleep(100);
                lock (loggerLock) 
                {
                    engrLogFile.Write(Encoding.UTF8.GetBytes(columnNames.ToString()), 0, columnNames.Length);
                    engrLogFile.Write(Encoding.UTF8.GetBytes(columnTypes.ToString()), 0, columnTypes.Length);
                }
                //if (SendConsoleLine !=null) SendConsoleLine(columnNames);

                sbTemp.Clear();
                sbTemp.Append("Opened new engr log file: ");
                sbTemp.Append(fileName);
                writeToColumns(sbTemp);

                engFileOpen = true;
            }
            catch (Exception e)
            {
                sbTemp.Clear().Append(e);//TODO P1 Does this need to set recovery mode
                if (SendConsoleLine != null) SendConsoleLine(sbTemp);
                engFileOpen = false;
            }
        }

        private static void CreateDirName()
        {
            timeNow = DateTime.Now;

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

            if (Directory.Exists(dirName.ToString()))
            {
                //This should never happen given it takes more than 1 second for the program to get here
                dirName.Append(dirNameCount.ToString());
                dirName.Append("\\");
                dirNameCount++;
                if (dirNameCount > 10)
                {
                    throw new IOException("Too many directories with same name:" + dirName);
                }
                Directory.CreateDirectory(dirName.ToString());
            }
            else
                Directory.CreateDirectory(dirName.ToString());
        }

        private static void EncoderNotification(StringBuilder sb)
        {
            writeToColumns(sb);
        }
    }
}
