using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.IO;

namespace PressureGenerator
{
    public class PACE6000driver : IPressureGenerator
    {
        #region --------------------------- Private global values ---------------------------
        private string DeviceName = "PACE 6000";
        #region ==Commands==
        /// <summary>
        /// Start program
        /// </summary>
        private const string START = "START";

        /// <summary>
        /// Select units
        /// </summary>
        private const string UNITS = "UNITS";

        /// <summary>
        /// Select rate [units/min]
        /// </summary>
        private const string RATE = "RATE";

        /// <summary>
        /// No overshoot
        /// </summary>
        private const string SETTLING = "SETTLING";

        /// <summary>
        /// Set-point, x units
        /// </summary>
        private const string SETPOINT = "SET POINT";

        /// <summary>
        /// Controller ON
        /// </summary>
        private const string CONTROL = "CONTROL";

        /// <summary>
        /// Wait in limits condition
        /// </summary>
        private const string WAITINLIMITS = "WAIT IN LIMITS";

        /// <summary>
        /// Switch to measure (controller off)
        /// </summary>
        private const string MEASURE = "MEASURE";

        /// <summary>
        /// Venting
        /// </summary>
        private const string VENT = "VENT";
        #endregion
        #endregion

        /// <summary>
        /// Commands update rate 8 per second
        /// </summary>
        private TimeSpan COMMS_UPDATE = TimeSpan.FromSeconds(1d / 8d);

        /// <summary>
        /// Altitude minimal limit [ft]
        /// </summary>
        private const Int32 ALT_ftMIN = -3000;
        /// <summary>
        /// Altitude maximal limit [ft]
        /// </summary>
        private const Int32 ALT_ftMAX = 55000;

        /// <summary>
        /// Altitude minimal limit [mbar]
        /// </summary>
        private const Int32 ALT_mbarMIN = 35;
        /// <summary>
        /// Altitude maximal limit [mbar]
        /// </summary>
        private const Int32 ALT_mbarMAX = 1150;

        /// <summary>
        /// Airspeed maximal limit [knots]
        /// </summary>
        private const UInt32 AIR_MAX = 650;

        private string[] PressInitComm = new string[]
            {
                string.Format(":SOUR:PRES:RANG {0}3.50barg{0}",'"'), 
                string.Format(":SOUR:PRES:RANG {0}4.50bara{0}",'"'),
                string.Format(":SOURCE1:PRES 980.000"),     
                string.Format(":SOURCE2:PRES 0.00000"),    
                string.Format(":OUTP1 1"),                   
                string.Format(":OUTP2 1")
            };

        private string[] RunCommand = new string[]
            {
                string.Format(":OUTP1 1"),                   
                string.Format(":OUTP2 1")
            };

        private string[] StopCommand = new string[]
            {
                string.Format(":OUTP1 0"),                   
                string.Format(":OUTP2 0")
            };

        private const string CommandReadStatic = ":SENS1:PRES?";
        private const string CommandReadPitot = ":SENS2:PRES?";
        private const string CommandSetStatic = ":SOURCE1:PRES ";
        private const string CommandSetPitot = ":SOURCE2:PRES ";

        private const char CR = (char)0x0D;

        private const int MBpS = 9600;
        private const int BITS = 8;
        private const Parity PARITY = Parity.None;
        private const StopBits STOPBIT = StopBits.One;

        private PressGen_t Communication;
        private Object Communication_Locker = new object();

        private const int WAIT = 100; // [ms]

        #region --------------------------- Public global values ---------------------------
        #endregion

        #region --------------------------- Structures&Enums ---------------------------
        private struct PressGen_t
        {
            public State_e State;
            public SerialPort SP;
            public string PortName;
            public int Baudrate;
            public int DataBits;
            public StopBits stopBits;
            public Parity parity;
            public string RXmsg;
            public string TXmsg;
            public bool IsPortOpen { get { return ((this.SP == null) ? (false) : ((this.SP.IsOpen))); } }

            public float Static;
            public float Pitot;

            public float setpointStatic;
            public float setpointPitot;
        }

        private enum State_e
        {
            Idle,
            Reading,
            Sending,
            Updating
        }
        #endregion

        #region --------------------------- Construcotrs&Destructor ---------------------------
        public PACE6000driver()
        {
            Communication = new PressGen_t()
                {
                    Baudrate = MBpS,
                    DataBits = BITS,
                    parity = PARITY,
                    stopBits = STOPBIT,
                    RXmsg = string.Empty,
                    TXmsg = string.Empty,
                    State = State_e.Idle
                };
        }

        ~PACE6000driver()
        {

        }
        #endregion

        #region --------------------------- Private sector ---------------------------
        private bool ConnectToDevice()
        {
            bool _res = false;

            try
            {
                Communication.SP = new SerialPort()
                    {
                        PortName = Communication.PortName,
                        BaudRate = Communication.Baudrate,
                        DataBits = Communication.DataBits,
                        StopBits = Communication.stopBits,
                        Parity = Communication.parity
                    };
                Communication.SP.Open();
                _res = Communication.SP.IsOpen;
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.ConnectToDevice()", "Exception", ex.ToString());
            }

            return _res;
        }

        private int Send(string msg)
        {
            System.Diagnostics.Stopwatch _sp = new System.Diagnostics.Stopwatch();
            _sp.Start();

            try
            {
                msg += CR;
                if (!CheckMsg(msg))
                    throw new InvalidDataException();
                if (Communication.IsPortOpen)
                {
                    Communication.TXmsg = msg;
                    Task t = new Task(TASK_send);
                    t.Start();
                    t.Wait(WAIT);
                    if(!t.IsCompleted)
                        throw new TaskCanceledException();
                    t.Dispose();
                }
            }
            catch (InvalidDataException ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Send(string msg)", "Cannot send message - bad format", ex.ToString());
            }
            catch (TaskCanceledException ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Send(string msg)", string.Format("Message is not send after {0}ms",WAIT), ex.ToString());
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Send(string msg)", "Exception", ex.ToString());
            }

            _sp.Stop();
            return Convert.ToInt32(_sp.ElapsedMilliseconds);
        }

        /// <summary>
        /// Reads all data in receive buffer. Wait_time = WaitUntilAsk + WAIT
        /// </summary>
        /// <param name="WaitUntilAsk">How long the function must wait to check receive buffer.</param>
        /// <param name="Answer"></param>
        /// <returns></returns>
        private string Read(int WaitUntilAsk, string Answer = null)
        {
            try
            {
                string _res = string.Empty;

                if (Communication.IsPortOpen)
                {
                    System.Threading.Thread.Sleep(WaitUntilAsk);
                    Task t = new Task(TASK_read);
                    t.Start();
                    t.Wait(WAIT);
                    if (!t.IsCompleted)
                        throw new TaskCanceledException();
                    t.Dispose();

                    string _rxmsg = Communication.RXmsg;
                    Communication.RXmsg = string.Empty;

                    if (CheckMsg(_rxmsg, Answer))
                    {
                        _res = _rxmsg;
                    }
                    //else
                    //    throw new InvalidDataException();
                }

                return _res;
            }
            catch (TaskCanceledException ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Read(string Answer = null)", string.Format("Nothing to read, wait time = {0}ms", WAIT + WaitUntilAsk), ex.ToString());
                return string.Empty;
            }
            catch (InvalidDataException ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Read(string Answer = null)", string.Format("Receive message is in bad format {0}",Communication.RXmsg), ex.ToString());
                return string.Empty;
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Read(string Answer = null)", "Exception", ex.ToString());
                return string.Empty;
            }
        }

        /// <summary>
        /// Call it as Task. Task lock Communication_Locker, send all bytes from transmitt buffer to pressure generator. Task does not contain waiting.
        /// State changes -> Sending -> Idle
        /// </summary>
        private void TASK_send()
        {
            try
            {
                if (Communication.TXmsg != string.Empty)
                {
                    lock (Communication_Locker)
                    {
                        Communication.State = State_e.Sending;

                        char[] _msg = Communication.TXmsg.ToCharArray();
                        Communication.TXmsg = string.Empty;
                        byte[] _rxbuff = new byte[_msg.Length];
                        for (int i = 0; i < _msg.Length; i++)
                        {
                            _rxbuff[i] = Convert.ToByte(_msg[i]);
                        }

                        Communication.SP.BaseStream.Write(_rxbuff, 0, _rxbuff.Length);

                        Communication.State = State_e.Idle;
                    }
                }
            }
            catch(Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.TASK_send()", "Exception", ex.ToString());
            }
        }

        /// <summary>
        /// Call it as Task. Task lock Communication_Locker, reads all bytes in receive buffer. Task does not contain waiting.
        /// State changes -> Reading -> Idle
        /// </summary>
        private void TASK_read()
        {
            try
            {
                lock (Communication_Locker)
                {
                    Communication.State = State_e.Reading;

                    if (Communication.RXmsg != string.Empty)
                        Communication.RXmsg = string.Empty;

                    List<byte> _rxbuffer = new List<byte>(256);
                    int _checking = 0;

                    while (Communication.SP.BytesToRead > 0)
                    {
                        byte[] _onebyte = new byte[1];
                        Communication.SP.BaseStream.Read(_onebyte, 0, 1);
                        _rxbuffer.Add(_onebyte[0]);

                        _checking++;
                        if (_checking >= 256)
                            break;
                    }

                    foreach (byte b in _rxbuffer)
                    {
                        Communication.RXmsg += Convert.ToString(Convert.ToChar(b));
                    }

                    Communication.State = State_e.Idle;
                }
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.TASK_read()", "Exception", ex.ToString());
            }
        }

        /// <summary>
        /// Checks recieve message from pressure generator.
        /// </summary>
        /// <param name="msg">Message from pressure generator</param>
        /// <param name="ACK">Message must contains this string</param>
        /// <returns></returns>
        private bool CheckMsg(string msg, string ACK = null)
        {
            bool _res = true;
            char[] answer = msg.ToCharArray();
            if (!msg.Contains(":") || !msg.Contains(CR))
                _res = false;

            if (ACK != null)
                if (!msg.Contains(ACK))
                    _res = false;

            return _res;
        }

        /// <summary>
        /// Provides initialization commands
        /// </summary>
        private void Initilization()
        {
            try
            {
                iSTP_DebugConsole.DebugConsole.WriteLine(string.Format("{0} - Initialization", PressureName));
                foreach (string msg in PressInitComm)
                {
                    iSTP_DebugConsole.DebugConsole.WriteLine(string.Format("Configurating -> msg = {0}", msg));

                    //Send
                    int _wait = Send(msg);

                    //Read answer
                    string _answ = Read(_wait);
                    //if (_answ != string.Empty)
                        iSTP_DebugConsole.DebugConsole.WriteLine(string.Format("PASSED {0}", _answ));
                    //else
                    //    iSTP_DebugConsole.DebugConsole.WriteLine("FAILED");
                }
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Initilization()", "Exception", ex.ToString());
            }
        }

        /// <summary>
        /// Update Static pressure
        /// </summary>
        private void UpdateStatic()
        {
            try
            {
                //Send
                int _wait = Send(CommandReadStatic);

                //Read answer
                string _answ = Read(_wait, CommandReadStatic.Substring(0, 5));

                //Decode
                if (_answ != string.Empty)
                {
                    Decode(_answ);
                }
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.ReadPitot()", "Exception", ex.ToString());
            }
        }

        /// <summary>
        /// Updates Pitot pressure
        /// </summary>
        private void UpdatePitot()
        {
            try
            {
                //Send
                int _wait = Send(CommandReadPitot);

                //Read answer
                string _answ = Read(_wait, CommandReadPitot.Substring(0,5));

                //Decode
                if (_answ != string.Empty)
                {
                    Decode(_answ);
                }
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.ReadPitot()", "Exception", ex.ToString());
            }
        }

        /// <summary>
        /// Decodes message from PACE 6000. Rewrite actual value.
        /// </summary>
        /// <param name="msg">Message from PACE 6000</param>
        private void Decode(string msg)
        {
            string[] parts = msg.Split('\n');
            foreach (string s in parts)
            {
                if (s.Contains("\r"))
                    s.Replace("\r", null);
                string[] drPartsminime = s.Split(' ');
                if (drPartsminime.Length == 2)
                {
                    if (!drPartsminime[0].Contains(":SENS2"))
                        Communication.Static = Convert.ToSingle(drPartsminime[1]);
                    else
                        Communication.Pitot = Convert.ToSingle(drPartsminime[1]);
                }
            }
        }

        /// <summary>
        /// Set new setpoint for Static or Pitot pressure due bool value
        /// </summary>
        /// <param name="setpoint">New setpoint</param>
        /// <param name="isStatic">True - setpoint for static pressure</param>
        private void SetValuesProvider(float setpoint, bool isStatic)
        {
            if(isStatic)
            {
                Communication.setpointStatic = setpoint;
                //Send
                int _wait = Send(CommandSetStatic + setpoint.ToString());

                //Read answer
                string _answ = Read(_wait);
            }
            else
            {
                Communication.setpointPitot = setpoint;
                //Send
                int _wait = Send(CommandSetPitot + (Communication.setpointStatic - Communication.setpointPitot).ToString());

                //Read answer
                string _answ = Read(_wait);
            }
        }

        /// <summary>
        /// Sets both setpoints
        /// </summary>
        /// <param name="Static">New static set point</param>
        /// <param name="Pitot">New pitot set point</param>
        private void SetValuesProvider(float Static, float Pitot)
        {
            //Send
            int _wait = Send(CommandSetStatic + Static.ToString());

            //Read answer
            string _answ = Read(_wait);

            //Send
            _wait = Send(CommandSetPitot + Pitot.ToString());

            //Read answer
            _answ = Read(_wait);
        }

        #endregion

        #region --------------------------- Protected sector ---------------------------
        #endregion

        #region --------------------------- Public sector ---------------------------
        /// <summary>
        /// Opens port
        /// </summary>
        /// <param name="portName">Port name</param>
        /// <param name="baudRate">Ignored, the method uses default value.</param>
        /// <param name="dataBits">Ignored, the method uses default value.</param>
        /// <param name="stopBits">Ignored, the method uses default value.</param>
        /// <param name="parity">Ignored, the method uses default value.</param>
        /// <returns>TRUE - port is open</returns>
        public bool Connect
            (
             string portName,
             string baudRate,
             string dataBits,
             string stopBits,
             string parity
            )
        {
            bool _res = false;
            try
            {
                Communication.PortName = portName;
                _res = ConnectToDevice();
                if(_res)
                    Initilization();
            }
            catch (Exception ex)
            {
                PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Connect()", "Can't open port", ex.ToString());
            }
            return _res;
        }

        public void Disconnect()
        {
            if (Communication.SP != null)
            {
                if (Communication.SP.IsOpen)
                    Communication.SP.Close();
                Communication.SP.Dispose();
                Communication.SP = null;
            }
        }

        /// <summary>
        /// Not supported
        /// </summary>
        /// <param name="iIP">Not supported</param>
        /// <param name="port">Not supported</param>
        /// <returns>Always FALSE</returns>
        public bool Connect(System.Net.IPAddress iIP, int port)
        {
            PressError.FillErrorStruct("PressureGenerator.PACE6000driver.Connect()", "Can't open port", "This device does not support ethernet communication");
            return false;
        }

        /// <summary>
        /// Reads actual value
        /// </summary>
        /// <returns>TRUE - reading was successful</returns>
        private bool CanUpdate = true;
        public bool ManualUpdate()
        {
            if (CanUpdate)
            {
                CanUpdate = false;
                UpdateStatic();
                UpdatePitot();
                CanUpdate = true;
            }
            return true;
        }

        /// <summary>
        /// Returns last actual Static pressure [mbar]
        /// </summary>
        /// <returns>last actual Static pressure [mbar]</returns>
        public float GetActStatic()
        {
            return Communication.Static;
        }

        /// <summary>
        /// Returns last actual Pitot pressure [mbar]
        /// </summary>
        /// <returns>last actual Pitot pressure [mbar]</returns>
        public float GetActPitot()
        {
            return Communication.Pitot;
        }

        /// <summary>
        /// Commands new setpoint for Static pressure [mbar]
        /// </summary>
        /// <param name="iStatic">new setpoint for Static pressure [mbar]</param>
        /// <returns>TRUE - success</returns>
        public bool SetStatic(float iStatic)
        {
            SetValuesProvider(iStatic, true);
            return true;
        }

        /// <summary>
        /// Commands new setpoint for Pitot pressure [mbar]
        /// </summary>
        /// <param name="iPitot">new setpoint for Pitot pressure [mbar]</param>
        /// <returns>TRUE - success</returns>
        public bool SetPitot(float iPitot)
        {
            SetValuesProvider(iPitot, false);
            return true;
        }

        /// <summary>
        /// Start pressure generator
        /// </summary>
        /// <returns>TRUE - success</returns>
        public bool Run()
        {
            foreach (string s in RunCommand)
            {
                int wait = Send(s);
                Read(wait);
            }
            return true;
        }

        /// <summary>
        /// Stop pressure generator
        /// </summary>
        /// <returns>TRUE - success</returns>
        public bool Stop()
        {
            foreach (string s in StopCommand)
            {
                int wait = Send(s);
                Read(wait);
            }
            return true;
        }

        /// <summary>
        /// Returns device name;
        /// </summary>
        public string PressureName { get { return DeviceName; } }

        public void SendCommand(string command)
        {
            int wait = Send(command);
            string answer = Read(wait);
            iSTP_DebugConsole.DebugConsole.WriteLine(string.Format("Send-> {0} ... Answer-> {1}", command, Communication.RXmsg));
        }
        #endregion
    }
}
