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

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace CPF
{
    public class FramingError : System.Exception
    {
        public FramingError() : base() { }
    }

    public class OverrunError : System.Exception
    {
        public OverrunError() : base() { }
    }

    public class ReceiverOverflow: System.Exception
    {
        public ReceiverOverflow() : base() { }
    }

    public class ReceiveParityError : System.Exception
    {
        public ReceiveParityError() : base() { }
    }

    public class TX_BufferFull: System.Exception
    {
        public TX_BufferFull() : base() { }
    }

    public class Channel_WTF : System.Exception
    {
        public Channel_WTF() : base() { }
    }

    public delegate void StreamHandler(byte[] buffer);

    // This class enables building simple user interfaces to send and receive data over a serial port.
    // Incoming serial data is handled in an event-driven manner.
    public class Channel : IDisposable
    {
        private SerialPort      _port;
        private byte            _readBufferIndex = 0;
        private byte[]          _readBuffer = new byte[8000];
        private bool            _processInput = false;
        private UTF8Encoding    _encoding = new UTF8Encoding();
         

        // Method to be called back once the input has been received for this option or item in a series of inputs
        public StreamHandler Callback { get; set; }

        public byte Terminator { get; set; }

        public Channel(string port, Int32 baudRate)
        {
            Callback = null;
            Terminator = 0x0A;
            _port = new SerialPort(port, baudRate, Parity.None, 8, StopBits.One);
            _port.ReadTimeout =  20;
            _port.WriteTimeout = 20;
            Stop();
            _port.Open();
            _port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
            _port.ErrorReceived += new SerialErrorReceivedEventHandler(ErrorOccurredHandler);
        }

        // Immediately send a line of text to the serial port
        public void Send(string line)
        {
            byte[] bytes = _encoding.GetBytes(line);
            _port.Write(bytes, 0, bytes.Length);
        }

        // Immediately send a line of text to the serial port
        public void Send(byte[] _buffer)
        {
            _port.Write(_buffer, 0, _buffer.Length);
        }

        // Start accepting user input from the serial port
        public void Start()
        {
            _port.DiscardInBuffer();
            _readBufferIndex = 0;
           _processInput = true;
        }

        // Stop accepting input from the serial port
        public void Stop()
        {
            _processInput = false;
        }

        // Free up the resources
        public void Dispose()
        {
            _port.Flush();
            _port.Close();
            _port = null;
            _readBuffer = null;
            _encoding = null;
            Callback = null;
        }

        // Interrupt-driven serial data handler.
        // Accepts up to 80 characters.
        // Invokes user-callbacks when a carriage-return or a Line-feed is detected.
        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            var sp = (SerialPort)sender;
            if (_processInput == true)
            {
                var bytes = sp.BytesToRead;
                while (bytes != 0)
                {
                    if (_readBufferIndex < _readBuffer.Length)
                    {
                        sp.Read(_readBuffer, _readBufferIndex, 1);
                        if (_readBuffer[_readBufferIndex] == Terminator)
                        {
                            // Null terminate the character string
                            _readBuffer[_readBufferIndex] = 0;
                            sp.DiscardInBuffer();
                            Callback(_readBuffer);
                            _readBufferIndex = 0;
                            _readBuffer[_readBufferIndex] = 0;
                            break;
                        }
                    }
                    else
                    {
                        sp.DiscardInBuffer();
                        _readBufferIndex = 0;
                        _readBuffer[_readBufferIndex] = 0;
                        break;
                    }
                    _readBufferIndex++;
                    bytes--;
                }
            }
            else
            {
                sp.DiscardInBuffer();
            }
        }

        private void ErrorOccurredHandler(object sender, SerialErrorReceivedEventArgs e)
        {
            switch (e.EventType)
            {
                case SerialError.Frame:
                    // throw new FramingError();
                    break;

                case SerialError.Overrun:
                    throw new OverrunError();

                case SerialError.RXOver:
                    throw new ReceiverOverflow();

                case SerialError.RXParity:
                    throw new ReceiveParityError();

                case SerialError.TXFull:
                    throw new TX_BufferFull();

                default:
                    throw new Channel_WTF();
            }
        }
    }
}
