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

using Microsoft.SPOT;

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

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

        public static bool IsOpen
        {
            get { return consolePort.IsOpen; }
        }

        public static bool BTSend = true;

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

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

        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 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 int maxFileSize = 524288;

        public virtual void detachEventHandler()
        {
            //Detach the "regular" console port event handler so the XModem class attach its event handler
            consolePort.DataReceived -= consolePortDataReceived;
        }

        private static StringBuilder sbWaiting = new StringBuilder("Waiting 30 seconds. Setup your PC to receive a file using XModem-1K protocol");
        private static StringBuilder sbDoneWaitingWhole = new StringBuilder("Starting XMODEM whole file upload");
        private static StringBuilder sbDoneWaitingPieces = new StringBuilder("Starting XMODEM file upload in pieces");

        public virtual void uploadDataFile(StringBuilder uploadFileName)
        {
            EngrLogger.writeToColumns("Starting uploadDataFile");

            uploadFile = new FileStream(uploadFileName.ToString(), FileMode.Open, FileAccess.Read);
            uploadFileLength = (int)uploadFile.Length;

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

            SendLine(sbWaiting);

            qS.timeStamp = DateTime.Now;
            qS.state = Program.CurrentState;
            qS.qRecordType = StructQueue.QRecordType.BTConsole;
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbWaiting.ToString()), qS.byteArray, sbWaiting.Length);
            Program.sQueue.Enqueue(qS);

            Thread.Sleep(30000);

            qS.timeStamp = DateTime.Now;
            qS.state = Program.CurrentState;
            qS.qRecordType = StructQueue.QRecordType.BTConsole;

            if (uploadFileLength < maxFileSize)
            {
                DataArray = new byte[uploadFileLength];
                uploadFile.Read(DataArray, 0, uploadFileLength);

                SendLine(sbDoneWaitingWhole);
                Array.Copy(UTF8Encoding.UTF8.GetBytes(sbDoneWaitingWhole.ToString()), qS.byteArray, sbDoneWaitingWhole.Length);
                Program.sQueue.Enqueue(qS);

                SendFileWhole();
            }
            else
            {
                SendLine(sbDoneWaitingPieces);
                Array.Copy(UTF8Encoding.UTF8.GetBytes(sbDoneWaitingPieces.ToString()), qS.byteArray, sbDoneWaitingPieces.Length);
                Program.sQueue.Enqueue(qS);

                SendFilePieces();
            }
            uploadFile.Close();
            Array.Clear(DataArray, 0, DataArray.Length);

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

        public virtual 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

            Debug.Print("Starting uploadDataFileWhole");

            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);

            qS.timeStamp = DateTime.Now;
            qS.state = Program.CurrentState;
            qS.qRecordType = StructQueue.QRecordType.BTConsole;
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbWaiting.ToString()), qS.byteArray, sbWaiting.Length);
            Program.sQueue.Enqueue(qS);

            Thread.Sleep(30000);
            SendLine(sbDoneWaitingWhole);

            qS.timeStamp = DateTime.Now;
            qS.state = Program.CurrentState;
            qS.qRecordType = StructQueue.QRecordType.BTConsole;
            Array.Copy(UTF8Encoding.UTF8.GetBytes(sbDoneWaitingWhole.ToString()), qS.byteArray, sbDoneWaitingWhole.Length);
            Program.sQueue.Enqueue(qS);

            SendFileWhole();

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

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

        public virtual void SendLine(StringBuilder sbLineToSend)
        {
            if (BTSend)
            {
                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[128];
        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 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 = STX
                {
                    if (consolePortInBytes[0] == STX)  //only move on to byte index 1 after we get a STX
                    {
                        consolePortInBytesIndex = consolePortInBytesIndex + 1;
                    }
                }
                else
                {
                    if (consolePortInBytes[consolePortInBytesIndex] == ETX)
                    {
                        Array.Clear(qS.byteArray, 0, qS.byteArray.Length);
                        Array.Copy(consolePortInBytes, qS.byteArray, consolePortInBytesIndex);
                        qS.timeStamp = DateTime.Now;
                        qS.state = Program.CurrentState;
                        qS.qRecordType = StructQueue.QRecordType.BTConsole;
                        Program.sQueue.Enqueue(qS);

                        sbTemp.Clear();
                        sbTemp.Append(UTF8Encoding.UTF8.GetChars(consolePortInBytes));
                        Debug.Print(sbTemp.ToString());

                        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 virtual bool SendFileWhole()
        {
            int numBytesSuccessfullySent = 0;
            bool success = false;

            numBytesSuccessfullySent = Modem.Send(DataArray);

            if (numBytesSuccessfullySent == DataArray.Length && Modem.TerminationReason == XModem.TerminationReasonEnum.EndOfFile)
            {
                SendLine(sbFUS);

                qS.timeStamp = DateTime.Now;
                qS.state = Program.CurrentState;
                qS.qRecordType = StructQueue.QRecordType.BTConsole;
                Array.Copy(UTF8Encoding.UTF8.GetBytes(sbFUS.ToString()), qS.byteArray, sbFUS.Length);
                Program.sQueue.Enqueue(qS);

                success = true;
            }
            else
            {
                SendLine(sbFUF);

                qS.timeStamp = DateTime.Now;
                qS.state = Program.CurrentState;
                qS.qRecordType = StructQueue.QRecordType.BTConsole;
                Array.Copy(UTF8Encoding.UTF8.GetBytes(sbFUF.ToString()), qS.byteArray, sbFUF.Length);
                Program.sQueue.Enqueue(qS);

                success = false;
            }
            return (success);
        }

        public virtual void SendFilePieces()
        {
            // In this example, this is the number of bytes to read from the file during each pass, to be packaged for sending.
            // This value is completely arbitrary. It may be as small as 1 byte all the way up to a very large value (within device
            // memory capacity). This is absolutely independent of XModem packet size. The XMODEM.AddToOutboundPacket() method
            // accepts arrays of any size and packetizes arguments automatically, whether they are larger or smaller than
            // the chosen packet size.
            int desiredBlockSize = maxFileSize;

            // Tracks our position in the file
            int fileOffset = 0;

            // Instruct the modem to begin the send process.
            // We MUST call this method first before calling XMODEM.AddToOutboundPacket() otherwise an exception will be thrown.
            Modem.Send();

            // Loop until all bytes have been read from "persistent storage".
            while (fileOffset < uploadFileLength)
            {
                int numFileBytesRemaining = uploadFileLength - fileOffset;

                // Determine the number of bytes that we can pull from the source file to populate the current block.
                int numBytesToPull;
                if (numFileBytesRemaining >= desiredBlockSize)
                {
                    // There are enough remaining bytes in the file that we can fill a block completely
                    numBytesToPull = desiredBlockSize;
                }
                else
                {
                    // The number of bytes remaining is smaller than the desired block size, so only
                    // use the available number
                    numBytesToPull = numFileBytesRemaining;
                }

                // Instantiate the block that will hold the bytes from persistent storage
                byte[] block = new byte[numBytesToPull];

                // Pretend that we are pulling the data from a file in persistent storage
                //Array.Copy(DataArray, fileOffset, block, 0, block.Length);
                //Do this to read from a file instead of pretending
                uploadFile.Seek(fileOffset, SeekOrigin.Begin);
                uploadFile.Read(block, 0, numBytesToPull);

                // Add the current block of data to the outbound packet. This is a blocking method call.
                // If the modem still does not have enough bytes for a packet, the fresh data is added to the pending packet,
                // and this method returns immediately.
                // If the modem has collected enough data for one or more packets, it transmits each packet and blocks
                // until transmission is complete. Note that this process can be relatively quick if the Receiver responds
                // quickly, but may be slow if packets have to be re-transmitted or the Receiver does not respond, requiring
                // timeouts to expire.
                int numBytesSuccessfullySentDuringThisCall = Modem.AddToOutboundPacket(block);

                // Determine if the latest packet has been transmitted successfully, or whether an error or timeout has
                // resulted in the file transfer being cancelled.
                if (Modem.TerminationReason == XModem.TerminationReasonEnum.TransferStillActiveNotTerminated)
                {
                    // So far so good, so proceed normally
                    fileOffset += numBytesToPull;
                }
                else
                {
                    // Something bad happened while attempting to send the latest packet
                    Debug.Print("PIECEMEAL FILE SEND FAILED!");
                    return;
                }
            }

            // Once all bytes have been packetized, we should inform the Receiver that the file is complete.
            // The EndFile() method automatically transmits any pending packets followed by the end-of-file byte.
            // We MUST do this to finish the file-send process properly. This is a blocking method call
            // which blocks until the Receiver acknowledges the end-of-file.
            Modem.EndFile();

            // Examine the ultimate outcome of our file send attempt
            if (Modem.TerminationReason == XModem.TerminationReasonEnum.EndOfFile)
                Debug.Print("PIECEMEAL FILE SEND SUCCESSFUL!");
            else
                Debug.Print("PIECEMEAL FILE SEND FAILED!");
        }
    }
}
