using System;
using System.Diagnostics;
using System.Xml;
using Aitex.Core.Common.DeviceData;
using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Device;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.IOCore;
using Aitex.Core.RT.OperationCenter;
using Aitex.Core.RT.SCCore;
using Aitex.Core.RT.Tolerance;
using Aitex.Core.Util;
using MECF.Framework.Common.DBCore;
using Venus_RT.Modules;
using Aitex.Core.RT.Log;

namespace Venus_RT.Devices.IODevices
{
    //will be upgrade later
    public class MfcBase1 : BaseDevice, IDevice
    {
        public virtual double SetPoint { get; set; }
        public virtual double FeedBack { get; set; }
        public virtual bool IsOutOfTolerance { get; }
        public virtual double Scale { set; get; }
        public virtual string DisplayName { set; get; }

        private float _percent10Calculate;
        private float _percent20Calculate;
        private float _percent30Calculate;
        private float _percent40Calculate;
        private float _percent50Calculate;
        private float _percent60Calculate;
        private float _percent70Calculate;
        private float _percent80Calculate;
        private float _percent90Calculate;
        private float _percent100Calculate;
        private float _verificationCalculate;
        private float _verificationSetpoint;
        private string _verificationResult = "";

        public MfcBase1()
        {
        }

        public virtual bool Initialize()
        {
            DATA.Subscribe($"{Module}.{Name}.VerificationResult", () => _verificationResult);
            return true;
        }

        public virtual void Monitor()
        {
        }

        public virtual void Reset()
        {
        }

        public virtual void Terminate()
        {
        }

        public virtual void Ramp(int time)
        {
        }

        public virtual void Ramp(double target, int time)
        {
        }

        public virtual void StopRamp()
        {
        }

        public virtual void SetVerificationResult(float setpoint, float calculateFlow, bool saveResult, double time, double deviation, bool result)
        {
            //ten points
            var delta = Scale / 10;
            _verificationResult += $"{setpoint},{calculateFlow},{time},{deviation},{result};";
            if (delta - 0.1 < setpoint && delta + 0.1 > setpoint)
            {
                _percent10Calculate = calculateFlow;
            }
            else if (2 * delta - 0.1 < setpoint && 2 * delta + 0.1 > setpoint)
            {
                _percent20Calculate = calculateFlow;
            }
            else if (3 * delta - 0.1 < setpoint && 3 * delta + 0.1 > setpoint)
            {
                _percent30Calculate = calculateFlow;
            }
            else if (4 * delta - 0.1 < setpoint && 4 * delta + 0.1 > setpoint)
            {
                _percent40Calculate = calculateFlow;
            }
            else if (5 * delta - 0.1 < setpoint && 5 * delta + 0.1 > setpoint)
            {
                _percent50Calculate = calculateFlow;
            }
            else if (6 * delta - 0.1 < setpoint && 6 * delta + 0.1 > setpoint)
            {
                _percent60Calculate = calculateFlow;
            }
            else if (7 * delta - 0.1 < setpoint && 7 * delta + 0.1 > setpoint)
            {
                _percent70Calculate = calculateFlow;
            }
            else if (8 * delta - 0.1 < setpoint && 8 * delta + 0.1 > setpoint)
            {
                _percent80Calculate = calculateFlow;
            }
            else if (9 * delta - 0.1 < setpoint && 9 * delta + 0.1 > setpoint)
            {
                _percent90Calculate = calculateFlow;
            }
            else if (10 * delta - 0.1 < setpoint && 10 * delta + 0.1 > setpoint)
            {
                _percent100Calculate = calculateFlow;
            }
            else
            {
                _verificationCalculate = calculateFlow;
                _verificationSetpoint = setpoint;
            }

            if (saveResult)
            {
                SaveVerificationData();
            }
        }

        private void SaveVerificationData()
        {
            var delta = (float)(Scale / 10);
            MFCVerificationData data = new MFCVerificationData()
            {
                Module = Module,
                Name = DisplayName,
                Percent10Setpoint = delta,
                Percent10Calculate = _percent10Calculate,

                Percent20Setpoint = delta * 2,
                Percent20Calculate = _percent20Calculate,

                Percent30Setpoint = delta * 3,
                Percent30Calculate = _percent30Calculate,

                Percent40Setpoint = delta * 4,
                Percent40Calculate = _percent40Calculate,

                Percent50Setpoint = delta * 5,
                Percent50Calculate = _percent50Calculate,

                Percent60Setpoint = delta * 6,
                Percent60Calculate = _percent60Calculate,

                Percent70Setpoint = delta * 7,
                Percent70Calculate = _percent70Calculate,

                Percent80Setpoint = delta * 8,
                Percent80Calculate = _percent80Calculate,

                Percent90Setpoint = delta * 9,
                Percent90Calculate = _percent90Calculate,

                Percent100Setpoint = delta * 10,
                Percent100Calculate = _percent100Calculate,

                Setpoint = _verificationSetpoint,
                Calculate = _verificationCalculate,
            };
            MFCVerificationDataRecorder.Add(data);
        }

        public void ResetVerificationData()
        {
            _percent10Calculate = 0;
            _percent20Calculate = 0;
            _percent30Calculate = 0;
            _percent40Calculate = 0;
            _percent50Calculate = 0;
            _percent60Calculate = 0;
            _percent70Calculate = 0;
            _percent80Calculate = 0;
            _percent90Calculate = 0;
            _percent100Calculate = 0;
            _verificationCalculate = 0;
            _verificationSetpoint = 0;
            _verificationResult = "";
        }
    }

    public class IoMfc : MfcBase1
    {
        public string Unit
        {
            get; set;
        }

        [Subscription(AITMfcDataPropertyName.Scale)]
        public override double Scale
        {
            get
            {
                if (_scN2Scale == null || _scScaleFactor == null)
                    return 0;
                return _scN2Scale.IntValue * _scScaleFactor.DoubleValue;
            }
        }

        [Subscription(AITMfcDataPropertyName.SetPoint)]
        public override double SetPoint
        {
            get
            {
                if (_aoFlow != null)
                {
                    byte[] high = BitConverter.GetBytes(_aoFlow.Buffer[_aoFlow.Index]);
                    byte[] low = BitConverter.GetBytes(_aoFlow.Buffer[_aoFlow.Index + 1]);
                    float flow = BitConverter.ToSingle(new[] { high[0], high[1], low[0], low[1] }, 0);

                    return flow * Scale / RtInstance.ANALOG_TRANS_RANGE;
                }
                return 0;
            }
            set
            {
                if (_aoFlow != null)
                {
                    byte[] flow = BitConverter.GetBytes((float)(value * RtInstance.ANALOG_TRANS_RANGE / Scale));

                    _aoFlow.Buffer[_aoFlow.Index] = BitConverter.ToInt16(flow, 0);
                    _aoFlow.Buffer[_aoFlow.Index + 1] = BitConverter.ToInt16(flow, 2);
                }
            }
        }

        [Subscription(AITMfcDataPropertyName.DefaultSetPoint)]
        public double DefaultSetPoint
        {
            get
            {
                if (_scDefaultSetPoint != null)
                    return _scDefaultSetPoint.IntValue;
                return 0;
            }
        }

        [Subscription(AITMfcDataPropertyName.FeedBack)]
        public override double FeedBack
        {
            get
            {
                if (_aiFlow != null)
                {
                    byte[] high = BitConverter.GetBytes(_aiFlow.Buffer[_aiFlow.Index]);
                    byte[] low = BitConverter.GetBytes(_aiFlow.Buffer[_aiFlow.Index + 1]);
                    float flow = BitConverter.ToSingle(new[] { high[0], high[1], low[0], low[1] }, 0);

                    return (_scRegulationFactor != null && _scRegulationFactor.IntValue > 0) ? flow * Scale / RtInstance.ANALOG_TRANS_RANGE / _scRegulationFactor.IntValue 
                                                                                             : flow * Scale / RtInstance.ANALOG_TRANS_RANGE;
                }
                return 0;
            }
        }

        [Subscription(AITMfcDataPropertyName.IsOutOfTolerance)]
        public override bool IsOutOfTolerance
        {
            get
            {
                return _toleranceChecker.Result;
            }
        }

        [Subscription(AITMfcDataPropertyName.IsEnableAlarm)]
        public bool EnableAlarm
        {
            get
            {
                if (_scEnableAlarm != null)
                    return _scEnableAlarm.BoolValue;
                return false;
            }
        }

        [Subscription(AITMfcDataPropertyName.AlarmRange)]
        public double AlarmRange
        {
            get
            {
                if (_scAlarmRange != null)
                    return _scAlarmRange.IntValue;
                return 0;
            }
        }

        [Subscription(AITMfcDataPropertyName.AlarmTime)]
        public double AlarmTime
        {
            get
            {
                if (_scAlarmTime != null)
                    return _scAlarmTime.IntValue;
                return 0;
            }
        }

        [Subscription(AITMfcDataPropertyName.PressureAlarm)]
        public bool PressureAlarm
        {
            get { return _diPressureAlarm != null ? _diPressureAlarm.Value : true; }
        }

        [Subscription(AITMfcDataPropertyName.MfcAlarm)]
        public bool MfcAlarm
        {
            get
            {
                return _bMfcAlarm;
            }
        }

        [Subscription(AITMfcDataPropertyName.IsEnable)]
        public bool Enable
        {
            get
            {
                if (_scEnable != null)
                    return _scEnable.BoolValue;
                return false;
            }
        }

        [Subscription(AITMfcDataPropertyName.IsOffline)]
        public bool IsOffline
        {
            get
            {
                if (_diOffline != null)
                    return _diOffline.Value;

                return false;
            }
        }

        public override string DisplayName
        {
            get
            {
                if (_scGasName != null)
                    return _scGasName.StringValue;
                return Display;
            }
        }

        private DeviceTimer rampTimer = new DeviceTimer();
        private double rampTarget;
        private double rampInitValue;
        private int rampTime;
        private bool _bMfcAlarm = false;
        private ToleranceChecker _toleranceChecker = new ToleranceChecker();

        private AIAccessor _aiFlow;
        private AOAccessor _aoFlow;
        private AOAccessor _aoRange;
        private DIAccessor _diOffline;
        private DIAccessor _diPressureAlarm;

        private SCConfigItem _scGasName;
        private SCConfigItem _scEnable;
        private SCConfigItem _scN2Scale;
        private SCConfigItem _scScaleFactor;
        private SCConfigItem _scAlarmRange;
        private SCConfigItem _scEnableAlarm;
        private SCConfigItem _scAlarmTime;
        private SCConfigItem _scDefaultSetPoint;
        private SCConfigItem _scRegulationFactor;

        private R_TRIG _trigOffline = new R_TRIG();
        private R_TRIG _trigPressureAlarm = new R_TRIG();

        private string _uniqueName;

        private string GasFlowOutOfTolerance = "GasFlowOutOfTolerance";
 

        public IoMfc(string module, XmlElement node, string ioModule = "")
        {
            Unit                = node.GetAttribute("unit");
            base.Module         = module;
            base.Name           = node.GetAttribute("id");
            base.Display        = node.GetAttribute("display");
            base.DeviceID       = node.GetAttribute("schematicId");

            _aoRange            = ParseAoNode("aoRange", node, ioModule);
            _diOffline          = ParseDiNode("diOffline", node, ioModule);
            _aiFlow             = ParseAiNode("aiFlow", node, ioModule);
            _aoFlow             = ParseAoNode("aoFlow", node, ioModule);
            _diPressureAlarm    = ParseDiNode("diPressureAlarm", node, ioModule);

            _scGasName          = SC.GetConfigItem($"{Module}.{Name}.GasName");
            _scEnable           = SC.GetConfigItem($"{Module}.{Name}.Enable");
            _scN2Scale          = SC.GetConfigItem($"{Module}.{Name}.MfcN2Scale");
            _scScaleFactor      = SC.GetConfigItem($"{Module}.{Name}.MfcScaleFactor");
            _scAlarmRange       = SC.GetConfigItem($"{Module}.{Name}.MfcAlarmRange");
            _scEnableAlarm      = SC.GetConfigItem($"{Module}.{Name}.MfcEnableAlarm");
            _scAlarmTime        = SC.GetConfigItem($"{Module}.{Name}.MfcAlarmTime");
            _scDefaultSetPoint  = SC.GetConfigItem($"{Module}.{Name}.DefaultSetPoint");
            _scRegulationFactor = SC.GetConfigItem($"{module}.{Name}.FlowRegulationFactor");

            _uniqueName = $"{Module}.{Name}";

#if DEBUG
            Debug.Assert(!string.IsNullOrWhiteSpace(_scGasName.StringValue));
            Debug.Assert(null != _scN2Scale);
            Debug.Assert(null != _aoFlow);
            Debug.Assert(null != _aiFlow);
#endif
        }

        public override bool Initialize()
        {
            EV.Subscribe(new EventItem("Event", GasFlowOutOfTolerance, "Gas Flow Out Of Tolerance", EventLevel.Alarm, EventType.HostNotification));


            DATA.Subscribe($"{Module}.{Name}", () =>
            {
                AITMfcData data = new AITMfcData
                {
                    Type              = "MFC",
                    UniqueName        = _uniqueName,
                    DeviceName        = Name,
                    DeviceSchematicId = DeviceID,
                    DisplayName       = DisplayName,
                    FeedBack          = FeedBack,
                    SetPoint          = SetPoint,
                    Scale             = Scale,
                    IsOffline         = IsOffline,
                };

                return data;
            }, SubscriptionAttribute.FLAG.IgnoreSaveDB);

            OP.Subscribe($"{Module}.{Name}.{AITMfcOperation.Ramp}", (name, args) =>
            {
                double target = (double)args[0];
                target = Math.Min(target, Scale);
                target = Math.Max(target, 0);

                Ramp(target, 0);
                LOG.Write(eEvent.EV_DEVICE_INFO, Module, $"Ramp to {target}{Unit}");
                return true;
            });

            DEVICE.Register($"{Module}.{Name}.{AITMfcOperation.Ramp}", (out string reason, int time, object[] param) =>
            {
                double target = Convert.ToDouble((string)param[0]);
                target = Math.Min(target, Scale);
                target = Math.Max(target, 0);

                Ramp(target, time);
                reason = $"{Display} ramp to {target}{Unit}";
                return true;
            });

            //@AAA use recipe
            DEVICE.Register($"{Module}.{Name}", (out string reason, int time, object[] param) =>
            {
                double target = Convert.ToDouble((string)param[0]);

                target = Math.Min(target, Scale);
                target = Math.Max(target, 0);

                Ramp(target, time);
                reason = $"{Display} ramp to {target}{Unit}";
                return true;
            });

            return base.Initialize();
        }

        public override void Monitor()
        {
            if (Enable)
            {
                Ramping();
                CheckTolerance();

                if (_aoRange != null)
                    _aoRange.Value = (short)Scale;

                _trigOffline.CLK = IsOffline;
                if (_trigOffline.Q)
                {
                    LOG.Write(eEvent.ERR_DEVICE_INFO, Module, string.Format("{0} is offline", DisplayName));
                    _bMfcAlarm = true;
                }

                _trigPressureAlarm.CLK = PressureAlarm == false;
                if (_trigPressureAlarm.Q)
                    LOG.Write(eEvent.ERR_DEVICE_INFO, Module, $"{Name}, {DisplayName} Pressure Alarm");
                if (PressureAlarm)
                {
                    _trigPressureAlarm.RST = true;
                }
            }
        }

        public override void Reset()
        {
            _bMfcAlarm = false;
            _toleranceChecker.Reset(AlarmTime);
            _trigPressureAlarm.RST = true;
            _trigOffline.RST = true;
        }

        public override void Terminate()
        {
            Ramp(DefaultSetPoint, 0);
        }

        public override void Ramp(int time)
        {
            Ramp(DefaultSetPoint, time);
        }

        public override void Ramp(double target, int time)
        {
            target = Math.Max(0, target);
            target = Math.Min(Scale, target);
            rampInitValue = SetPoint;    //ramp 初始值取当前设定值,而非实际读取值。零漂问题
            rampTime = time;
            rampTarget = target;
            rampTimer.Start(rampTime);
        }

        public override void StopRamp()
        {
            Ramp(SetPoint, 0);
        }

        private void Ramping()
        {
            if (rampTimer.IsTimeout() || rampTime == 0)
            {
                SetPoint = rampTarget;
            }
            else
            {
                SetPoint = rampInitValue + (rampTarget - rampInitValue) * rampTimer.GetElapseTime() / rampTime;
            }
        }

        private void CheckTolerance()
        {
            if (!EnableAlarm)
                return;

            // 流率检查

            _toleranceChecker.Monitor(FeedBack, SetPoint - Math.Abs(AlarmRange), SetPoint + Math.Abs(AlarmRange), AlarmTime);

            if (_toleranceChecker.Trig)
            {
                LOG.Write(eEvent.ERR_DEVICE_INFO, Module, Display + $" 越界 in {AlarmTime:0} seconds");

                EV.Notify(GasFlowOutOfTolerance);
            }
        }
    }
}