using System;
using System.IO;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.IO;
using GHI.Premium.IO;

namespace Logger
{
    public class SD_Card_WTF_Exception : System.Exception
    {
        public SD_Card_WTF_Exception() : base() { }
        public SD_Card_WTF_Exception(string message) : base(message) { }
        public SD_Card_WTF_Exception(string message, System.Exception inner) : base(message, inner) { }
    }

    public class SD_Logger : IDataLogger, IDisposable
    {
        // A write queue to (help) mask the latency of writing to the SD card

        private SynchronizedLogEntryQueue   _logQ;
        private Thread                      _writingThread;
        private LogEntry                    _writeBuffer;

        private const string FILE_NAME_FORMAT = "yyyyMMdd";     // format for file name data logging (YYYYMMDD.txt)
        private const string FILE_EXT = ".log";                 // extension for data logging files

        private string      _rootDirectory;
        private string      _dirPath;           // directory on SD for data logging
        private FileStream  _fs;                // file stream for data logging
        private TextWriter  _tw;                // text writer for data logging
        private bool        _isOpen;            // data logger opened or not
        private bool        _disposed = false;  // disposing state

        // Constructor, "dirpath" = Directory on SD for data logging
        public SD_Logger(string logDirectory)
        {
            RemovableMedia.Insert += new InsertEventHandler(RemovableMedia_Insert);
            RemovableMedia.Eject += new EjectEventHandler(RemovableMedia_Eject);
            _logQ = new SynchronizedLogEntryQueue(128);
            _writeBuffer = new LogEntry();
            _rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory;
            _dirPath = _rootDirectory + logDirectory;
        }

        static void RemovableMedia_Eject(object sender, MediaEventArgs e)
        {
            Debug.Print("SD card ejected");
        }

        static void RemovableMedia_Insert(object sender, MediaEventArgs e)
        {
            Debug.Print("SD card inserted");

            if (e.Volume.IsFormatted)
            {
                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("SD card is not formatted");
            }
        }

        public void Open()
        {
            // create root data directory if doesn't exist
            if (!Directory.Exists(_dirPath))
                Directory.CreateDirectory(_dirPath);
            
            // create file path for data logging
            string filePath = _dirPath + DateTime.Now.ToString(FILE_NAME_FORMAT) +  FILE_EXT;
            
            // open the file in append mode or create it
            if (_fs == null)
            {
                _fs = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read);
                _tw = new StreamWriter(_fs);
            }

            _isOpen = true;
            _writingThread= new Thread(new ThreadStart(LogWriter));
            _writingThread.Start();
        }

        public void Close()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
            _isOpen = false;
            // TO DO:: Add Sync to make sure _logQ is Empty
            //         Before stopping log Writer
            _writingThread.Suspend();
        }

        public void Log(LogEntry e)
        {
            _logQ.Add(e);
        }

        private void LogWriter()
        {
            PersistentStorage sdPS = null;
            const int POLL_TIME = 500; // check every 500 millisecond

            bool sdExists;
            while (IsOpen)
            {
                try // If SD card was removed while mounting, it may throw exceptions
                {
                    sdExists = PersistentStorage.DetectSDCard();

                    // make sure it is fully inserted and stable
                    if (sdExists)
                    {
                        Thread.Sleep(50);
                        sdExists = PersistentStorage.DetectSDCard();
                    }

                    if (sdExists && sdPS == null)
                    {
                        sdPS = new PersistentStorage("SD");
                        sdPS.MountFileSystem();
                    }
                    
                    if (!sdExists && sdPS != null)
                    {
                        sdPS.UnmountFileSystem();
                        sdPS.Dispose();
                        sdPS = null;
                    }

                }
                catch
                {
                    if (sdPS != null)
                    {
                        sdPS.Dispose();
                        sdPS = null;
                    }
                }

                while (!_logQ.Empty() && sdPS != null)
                {
                    _logQ.Remove(ref _writeBuffer);
                    string writeThis = _writeBuffer.ToString();
                    Debug.Print(writeThis);
                    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(writeThis);
                    _fs.Write(buffer, 0, buffer.Length);
                }
                Thread.Sleep(POLL_TIME);
            }
        }

        public bool IsOpen
        {
            get { return _isOpen; }
        }

        ~SD_Logger()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Close();
        }

        // Dispose managed and unmanaged resources
        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // dispose managed resources

                    if (_tw != null)
                        _tw.Dispose();
                    _tw = null;

                    if (_fs != null)
                        _fs.Dispose();
                    _fs = null;
                }

                // no unmanaged resources to dispose
            }

            _disposed = true;
        }

        // Check SD card presence
        private void CheckSD()
        {
            bool isSDCardInserted = false;
            VolumeInfo[] volumes = VolumeInfo.GetVolumes();
            isSDCardInserted = (volumes.Length > 0);
            if (!isSDCardInserted)
            {
                throw new SD_Card_WTF_Exception();
            }
        }
    }
}
