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

namespace ComService
{
    public class StmService
    {
        public static int error = 0;
        static SerialPort _serialPort;
        private readonly System.Timers.Timer _timer;
        private DateTime _lastScanTime;

        private volatile object _locker = new object();
        private byte[] StopCommand = { 0x7E, 0x05, 0xDA, 0, 0 };//FC, count, crc
        private byte[] StartCommand = { 0x7E, 0x05, 0x12, 0, 0 };//FC, count, crc
        private byte[] StopCommand1 = { 0x7E, 0x05, 0xDA, 0, 0, 0, 0, 0, 0, 0, 0 };//FC, count, crc
        private byte[] StartCommand1 = { 0x7E, 0x05, 0x12, 0, 0, 0, 0, 0, 0, 0, 0 };//FC, count, crc
        private byte[] RefreshCommand = { 0x7E, 0x05, 0x04, 0, 0, 0, 0, 0, 0, 0, 0 };//FC, count, crc
        private byte[] SetDatetimeCommand = { 0x7E, 0x05, 0xC6, 0, 0, 0, 0, 0, 0, 0, 0 };//FC, count, crc
        private byte[] SleepCommand = { 0x7E, 0x05, 0x4D, 0, 0, 0, 0, 0, 0, 0, 0 };//FC, count, crc
        //public StmService(string portname, int baudrate, Parity parity, int databits, StopBits stopbits)
        public StmService()
        {
            _serialPort = new SerialPort();

            _timer = new System.Timers.Timer();
            _timer.Interval = 250;
            _timer.Elapsed += OnTimerElapsed;
        }

        /* Parameters of communication */
        public string ComPort
        {
            get { return _serialPort.PortName; }
            set { _serialPort.PortName = value; }
        }

        public string BaudRate
        {
            get { return _serialPort.BaudRate.ToString(); }
            set { _serialPort.BaudRate = Convert.ToInt32(value); }
        }

        public Parity Parityy
        {
            get { return _serialPort.Parity; }
            set { _serialPort.Parity = value; }
        }

        public int DataBits
        {
            get { return _serialPort.DataBits; }
            set { _serialPort.DataBits = value; }
        }

        public StopBits StopBitss
        {
            get { return _serialPort.StopBits; }
            set { _serialPort.StopBits = value; }
        }

        public ConnectionStates ConnectionState { get; private set; }

        public TimeSpan ScanTime { get; private set; }

        public event EventHandler ValuesRefreshed;

        /* Monitored data */
        public bool MeasureActive { get; private set; }

        public double Temperature { get; private set; }

        public double Humidity { get; private set; }

        public double Pressure { get; private set; }

        public int MeasurementPeriod { get; set; }

        public string ActualDateTime { get; set; }

        public byte Date { get; set; }
        public byte Month { get; set; }
        public byte Year { get; set; }
        public byte Hours { get; set; }
        public byte Minutes { get; set; }
        public byte Seconds { get; set; }


        //Function for Port opening
        public void Connect()
        {
            _serialPort.PortName = ComPort;
            _serialPort.BaudRate = Convert.ToInt32(BaudRate);
            _serialPort.Parity = Parityy;
            _serialPort.DataBits = DataBits;
            _serialPort.StopBits = StopBitss;
            _serialPort.Handshake = Handshake.None;
            _serialPort.ReadTimeout = 2000;
            _serialPort.WriteTimeout = 2000;


            try 
            {
                ConnectionState = ConnectionStates.Connecting;
                _serialPort.Open();
                if(_serialPort.IsOpen)
                {
                    ConnectionState = ConnectionStates.Online;
                    _lastScanTime = DateTime.Now;
                    _timer.Start();
                }
                else
                {
                    Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Connection error: ");
                    ConnectionState = ConnectionStates.Offline;
                }
            }
            catch
            {
                ConnectionState = ConnectionStates.Offline;
                //OnValuesRefreshed();
                throw;
            }
        }

        public void Disconnect()
        {
            if(_serialPort.IsOpen)
            {
                _timer.Stop();
                _serialPort.Close();
                ConnectionState = ConnectionStates.Offline;
                ScanTime = TimeSpan.Zero;
                //OnValuesRefreshed();
            }
        }

        /* Timer stuffs */
        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                _timer.Stop();
                if (ConnectionState == ConnectionStates.Online)
                    ScanTime += DateTime.Now - _lastScanTime;
                RefreshValues();
                OnValuesRefreshed();
            }
            finally
            {
                _timer.Start();
            }
            _lastScanTime = DateTime.Now;
        }

        private void OnValuesRefreshed()
        {
            ValuesRefreshed?.Invoke(this, new EventArgs());
        }


        private void RefreshValues()
        {
            ushort crc = GetCRC(RefreshCommand, 9);
            RefreshCommand[9] = (byte)(crc >> 8);
            RefreshCommand[10] = (byte)(crc & 0x00FF);
            byte[] message = new byte[16];
            lock (_locker)
            {
                Write(RefreshCommand);
                Thread.Sleep(150);
                if (GetResponse(ref message) == 1)
                {
                    if (message[1] == 0x55)
                        MeasureActive = true;
                    else
                        MeasureActive = false;

                    MeasurementPeriod = message[1];
                    Temperature = ((message[2] << 8) | message[3])/100.0;
                    Humidity = ((message[4] << 8) | message[5])/100.0;
                    Pressure = ((message[6] << 8) | message[7])/10.0;
                    ActualDateTime = message[8].ToString() + "-" + message[9].ToString() + "-" + message[10].ToString() + " " + message[11].ToString() + ":" + message[12].ToString() + ":" + message[13].ToString();
                }
                else
                {
                    error++;
                    Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Read error: ");
                }
            }
        }
        /*private void RefreshValues()
        {
            byte[] command = { 0x7E, 0x05, 0x04, 0, 0 };//FC, count, crc
            ushort crc = GetCRC(command, 3);
            command[3] = (byte)(crc >> 8);
            command[4] = (byte)(crc & 0x00FF);
            byte[] message = new byte[10];
            lock (_locker)
            {
                Write(command);
                Thread.Sleep(50);
                if(GetResponse(ref message) == 1)
                {
                    if (message[1] == 0x55)
                        MeasureActive = true;
                    else
                        MeasureActive = false;
                    
                    MeasurementPeriod = message[1];
                    Temperature = (message[2] << 8) | message[3];
                    Humidity = (message[4] << 8) | message[5];
                    Pressure = (message[6] << 8) | message[7];
                }               
                else
                {
                    error++;
                    Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Read error: ");
                }
            }
        }*/

        /* Measurement commands */
        public Task StopMeasurement()
        {
            return Task.Run(() =>
            {
                _timer.Stop();
                //byte[] command = { 0x7E, 0x05, 0xDA, 0, 0 };//FC, count, crc
                ushort crc = GetCRC(StopCommand1, 9);
                StopCommand1[9] = (byte)(crc >> 8);
                StopCommand1[10] = (byte)(crc & 0x00FF);
                /*ushort crc = GetCRC(SleepCommand, 9);
                SleepCommand[9] = (byte)(crc >> 8);
                SleepCommand[10] = (byte)(crc & 0x00FF);*/
                //Stop command
                if (MeasureActive)
                {
                    lock (_locker)
                    {
                        byte[] message = new byte[16];
                        Write(StopCommand1);
                        //Write(StopCommand1);
                        Thread.Sleep(50);
                        if (GetResponse(ref message) == 1)
                        {
                            uint a = 1;
                        }
                        else
                        {
                            error++;
                            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Read error: ");
                        }
                    }
                }
                _timer.Start();
            });
        }

        public Task Sleep()
        {
            return Task.Run(() =>
            {
                _timer.Stop();
                //byte[] command = { 0x7E, 0x05, 0xDA, 0, 0 };//FC, count, crc
                /*ushort crc = GetCRC(StopCommand1, 9);
                StopCommand1[9] = (byte)(crc >> 8);
                StopCommand1[10] = (byte)(crc & 0x00FF);*/
                ushort crc = GetCRC(SleepCommand, 9);
                SleepCommand[9] = (byte)(crc >> 8);
                SleepCommand[10] = (byte)(crc & 0x00FF);
                //Stop command
                
                    lock (_locker)
                    {
                        byte[] message = new byte[16];
                        Write(SleepCommand);
                        //Write(StopCommand1);
                        Thread.Sleep(50);
                        if (GetResponse(ref message) == 1)
                        {
                            uint a = 1;
                        }
                        else
                        {
                            error++;
                            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Read error: ");
                        }
                    }
                
                //_timer.Start();
            });
        }

        public Task StartMeasurement()
        {
            return Task.Run(() =>
            {
                _timer.Stop();
                //byte[] command = { 0x7E, 0x05, 0x12, 0, 0 };//FC, count, crc
                ushort crc = GetCRC(StartCommand1, 9);
                StartCommand1[9] = (byte)(crc >> 8);
                StartCommand1[10] = (byte)(crc & 0x00FF);
                //Start command
                if (!MeasureActive)
                {
                    lock (_locker)
                    {
                        byte[] message = new byte[16];
                        Write(StartCommand1);
                        Thread.Sleep(50);
                        if (GetResponse(ref message) == 1)
                        {
                            uint a = 1;
                        }
                        else
                        {
                            error++;
                            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Read error: ");
                        }
                    }
                
                }
                _timer.Start();
            });
        }  

        public Task SetDatetime()
        {
            return Task.Run(() =>
            {
                _timer.Stop();
                //byte[] command = { 0x7E, 0x05, 0x12, 0, 0 };//FC, count, crc
                
                SetDatetimeCommand[3] = Date;
                SetDatetimeCommand[4] = Month;
                SetDatetimeCommand[5] = Year;
                SetDatetimeCommand[6] = Hours;
                SetDatetimeCommand[7] = Minutes;
                SetDatetimeCommand[8] = Seconds;
                ushort crc = GetCRC(SetDatetimeCommand, 9);
                SetDatetimeCommand[9] = (byte)(crc >> 8);
                SetDatetimeCommand[10] = (byte)(crc & 0x00FF);
                //Start command
                if (!MeasureActive)
                {
                lock (_locker)
                {
                    byte[] message = new byte[16];
                    Write(SetDatetimeCommand);
                    Thread.Sleep(50);
                    if (GetResponse(ref message) == 1)
                    {
                        uint a = 1;
                    }
                    else
                    {
                        Thread.Sleep(1000);
                        error++;
                        Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "\t Read error: ");
                    }
                }
                
                }
                _timer.Start();
            });
        }

        /* Helper functions */
        private void Write(byte[] command)
        {
            try
            {
                _serialPort.DiscardInBuffer();
                _serialPort.DiscardOutBuffer();
                _serialPort.Write(command, 0, command.Length);
                
            }
            catch
            {
                throw;
            }
        }

        private uint GetResponse(ref byte[] message)
        {
            uint count = 0;
            byte r_byte;
            uint thrown_bytes = 0;
            if (_serialPort.BytesToRead == 0)
            {
                SpinWait.SpinUntil(() => _serialPort.BytesToRead > 0, 5000);
                //continue;
            }
            while (_serialPort.BytesToRead != 0)
            {
                try
                {
                    
                    r_byte = (byte)_serialPort.ReadByte();
                    if (count == 0 && r_byte == 0x7E)
                    {
                        message[count++] = r_byte;
                    }
                    else if (count != 0)
                        message[count++] = r_byte;
                    else
                    {
                        thrown_bytes++;
                    }
                    if (thrown_bytes >= 2)
                        break;
                    if (count >= 16)
                        break;
                }
                catch
                {
                    throw;
                }
            }
            if (count > 2)
            {
                ushort crc = (ushort)((ushort)(message[count - 2] << 8) | ((ushort)message[count - 1]));
                if (crc == GetCRC(message, count - 2))
                    return 1;
                else
                    return 0;
            }
            return 0;
        }


        /*private uint GetResponse(ref byte[] message)
        {
            uint count = 0;
            byte r_byte;
            uint thrown_bytes = 0;
            while (_serialPort.BytesToRead != 0)
            {              
                try
                {
                    if (_serialPort.BytesToRead == 0)
                    {
                        SpinWait.SpinUntil(() => _serialPort.BytesToRead > 0, 50);
                        continue;
                    }
                    r_byte = (byte)_serialPort.ReadByte();
                    if(count == 0 && r_byte == 0x7E)
                    {
                        message[count++] = r_byte;
                    }
                    else if(count!=0)
                        message[count++] = r_byte;
                    else
                    {
                        thrown_bytes++;
                    }
                    if (thrown_bytes >= 2)
                        break;
                    if (count >= 10)
                        break;
                }
                catch
                {
                    throw;
                }
            }
            if(count > 2)
            {
                ushort crc = (ushort)((ushort)(message[count - 2] << 8) | ((ushort)message[count - 1]));
                if (crc == GetCRC(message, count - 2))
                    return 1;
                else
                    return 0;
            }
            return 0;

            
        }*/

        private static ushort GetCRC(byte[] message, uint length)
        {
            ushort CRCFull = 0xFFFF;
            char CRCLSB;

            if (length < 2)
                return 0;
            for (int i = 0; i < (length) - 2; i++)
            {
                CRCFull = (ushort)(CRCFull ^ message[i]);

                for (int j = 0; j < 8; j++)
                {
                    CRCLSB = (char)(CRCFull & 0x0001);
                    CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);

                    if (CRCLSB == 1)
                        CRCFull = (ushort)(CRCFull ^ 0xA001);
                }
            }

            //CRCFull = (ushort)(((CRCFull & 0x00FF) << 8) | (CRCFull >> 8));

            return CRCFull;
        }

        public Task WriteBaudRate(int baudRate)
        {
            return Task.Run(() =>
            {
                BaudRate = baudRate.ToString();
            });
        }

        public Task WriteDataBits(int databits)
        {
            return Task.Run(() =>
            {
                DataBits = databits;
            });
        }

        public Task WriteParity(Parity parity)
        {
            return Task.Run(() =>
            {
                Parityy = parity;
            });
        }

        public Task WriteStopBits(StopBits stopbits)
        {
            return Task.Run(() =>
            {
                StopBitss = stopbits;
            });
        }

        public Task WriteComPort(string comport)
        {
            return Task.Run(() =>
            {
                ComPort = comport;
            });
        }

    }
}
