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

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHI.Hardware.EMX;

namespace CPF
{
    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 SerialReader : IDisposable
    {
        private SerialPort      _port;
        private byte            _readBufferIndex = 0;
        private byte[]          _readBuffer = new byte[80];
        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 SerialReader(string port, Int32 baudRate)
        {
            Callback = null;
            _port = new SerialPort(port, baudRate, Parity.None, 8, StopBits.One);
            _port.ReadTimeout = 1000;
            _port.WriteTimeout = 20;
            _port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
            _port.ErrorReceived += new SerialErrorReceivedEventHandler(ErrorOccurredHandler);
            _port.Open();
            Stop();
        }

        // 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;
            _readBuffer[_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] == '\r')
                            //_readBuffer[_readBufferIndex] == '\n')
                        {
                            Callback(_readBuffer);
                            _readBuffer[_readBufferIndex] = 0;
         
                            sp.DiscardInBuffer();
                        }
                    }
                    else
                    {
                        sp.DiscardInBuffer();
                        _readBufferIndex = 0;
                        _readBuffer[_readBufferIndex] = 0;
                        break;
                    }
                    _readBufferIndex++;
                    bytes--;
                }
            }
            else
            {
                sp.DiscardInBuffer();
            }
        }

        private void ErrorOccurredHandler(object sender, SerialErrorReceivedEventArgs e)
        {
            // SerialErrorReceived = true;
            string eventTypeName;
            switch (e.EventType)
            {
                case SerialError.Frame:     eventTypeName = "frame"; 
                                            break;
                case SerialError.Overrun:   eventTypeName = "overrun"; 
                                            break;
                case SerialError.RXOver:    eventTypeName = "RX overflow"; 
                                            break;
                case SerialError.RXParity:  eventTypeName = "RX parity error"; 
                                            break;
                case SerialError.TXFull:    eventTypeName = "TX buffer full"; 
                                            break;
                default:                    eventTypeName = e.ToString(); 
                                            break;
            }
            Debug.Print("Error received : " + eventTypeName);
        }
    }
}
