using System;
using Microsoft.SPOT;

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

namespace CPF
{
    class BTConsole
    {
        private static SerialPort consolePort = new SerialPort("COM1", 115200, Parity.None, 8, StopBits.One);
        public static XModem Modem = new XModem(consolePort, XModem.Variants.XModem1K);

        private static StructQueue.qStruct qS = new StructQueue.qStruct();

        public static void OpenConsolePort()
        {
            if (!consolePort.IsOpen)
            {
                consolePort.Open();
            }
            consolePort.DiscardInBuffer();
            consolePort.DiscardOutBuffer();
            consolePort.DataReceived += new SerialDataReceivedEventHandler(consolePortDataReceived);

            qS.byteArray = new byte[StructQueue.qRecordWidth];
        }

        private static StringBuilder uploadStartComment = new StringBuilder(128);
        private static StringBuilder sbFUS = new StringBuilder("File Upload Successful");
        private static StringBuilder sbFUF = new StringBuilder("File Upload Failed");
        private static StringBuilder sbWaiting = new StringBuilder("Waiting 30 seconds. Setup your PC to receive a file using XModem-1K protocol");
        private static StringBuilder sbDoneWaiting = new StringBuilder("Starting XMODEM upload");
        private static StringBuilder uploadStartPrefix = new StringBuilder(128);
        private static StringBuilder uploadPrefix2 = new StringBuilder(128);

        public static MemoryStream DataMemoryStream = new MemoryStream(); // Grows dynamically
        public static byte[] DataArray = new byte[0];

        private static FileStream uploadFile;
        private static int uploadFileLength = 0;

        private static Object BTLock = new Object();

        public static void uploadDataFileWhole(StringBuilder uploadFileName)
        {
            //The XModem SendFileWhole method expects the whole file to be in a byte array in memory
            //There is a SendFilePieces for really big files
            uploadFile = new FileStream(uploadFileName.ToString(), FileMode.Open, FileAccess.Read);
            uploadFileLength = (int)uploadFile.Length;
            DataArray = new byte[uploadFileLength];
            uploadFile.Read(DataArray, 0, uploadFileLength);

            //Detach the "regular" console port event handler so the XModem class attach its event handler
            consolePort.DataReceived -= consolePortDataReceived;

            SendLine(sbWaiting);
            lock (BTLock) { Program.MessageQueue.Enqueue(UTF8Encoding.UTF8.GetBytes(EngrLogger.getPrefix(sbWaiting).ToString())); }
            Thread.Sleep(30000);
            SendLine(sbDoneWaiting);
            lock (BTLock) { Program.MessageQueue.Enqueue(UTF8Encoding.UTF8.GetBytes(EngrLogger.getPrefix(sbDoneWaiting).ToString())); }
            
            SendFileWhole();

            //Re-attach "regular" console port event handler
            consolePort.DataReceived += consolePortDataReceived;
        }

        private static StringBuilder sbTemp = new StringBuilder(512);
        private static byte[] byteTemp = new byte[1024];

        public static void SendLine(StringBuilder sbLineToSend)
        {
            sbTemp.Clear();
            sbTemp.Append(sbLineToSend);
            sbTemp.AppendLine();
            Array.Clear(byteTemp, 0, byteTemp.Length);
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), byteTemp, sbTemp.Length);
            consolePort.Write(byteTemp, 0, byteTemp.Length);
        }

        public static void CloseConsolePort()
        {
            consolePort.Close();
        }

        private static byte[] consolePortReadBytes = new byte[64];
        private static byte[] consolePortInBytes = new byte[16];
        private static byte[] header = UTF8Encoding.UTF8.GetBytes("CON");
        private static byte[] message = new byte[256];


        private static byte STX = 36;            //Start of text = "$"
        private static byte ETX = 10;            //End of text = LF

        private static int messageNum = 0;
        private static int consolePortBytesToRead = 0;
        private static int consolePortInBytesIndex = 0;

        private static object consoleLock = new object();

         private static void consolePortDataReceived(object sender, SerialDataReceivedEventArgs e)
        {

            consolePortBytesToRead = consolePort.BytesToRead;

            try
            {
                consolePort.Read(consolePortReadBytes, 0, consolePortBytesToRead);
            }
            catch
            {
                Debug.Print("Console Read Exception");          //TODO do something smarter here
            }

            for (int i = 0; i < consolePortBytesToRead; i++)
            {
                consolePortInBytes[consolePortInBytesIndex] = consolePortReadBytes[i];
                if (consolePortInBytesIndex == 0)    //Check to make sure first character is = LF
                {
                    if (consolePortInBytes[0] == STX)  //only move on to byte index 1 after we get a LF
                    {
                        consolePortInBytesIndex = consolePortInBytesIndex + 1;
                    }
                }
                else
                {
                    if (consolePortInBytes[consolePortInBytesIndex] == ETX)
                    {
                        //Array.Clear(message, 0, message.Length);
                        //Array.Copy(header, message, header.Length);
                        //Array.Copy(consolePortInBytes, 1, message, header.Length, consolePortInBytesIndex);
                        //lock (consoleLock) { Program.MessageQueue.Enqueue(message); }

                        Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
                        Array.Copy(consolePortInBytes, qS.byteArray, consolePortInBytesIndex);
                        qS.timeStamp = DateTime.Now;
                        qS.state = Program.CPFState;
                        qS.qRecordType = StructQueue.QRecordType.Console;
                        lock (consoleLock) { Program.sQueue.Enqueue(qS); }

                        consolePortInBytesIndex = 0;
                        messageNum = messageNum + 1;

                        Array.Clear(consolePortInBytes, 0, consolePortInBytes.Length);
                        Array.Clear(consolePortReadBytes, 0, consolePortReadBytes.Length);
                    }
                    else
                    {
                        consolePortInBytesIndex = consolePortInBytesIndex + 1;
                    }
                }
            }
        }

        public static void ReceiveFileWhole()
        {
            // Passing an empty MemoryStream object to the XModem.Receive() method indicates that the user wishes to receive the entire
            // file in one big lump. NOTE: If using this technique, your device must have enough contiguous RAM to support the expected 
            // file size. Contiguous RAM is the largest memory that can be allocated to a single object. It is usually much smaller
            // than a device's "total" RAM.

            // Start the XModem receive process. This method blocks until the transfer has terminated (either through successful
            // end-of-file, or due to errors such as timeouts, excessive retries, etc). You can examine the termination reason to 
            // determine under what conditions the transfer has stopped.
            XModem.TerminationReasonEnum terminationReason = Modem.Receive(DataMemoryStream);

            if (terminationReason == XModem.TerminationReasonEnum.EndOfFile)
            {
                Debug.Print("LUMP FILE RECEIVE SUCCESSFUL!");

                // Transfer successful, so convert MemoryStream to byte array
                DataArray = DataMemoryStream.ToArray();

                // Because XModem packets are a fixed size, if the original transmitted file does not fill a packet completely,
                // padding bytes are added until the packet is the required size. The padding byte is usually the SUB symbol
                // (byte value 26). You may strip away these padding bytes if desired. This may be a problem, however,
                // if one or more SUB bytes dangling at the end is actually a legitimate part of the file (rare, but theoretically
                // possible).

                // In this example, we will assume that consecutive dangling SUB bytes are never a legitimate part of the file,
                // and we can strip them away so that the received file will match its original size.
                DataArray = Modem.TrimPaddingBytesFromEnd(DataArray);
            }
            else
            {
                // Something went wrong during the transfer. You may examine the termination reason and respond accordingly.
                Debug.Print("LUMP FILE RECEIVE FAILED!");
            }
        }

        public static bool SendFileWhole()
        {
            int numBytesSuccessfullySent = 0;
            bool success = false;

            numBytesSuccessfullySent = Modem.Send(DataArray);

            if (numBytesSuccessfullySent == DataArray.Length && Modem.TerminationReason == XModem.TerminationReasonEnum.EndOfFile)
            {
                SendLine(sbFUS);
                lock (BTLock) { Program.MessageQueue.Enqueue(UTF8Encoding.UTF8.GetBytes(EngrLogger.getPrefix(sbFUS).ToString())); }
                success = true;
            }
            else
            {
                SendLine(sbFUF);
                lock (BTLock) { Program.MessageQueue.Enqueue(UTF8Encoding.UTF8.GetBytes(EngrLogger.getPrefix(sbFUF).ToString())); }
                success = false;
            }
            return (success);
        }
    }
}
