using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;

using MECF.Framework.Common.Communications;
using MECF.Framework.Common.Equipment;

using Aitex.Core.RT.SCCore;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.Device;
using Aitex.Core.RT.Log;
using Venus_Core;
using MECF.Framework.Common.Device.Bases;
namespace Venus_RT.Devices
{
    class AdixenTurboPump : TurboPump
    {
        public enum Operation
        {
            SetAddress,
            GetChecksum,
            GetFaultList,
            SetDatalogInterval,
            EnableDatalog,
            EnableEcho,
            DisableEcho,
            IdentifyDevice,
            IdentifyProduct,
            GetCurrentValues,
            SwitchSpeedToNormal,
            SwitchSpeedToStandbyValue,
            SetStandbySpeed,
            GetParametersState,
            GetIntervalParametersState,
            SetPumpWorkingTime,
            SetElectronicWorkingTime,
            SetStartDelay,
            SetTimeToVent,
            SetVentingTime,
            SetSpeedThreshold,
            SetControlTemperatrue,
            StopPumpThermostage,
            SetBearingThreshold,
            GetCurrentSpeed,
            StartPumpRotation,
            StopPump,
            SetVersions,
            SetAnalogOutput,
            SetTemperatureUnit,
            SetBuzzerOpt,
            SetCommandMode,
            SetBraking,
            Invalid,
        }

        private readonly Dictionary<Operation, string> _noneParaCommandOp = new Dictionary<Operation, string>
        {
            {Operation.EnableDatalog,               "#{0:D3}DLR\r" },
            {Operation.EnableEcho,                  "#{0:D3}ECHON\r" },
            {Operation.DisableEcho,                 "#{0:D3}ECHOFF\r" },
            {Operation.SwitchSpeedToNormal,         "#{0:D3}NSP\r" },
            {Operation.SwitchSpeedToStandbyValue,   "#{0:D3}SBY\r" },
            {Operation.StopPumpThermostage,         "#{0:D3}SET31,30\r" },
            {Operation.StartPumpRotation,           "#{0:D3}TMPON\r" },
            {Operation.StopPump,                    "#{0:D3}TMPOFF\r" },
        };

        private readonly Dictionary<Operation, string> _readDataCommandOp = new Dictionary<Operation, string>
        {
            {Operation.GetChecksum,                 "#{0:D3}CHKVS\r" },
            {Operation.GetFaultList,                "#{0:D3}DEF\r" },
            {Operation.IdentifyDevice,              "#{0:D3}IDN\r" },
            {Operation.IdentifyProduct,             "#{0:D3}IDP\r" },
            {Operation.GetCurrentValues,            "#{0:D3}LEV10\r" },
            {Operation.GetParametersState,          "#{0:D3}SEL10\r" },
            {Operation.GetCurrentSpeed,             "#{0:D3}SPD\r" },
            {Operation.GetIntervalParametersState,  "#{0:D3}STA\r" },
        };

        private readonly Dictionary<Operation, string> _setDataCommandOp = new Dictionary<Operation, string>
        {
            {Operation.SetAddress,                  "#{0:D3}ADR {1:D3}\r" },
            {Operation.SetDatalogInterval,          "#{0:D3}DLI {1:D3}\r" },
            {Operation.SetStandbySpeed,             "#{0:D3}RPM,{1:D4}\r" },
            {Operation.SetPumpWorkingTime,          "#{0:D3}SET10,{1:D5}\r"  },
            {Operation.SetElectronicWorkingTime,    "#{0:D3}SET11,{1:D5}\r"  },
            {Operation.SetStartDelay,               "#{0:D3}SET13,{1:D5}\r" },
            {Operation.SetTimeToVent,               "#{0:D3}SET14,{1:D5}\r" },
            {Operation.SetVentingTime,              "#{0:D3}SET15,{1:D4}\r" },
            {Operation.SetSpeedThreshold,           "#{0:D3}SET30,{1:D2}\r" },
            {Operation.SetControlTemperatrue,       "#{0:D3}SET31,{1:D2}\r" },
            {Operation.SetBearingThreshold,         "#{0:D3}SET32,{1:D3}\r" },

            {Operation.SetAnalogOutput,             "#{0:D3}OPT01,{1:D1}\r" },
            {Operation.SetTemperatureUnit,          "#{0:D3}OPT02,{1:D1}\r" },
            {Operation.SetBuzzerOpt,                "#{0:D3}OPT11,{1:D1}\r" },
            {Operation.SetCommandMode,              "#{0:D3}OPT14,{1:D1}\r" },
            {Operation.SetBraking,                  "#{0:D3}OPT25,{1:D1}\r" },
        };

        private readonly Dictionary<string, string> _comError = new Dictionary<string, string>
        {
            {"Err0",  "adjustment error(out of bounds)" },
            {"Err1",  "command error (syntax) " },
            {"Err2",  "parameter error (e.g. non hexadecimal character)" },
            {"Err3",  "context error" },
            {"Err4",  "checksum error" },
        };

        private readonly ushort PUMP_ON_FLAG            = 0x02;
        private readonly ushort PUMP_ERROR_FLAG         = 0x40;
        private readonly ushort ISO_VALVE_OPEN_FLAG     = 0x01;
        private readonly ushort WATER_VALVE_OEPN_FLAG   = 0x08;
        private readonly ushort VALVE_AT_SPEED_FLAG     = 0x40;

        public override bool IsRunning
        {
            get
            {
                return (_pumpStatus & PUMP_ON_FLAG) == PUMP_ON_FLAG;
            }
        }

        public override bool IsError
        {
            get
            {
                return (_pumpStatus & PUMP_ERROR_FLAG) == PUMP_ERROR_FLAG;
            }
        }

        public bool IsISOValveOpen
        {
            get
            {
                return (_valveStatus & ISO_VALVE_OPEN_FLAG) == ISO_VALVE_OPEN_FLAG;
            }
        }

        public bool IsWaterValveOpen
        {
            get
            {
                return (_valveStatus & WATER_VALVE_OEPN_FLAG) == WATER_VALVE_OEPN_FLAG;
            }
        }

        public bool AtSpeed
        {
            get
            {
                return (_valveStatus & VALVE_AT_SPEED_FLAG) == VALVE_AT_SPEED_FLAG;
            }
        }

        public string sTemperature
        {
            get { return Temperature.ToString(); }
        }

        public string RotationalSpeed
        {
            get { return Speed.ToString(); }
        }

        private readonly int _timeout = 2000;
        private readonly int _address = 0;
        private readonly int _internal_parameters_length = 78;
        private readonly AsyncSerialPort _serial;
        private Stopwatch _queryWatch = new Stopwatch();
        private ConcurrentQueue<Operation> _queCommands = new ConcurrentQueue<Operation>();

        private Operation _lastCommand = Operation.Invalid;
        private string _lastAlarmString = string.Empty;

        private Regex _rex_ok = new Regex(@"#\d{1,3},OK");
        private Regex _rex_err = new Regex(@"#\d{1,3},Err");

        private byte _pumpStatus = 0;
        private byte _valveStatus = 0;

        public AdixenTurboPump(ModuleName mod)
        {
            Name = VenusDevice.TurboPump.ToString();
            Module = mod.ToString();
            var _PortNum = SC.GetStringValue($"{mod}.TurboPump.Port");
            _serial = new AsyncSerialPort(_PortNum, 9600, 8, System.IO.Ports.Parity.None, System.IO.Ports.StopBits.One, "\r", false);
        }

        public override bool Initialize()
        {
            if (!_serial.Open())
            {
                LOG.Write(eEvent.ERR_TURBO_PUMP, Module, "Adixen Turbo Pump 串口无法打开");
                return false;
            }

            _serial.OnDataChanged += OnPortDataChanged;
            _serial.OnBinaryDataChanged += OnPortBinaryDataChanged;
            _serial.OnErrorHappened += OnErrorOccurred;

            return true;
        }

        public override void Monitor()
        {
            if(_lastCommand == Operation.Invalid)
            {
                if(_queCommands.IsEmpty)
                {
                    _sendCommand(Operation.GetIntervalParametersState);
                    _lastCommand = Operation.GetIntervalParametersState;
                }
                else
                {
                    Operation command;
                    if(_queCommands.TryDequeue(out command))
                    {
                        _sendCommand(command);
                        _lastCommand = command;
                    }
                }
            }
            else if(_queryWatch.ElapsedMilliseconds >= _timeout)
            {
                _noRepeatAlarm($"last command: {_lastCommand} timeout");
                Reset();
            }
        }

        public override void Reset()
        {
            Operation result;
            while (_queCommands.TryDequeue(out result)) ;

            //_serial.ClearPortBuffer();
            _lastCommand = Operation.Invalid;
        }

        public override void Terminate()
        {
            _serial?.Close();
        }


        private void OnErrorOccurred(string obj)
        {
            _noRepeatAlarm($"[{Module}] Adixen Turbo Pump serial port error: [{obj}]");
        }

        private void OnPortDataChanged(string obj)
        {
            try
            {
                if (obj.Substring(0, 2) == "OK")
                {
                    _lastCommand = Operation.Invalid;
                    return;
                }

                if(obj.Substring(0, 3) == "Err")
                {
                    if (_comError.ContainsKey(obj.Substring(0, 4)))
                    {
                        _noRepeatAlarm($"Adixen Turbo Pump communication error: {_comError[obj.Substring(0, 4)]}");
                    }
                    else
                    {
                        _noRepeatAlarm($"Adixen Turbo Pump unknown communication error: {obj}");
                    }

                    _lastCommand = Operation.Invalid;
                    return;
                }

                if(_lastCommand == Operation.Invalid)
                {
                    LOG.Write(eEvent.ERR_TURBO_PUMP, Module, $"Adixen Turbo Pump unexpected communication data: {obj}");
                    return;
                }

                var result_data = obj.Trim().Split(',');
                switch(_lastCommand)
                {
                    case Operation.GetCurrentSpeed:
                        if(result_data.Length >= 2)
                        {
                            var str_speed = result_data[1].Split();
                            int speed = 0;
                            if (int.TryParse(str_speed[0], out speed))
                            {
                                Speed = speed;
                            }
                        }
                        break;
                    case Operation.GetIntervalParametersState:
                        if(result_data.Length >= 9)
                        {
                            int speed = 0;
                            int temp = 0;
                            if(int.TryParse(result_data[0], out speed))
                            {
                                Speed = speed;
                            }

                            if(int.TryParse(result_data[8], out temp))
                            {
                                Temperature = temp;
                            }
                        }
                        break;
                }

                _lastCommand = Operation.Invalid;

            }
            catch (Exception ex)
            {
                _noRepeatAlarm($"[{Module}] Adixen Turbo Pump error: [{ex.Message}], Data: {obj}");
            }
        }

        private void OnPortBinaryDataChanged(byte[] obj)
        {
            bool bValidate = false;
            for(int i=0; i< obj.Length; i++)
            {
                if(obj[i] == (byte)'#')
                {
                    int nOffset = 5;
                    if(_lastCommand == Operation.GetIntervalParametersState && obj.Length - i >= _internal_parameters_length)
                    {
                        _pumpStatus = obj[i + 6];
                        _valveStatus = obj[i + 7];
                        nOffset = 9;
                    }
                    
                    byte[] array = new byte[obj.Length - i - nOffset];
                    Array.Copy(obj, i + nOffset, array, 0, obj.Length - i - nOffset);
                    var strData = System.Text.Encoding.Default.GetString(array);
                    OnPortDataChanged(strData);
                    bValidate = true;
                    break;
                }
            }

            if(!bValidate)
            {
                var strData = System.Text.Encoding.Default.GetString(obj);
                _noRepeatAlarm($"Receive invalidate data:{strData} ");
            }
        }

        public override void SetPumpOnOff(bool on)
        {
            _queCommands.Enqueue(on ? Operation.StartPumpRotation : Operation.StopPump);
        }

        private bool _sendCommand(Operation op)
        {
            if (_noneParaCommandOp.ContainsKey(op))
            {
                var cmd = string.Format(_noneParaCommandOp[op], _address);
                return _sendCmd(cmd);
            }
            else if (_readDataCommandOp.ContainsKey(op))
            {
                var cmd = string.Format(_readDataCommandOp[op], _address);
                return _sendCmd(cmd);
            }

            _noRepeatAlarm($"Adixen Turbo Pump: The {op} command need parameters");
            return false;
        }
        private bool _sendCommand(Operation op, int data)
        {
            if (_setDataCommandOp.ContainsKey(op))
            {
                var cmd = string.Format(_setDataCommandOp[op], _address, data);
                return _sendCmd(cmd);
            }

            _noRepeatAlarm($"Adixen Turbo Pump: The command {op} does not need one parameter");
            return false;
        }

        private bool _sendCmd(string cmd)
        {
            _queryWatch.Restart();
            return _serial.Write(cmd);
        }

        private void _noRepeatAlarm(string alarm)
        {
            if(_lastAlarmString != alarm)
            {
                _lastAlarmString = alarm;
                LOG.Write(eEvent.ERR_TURBO_PUMP, Module, alarm);
            }
        }
    }
}