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

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

namespace CPF
{
    public class PressureSensor
    {
        public double DGH_MinCurrent        =  4.0;
        public double DGH_MaxCurrent        = 20.0;
        public double DGH_ActualMinCurrent  =  4.0;
        public double DGH_ActualMaxCurrent  = 20.0;
        public double DGH_SensorSpanIn_PSI  = 15.0;

        // Called when we have read the pressure correctly
        public  delegate void   Pressure(double decibars);
        public  Pressure        OnPressureChange;

        private delegate void   State(byte ch);
        private SerialPort      m_Uart;
        private Thread          m_DGH_Thread;
        private TimerCallback   m_timerCallback;
        private Timer           m_timer;
        private AutoResetEvent  m_sample;
        private State           m_State;
        private const int       m_MaxMantissaDigits = 5;
        private const int       m_MaxFractionDigits = 2;

        private byte[]          m_readPressureCommand;
        private double          m_Pressure;
        private Int32           m_Mantissa;
        private Int32           m_Fraction;
        private int             m_Digits;
        private bool            m_Negate;
        private byte[]          m_Buffer;
        private Calibrator      m_CurrentLoopCalibrator;

        private const byte      m_CarriageReturn = 0x0D;
        private const byte      m_Zeros          = 0x00;
        private const byte      m_Star           = 0x2A;
        private const byte      m_Plus           = 0x2B;
        private const byte      m_Minus          = 0x2D;
        private const byte      m_ZeroDigit      = 0x30;
        private const byte      m_NineDigit      = 0x39;
        private const byte      m_Point          = 0x2E;

        public PressureSensor(string SerialPort, int BaudRate, double sampleRateIn_Hz, int delayUntilStartIn_mSecs)
        {
            m_Uart = new SerialPort(SerialPort, BaudRate);
            m_DGH_Thread = new Thread(new ThreadStart(Run));
            m_sample = new AutoResetEvent(false);
            m_timerCallback = new TimerCallback(Sample);

            int sampleRateIn_mSec = Tools.SampleRateIn_mSec(sampleRateIn_Hz);
            m_timer = new Timer(m_timerCallback, null, delayUntilStartIn_mSecs, sampleRateIn_mSec);
            m_Buffer = new byte[128];
            m_CurrentLoopCalibrator = new Calibrator(DGH_MinCurrent, DGH_ActualMinCurrent,
                                                     DGH_MaxCurrent, DGH_ActualMaxCurrent,
                                                     DGH_SensorSpanIn_PSI);
            m_Uart.ErrorReceived += new SerialErrorReceivedEventHandler(UART_ErrorOccurred);
            m_readPressureCommand = Encoding.UTF8.GetBytes("$1RD\r\n");
            m_Digits = 0;
            m_Negate = false;
            m_State = ZeroPad;
        }

        // Checks if we're listening to the DGH 
        public bool Active { get { return this.m_Uart.IsOpen; } }

        // Starts listening to the Pressure Sensor
        public void Start()
        {
            m_Uart.Open();
            m_DGH_Thread.Start();
        }

        // Stops listening to the Pressure Sensor
        public void Stop()
        {
            m_timer.Dispose();
            m_timer = null;
            m_Uart.Close();
            m_DGH_Thread.Suspend();
        }

        private void Sample(object unused)
        {
            m_sample.Set();
        }

        public void Run()
        {
            while (true)
            {
                m_sample.WaitOne();
                m_Uart.Write(m_readPressureCommand, 0, m_readPressureCommand.Length);
                int bytesRead = m_Uart.Read(m_Buffer, 0, m_Buffer.Length);
                for (int i = 0; i < bytesRead; i = i + 1)
                {
                    m_State(m_Buffer[i]);
                }
            }
        }

        private void ZeroPad(byte ch)
        {
            if (ch == m_Zeros)
            {
                m_State = Asterisk;
            }
            else 
            {
                m_State = Error_CR;
            }
        }

        private void Asterisk(byte ch)
        {
            if (ch == m_Star)
            {
                // Debug.Print("Asterisk ==> Sign");
                m_State = Sign;
            }
            else
            {
                m_State = Error_CR;
            }
        }

        private void Sign(byte ch)
        {
            if ((ch == m_Plus) || (ch == m_Minus))
            {
                m_Digits = 0;
                m_Mantissa = 0;
                m_Pressure = 0.0;
                m_Negate = (ch == '+') ? false : true;
                m_State = Mantissa;
                // Debug.Print("Sign  ==> Mantissa");
            }
            else
            {
                m_State = Error_CR;
            }
        }

        private void Mantissa(byte ch)
        {
            if ((ch >= m_ZeroDigit) && (ch <= m_NineDigit))
            {
                if (m_Digits < m_MaxMantissaDigits)
                {
                    m_Mantissa = m_Mantissa * 10 + (ch - m_ZeroDigit);
                    m_Digits = m_Digits + 1;
                }
                else
                {
                    m_State = Error_CR;
                }
                if (m_Digits == m_MaxMantissaDigits)
                {
                    // Debug.Print("Mantissa  ==> DecimalPoint");
                    m_State = DecimalPoint;
                }
            }
            else
            {
                m_State = Error_CR;
            }
        }

        private void DecimalPoint(byte ch)
        {
            if (ch == m_Point)
            {
                // Debug.Print("DecimalPoint  ==> Fraction");
                m_Digits = 0;
                m_Fraction = 0;
                m_State = Fraction;
            }
            else
            {
                m_State = Error_CR;
            }
        }
        
        private void Fraction(byte ch)
        {
            if ((ch >= m_ZeroDigit) && (ch <= m_NineDigit)) {
                if (m_Digits < m_MaxFractionDigits)
                {
                    m_Fraction = m_Fraction * 10 + (ch - m_ZeroDigit);
                    m_Digits = m_Digits + 1;
                }
                else
                {
                    m_State = Error_CR;
                }
                if (m_Digits == m_MaxFractionDigits)
                {
                    // Debug.Print("Fraction ==> NormalCR");
                    m_State = Normal_CR;
                }
            } else {
                m_State = Error_CR;
            }
        }

        private void Normal_CR(byte ch)
        {
            if (ch == m_CarriageReturn)
            {
                // Debug.Print("NormalCR ==> Zeropad");
                m_State = ZeroPad;
                m_Pressure = ((double)m_Mantissa) + (((double)m_Fraction) / 100.0);
                if (m_Negate == true)
                    m_Pressure = m_Pressure * -1.0;
                if (OnPressureChange != null)
                    OnPressureChange(m_CurrentLoopCalibrator.Scale(m_CurrentLoopCalibrator.Calibrate(m_Pressure)));
            }
        }

        private void Error_CR(byte ch)
        {
            if (ch == m_CarriageReturn)
            {
                // Debug.Print("ErrorCR ==> ErrorLF");
                m_State = ZeroPad;
            }
        }           

        // Called when an error occurs on the serial port 
        private void UART_ErrorOccurred(object sender, SerialErrorReceivedEventArgs e)
        {
#if DEBUG
            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);
#endif
        }
    }
}
