using System;
using System.Xml;
using Aitex.Core.Common.DeviceData;
using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.IOCore;
using Aitex.Core.RT.SCCore;
using Aitex.Core.RT.Tolerance;
using Aitex.Core.Util;

namespace Aitex.Core.RT.Device.Unit
{
    public class IoMfc : BaseDevice, IDevice
    {
        public string Unit
        {
            get; set;
        }

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

        [Subscription(AITMfcDataPropertyName.SetPoint)]
        public double SetPoint
        {
            get
            {
                if (_aoFlow != null)
                {
                    return _aoFlow.Value;
                }
                return 0;
            }
            set
            {
                if (_aoFlow != null)
                {
                    _aoFlow.Value = (float)value;
                }
            }
        }

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

        [Subscription(AITMfcDataPropertyName.FeedBack)]
        public double FeedBack
        {
            get
            {
                if (_aiFlow != null)
                    return (_scRegulationFactor != null && _scRegulationFactor.Value > 0) ? _aiFlow.Value / _scRegulationFactor.Value : _aiFlow.Value;
                return 0;
            }
        }

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

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

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

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

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

                return false;
            }
        }

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

        private DeviceTimer rampTimer = new DeviceTimer();
        private double rampTarget;
        private double rampInitValue;
        private int rampTime;

        private ToleranceChecker _toleranceChecker = new ToleranceChecker();

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

        private SCString _scGasName;
        private SCItem<bool> _scEnable;
        private SCItem<double> _scN2Scale;
        private SCItem<double> _scScaleFactor;
        private SCItem<double> _scAlarmRange;
        private SCItem<bool> _scEnableAlarm;
        private SCItem<double> _scAlarmTime;
        private SCItem<double> _scDefaultSetPoint;
        private SCItem<double> _scRegulationFactor;

        private R_TRIG _trigOffline = new R_TRIG();

        public IoMfc(string module, XmlElement node)
        {
            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);
            _diOffline          = ParseDiNode("diOffline", node);

            _aiFlow             = ParseAiNode("aiFlow", node);
            _aoFlow             = ParseAoNode("aoFlow", node);

            _scGasName          = ParseScNodeString("scGasName", node);
            _scEnable           = ParseScNodeBool("scEnable", node);
            _scN2Scale          = ParseScNodeDouble("scN2Scale", node);
            _scScaleFactor      = ParseScNodeDouble("scScaleFactor", node);
            _scAlarmRange       = ParseScNodeDouble("scAlarmRange", node);
            _scEnableAlarm      = ParseScNodeBool("scEnableAlarm", node);
            _scAlarmTime        = ParseScNodeDouble("scAlarmTime", node);
            _scDefaultSetPoint  = ParseScNodeDouble("scDefaultSetPoint", node);
            _scRegulationFactor = ParseScNodeDouble("scFlowRegulationFactor", node);
        }

        public bool Initialize()
        {
            DATA.Subscribe(string.Format("Device.{0}.{1}", Module, Name), () =>
            {
                AITMfcData data = new AITMfcData()
                {
                    Type = "MFC",
                    DeviceName = Name,
                    DeviceSchematicId = DeviceID,
                    DisplayName = DisplayName,
                    FeedBack = FeedBack,
                    SetPoint = SetPoint,
                    Scale = Scale,
                    IsOffline = IsOffline,
                };

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

            DEVICE.Register($"{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 = string.Format("{0} ramp to {1}{2}", Display, target, Unit);
                return true;
            });

            //@AAA use recipe
            DEVICE.Register($"{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 = string.Format("{0} ramp to {1}{2}", Display, target, Unit);
                return true;
            });

            return true;
        }

        public void Monitor()
        {
            Ramping();
            CheckTolerance();

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

            _trigOffline.CLK = IsOffline;
            if (_trigOffline.Q)
            {
                EV.PostMessage(Module, EventEnum.DefaultAlarm, string.Format("{0} is offline", DisplayName));
            }
        }

        public void Reset()
        {
            _toleranceChecker.Reset(AlarmTime);

            _trigOffline.RST = true;
        }

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

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

        public 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 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)
            {
                EV.PostMessage(Module, EventEnum.ToleranceAlarm, Module, Display, $"Out of range in {AlarmTime.ToString("0")} seconds");
            }
        }
    }
}