using System;
using System.Diagnostics;
using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Fsm;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;
using MECF.Framework.Common.Equipment;

namespace Aitex.Core.RT.Device
{
	public abstract class DeviceEntityT<T> : Entity, IEntity where T : class, IDeviceManager, new()
	{
		public enum STATE
		{
			INIT = 0,
			RUNNING = 1,
			WAIT_RESET = 2,
			ERROR = 3
		}

		public enum MSG
		{
			INIT = 0,
			ERROR = 1,
			WAIT_RESET = 2,
			RESET = 3
		}

		
		protected const int MIN_MONITOR_INV_MS = 0;
		protected const int MAX_MONITOR_INV_MS = 5000;
		protected const int DEF_MONITOR_INV_MS = 100;
        
		private readonly R_TRIG _rTrigOverallMonitorTooSlow = new();
		private readonly R_TRIG _rTrigOverallMonitorInvTooLong = new();
		private readonly DeviceTimer _tmrMonitorPeriod = new();
		private readonly Stopwatch _swMonitorCost = new ();
		private readonly Stopwatch _swMonitorInv = new ();
		private int _overallMonitorCostMs = 0;
		private int _overallMonitorCnt = 0;
		private int _overallMonitorInvMs = 0;
		
		private T mgr = null;

		public bool IsWaitReset => fsm.State == 2;

		public DeviceEntityT()
		{
			base.Running = false;
			fsm = new StateMachine<DeviceEntityT<T>>("DeviceLayer", 0, 50);
			EnterExitTransition<STATE, MSG>(STATE.INIT, null, MSG.INIT, null);
			Transition(STATE.INIT, MSG.INIT, fInit, STATE.RUNNING);
			Transition(STATE.RUNNING, FSM_MSG.TIMER, fRun, STATE.RUNNING);
			Transition(STATE.RUNNING, MSG.RESET, fReset, STATE.RUNNING);
			mgr = Singleton<T>.Instance;
			
			DATA.Subscribe($"{ModuleName.System}.{ModuleName.Diagnosis}.OverallMonitorCostMs", () => _overallMonitorCostMs);
			DATA.Subscribe($"{ModuleName.System}.{ModuleName.Diagnosis}.OverallMonitorCnt", () => _overallMonitorCnt);
			DATA.Subscribe($"{ModuleName.System}.{ModuleName.Diagnosis}.OverallMonitorInvMs", () => _overallMonitorInvMs);
			
			
			Debug.Assert(MIN_MONITOR_INV_MS < DEF_MONITOR_INV_MS, "const variable definition error.");
			Debug.Assert(MAX_MONITOR_INV_MS > DEF_MONITOR_INV_MS, "const variable definition error.");
			MonitorPeriodMs = DEF_MONITOR_INV_MS;
			
			_tmrMonitorPeriod.Start(MonitorPeriodMs);
		}
		
		public virtual int MonitorPeriodMs
		{
			get;
			private set;
		}
		
		public void ChangeMinMonitorPeriodMs(int period)
		{
			if (period == MonitorPeriodMs)
				return;

			var _ssPeriod = MIN_MONITOR_INV_MS;
			if (period < MIN_MONITOR_INV_MS)
				MonitorPeriodMs = MIN_MONITOR_INV_MS;
			else if (period > MAX_MONITOR_INV_MS)
				MonitorPeriodMs = MAX_MONITOR_INV_MS;
			else
				MonitorPeriodMs = period;
			
			LOG.Info($"{this}.{nameof(MonitorPeriodMs)} change from {_ssPeriod}ms to {MonitorPeriodMs}ms");
		}

		public bool Check(int msg, out string reason, params object[] args)
		{
			reason = "";
			return true;
		}

		private bool fInit(object[] objs)
		{
			return true;
		}

		private bool fRun(object[] objs)
		{
			try
			{
				if (_tmrMonitorPeriod.IsTimeout())
				{
					_swMonitorInv.Stop();
					_overallMonitorInvMs = (int)_swMonitorInv.Elapsed.TotalMilliseconds;
					
					_swMonitorCost.Reset();
					_swMonitorCost.Start();
					
					_tmrMonitorPeriod.Start(MonitorPeriodMs);
					mgr.Monitor();
					
					_swMonitorCost.Stop();
					_overallMonitorCostMs = (int)_swMonitorCost.Elapsed.TotalMilliseconds;
					_overallMonitorCnt++;
					_overallMonitorCnt %= 100;
					
					_swMonitorInv.Reset();
					_swMonitorInv.Start();
					
				}

				_rTrigOverallMonitorTooSlow.CLK = _overallMonitorCostMs > 3000;
				if(_rTrigOverallMonitorTooSlow.Q)
					LOG.Warning($"Device Manager overall monitor too slow, took {_overallMonitorCostMs}ms where the limit is 3000ms");

				_rTrigOverallMonitorInvTooLong.CLK = _overallMonitorInvMs > MAX_MONITOR_INV_MS * 1.5;
				if(_rTrigOverallMonitorInvTooLong.Q)
					LOG.Warning($"Device Manager overall monitor interval is too long, took {_overallMonitorInvMs}ms where the limit is {MAX_MONITOR_INV_MS * 1.5}ms");
			}
			catch (Exception ex)
			{
				Running = false;
				LOG.Error("Device Run exception", ex);
			}
			Running = true;
			return true;
		}

		private bool fReset(object[] objs)
		{
			mgr.Reset();
			return true;
		}
	}
}
