using System;
using System.Collections.Generic;
using System.Diagnostics;
using Aitex.Core.RT.Device;
using Aitex.Core.RT.IOCore;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;
using BlinkDataType = System.Collections.Generic.KeyValuePair<MECF.Framework.Common.Device.Bases.SignalTowerActions, uint>;

namespace MECF.Framework.Common.Device.Bases
{
    /// <summary>
    /// 信号塔元件对象，包含灯和蜂鸣器。
    /// 受支持的元件请参考<see cref="STLightTypes"/>。
    /// <remarks>
    /// 信号塔元件的Blink模式基于一个简单的Switch-Case状态机实现。
    /// </remarks>
    /// </summary>
    public class SignalTowerLightBase : BaseDevice, IDevice
    {
        #region Variables

        private enum FsmStateBlink
        {
            Init,
            PrepareOn,
            On,
            PrepareOff,
            Off,
            ActionDone,
            CycleFinished,
            Cleanup,
            Idle
        }

        private DOAccessor _doLight;
        private AOAccessor _aoBlinkFreqHz;
        private readonly STLightTypes _lightType;
        private STAction _action;

        /// <summary>
        /// 闪烁次数计数器。
        /// <remarks>当该计数器归零时，结束闪烁。</remarks>
        /// </summary>
        private int _blinkCycleDownCounter;

        private STBlinkPattern _blinkPattern;
        private List<BlinkDataType> _blinkData;
        private Queue<BlinkDataType> _qBlinkData;
        private BlinkDataType _nextBlinkData;


        private FsmStateBlink _blinkStage;
        private readonly DeviceTimer _timBlinkOn;
        private readonly DeviceTimer _timBlinkOff;

        #endregion

        #region Constructors

        /// <summary>
        /// 构建信号塔元件对象的实例。
        /// </summary>
        /// <param name="module">所属模组</param>
        /// <param name="light">信号塔元件类型，请参考<see cref="STLightTypes"/>。</param>
        /// <param name="doSwitch">控制元件开关的DO。</param>
        /// <param name="aoBlinkFreq">控制Blink频率的AO。</param>
        public SignalTowerLightBase(string module, STLightTypes light, DOAccessor doSwitch, AOAccessor aoBlinkFreq)
            :base(module, light.ToString(), null, null)
        {
            _lightType = light;
            _doLight = doSwitch;
            _aoBlinkFreqHz = aoBlinkFreq;

            _timBlinkOn = new DeviceTimer();
            _timBlinkOff = new DeviceTimer();
        }

        #endregion

        #region Properties

        /// <summary>
        /// 返回信号塔元件类型。
        /// </summary>
        public STLightTypes Type => _lightType;

        /// <summary>
        /// 返回正在执行的动作
        /// </summary>
        public STAction Action => _action;

        /// <summary>
        /// 返回当前信号塔组件是否为蜂鸣器。
        /// </summary>
        public bool IsBuzzer => _lightType 
            is STLightTypes.Buzzer 
            or STLightTypes.Buzzer1 
            or STLightTypes.Buzzer2 
            or STLightTypes.Buzzer3 
            or STLightTypes.Buzzer4 
            or STLightTypes.Buzzer5;

        #endregion

        #region Monitor Methods

        /// <summary>
        /// 后台扫描线程执行的方法。
        /// </summary>
        protected override void HandleMonitor()
        {
            /*
             * 实现一个基于switch-case的建议状态机，用于实现Blink ON-OFF切换。
             * 注意：请避免使用线程实现Blink模式，不符合程序架构，不严谨的线程处理可能造成意外结果。
             */

            // 如果时Blinking模式，实现Blinking逻辑
            MonitorBlink();
        }

        /// <summary>
        /// 执行Blink逻辑。
        /// </summary>
        private void MonitorBlink()
        {
            if (_action == null)
                return;

            // 如果信号塔元件的状态已经转为Off，但状态机没有进入Idle，说明可能人为取消了信号，需要复位状态机。
            if (_action.Output == SignalTowerActions.Off && _blinkStage != FsmStateBlink.Idle)
            {
                Reset();
            }
            
            // 如果动作状态指定使用自定义模式，并且循环次数未用完，则根据Pattern进行闪烁
            if (_action.Output == SignalTowerActions.Customized && _blinkCycleDownCounter > 0)
            {
                switch (_blinkStage)
                {
                    case FsmStateBlink.Idle:
                        // 空闲状态
                        // 注意：该状态不应反复进入，因为_action和_blinkCycleDownCounter在首次进入Idle
                        // 状态时，会被清除，从而进入Switch-Case状态机的条件无法满足
                        
                        break;
                    
                    case FsmStateBlink.Cleanup:
                        // 控制周期结束
                        Reset();
                        break;

                    case FsmStateBlink.Init:
                        // 如果闪烁数据列队为空，可能：
                        // 1、第一次进入状态机
                        // 2、上个Cycle已经结束，需要执行下个Cycle
                        if (_qBlinkData == null || _qBlinkData.Count <= 0)
                            _qBlinkData = new Queue<BlinkDataType>(_blinkData);
                        
                        // 如果未能获取闪烁数据，则终止状态机
                        if (_qBlinkData.Count == 0)
                        {
                            _blinkStage = FsmStateBlink.Cleanup;
                            break;
                        }
                        
                        _nextBlinkData = _qBlinkData.Dequeue();

                        if (_nextBlinkData.Key == SignalTowerActions.On)
                            _blinkStage = FsmStateBlink.PrepareOn;
                        else if (_nextBlinkData.Key == SignalTowerActions.Off)
                            _blinkStage = FsmStateBlink.PrepareOff;
                        else
                        {
                            LOG.Error($"Incorrect status {_nextBlinkData.Key} in blink pattern");
                            _blinkStage = FsmStateBlink.Cleanup;
                            break;
                        }

                        //_doLight.Value = false;
                        _timBlinkOn.Stop();
                        _timBlinkOff.Stop();
                        break;

                    case FsmStateBlink.PrepareOn:
                        _doLight.Value = true;
                        _timBlinkOn.Start(_nextBlinkData.Value);
                        _timBlinkOff.Stop();
                        _blinkStage = FsmStateBlink.On;
                        LOG.Debug($"{this} PrepareON, state keeping for {_nextBlinkData.Value}ms.");
                        break;

                    case FsmStateBlink.On:
                        // 等待输出ON状态结束
                        if (_timBlinkOn.IsTimeout())
                            _blinkStage = FsmStateBlink.ActionDone;
                        break;

                    case FsmStateBlink.PrepareOff:
                        _doLight.Value = false;
                        _timBlinkOn.Stop();
                        _timBlinkOff.Start(_nextBlinkData.Value);
                        _blinkStage = FsmStateBlink.Off;
                        LOG.Debug($"{this} PrepareOFF, state keeping for {_nextBlinkData.Value}ms.");
                        break;

                    case FsmStateBlink.Off:
                        // 等待输出OFF状态结束
                        if (_timBlinkOff.IsTimeout())
                            _blinkStage = FsmStateBlink.ActionDone;
                        break;

                    case FsmStateBlink.ActionDone:
                        // 上个输出完成后，判断动作列队是否为空。
                        // 如果不为空，则继续执行；
                        // 否则，表示一个循环已完成，跳转到CycleFinished状态
                        if (_qBlinkData.Count <= 0)
                            _blinkStage = FsmStateBlink.CycleFinished;
                        else
                            _blinkStage = FsmStateBlink.Init;
                        break;

                    case FsmStateBlink.CycleFinished:
                        _blinkCycleDownCounter--;
                        if (_blinkCycleDownCounter <= 0)
                        {
                            // Blink循环结束，关闭输出
                            _doLight.Value = false;
                            _action.Output = SignalTowerActions.Off;
                            _blinkStage = FsmStateBlink.Cleanup;
                            _action.IsCycleDone = true;
                            LOG.Debug($"{this} Cycle finished.");
                        }
                        else
                        {
                            // 进入下一个循环。
                            _blinkStage = FsmStateBlink.Init;
                            LOG.Debug($"{this} Entering next cycle, cycle left: {_blinkCycleDownCounter}");
                        }

                        break;
                }
            }
        }
        
        #endregion
        
        #region Methods

        /// <summary>
        /// 初始化信号塔元件。
        /// </summary>
        /// <returns>True：初始化成功；False：初始化失败。</returns>
        public bool Initialize()
        {
            _blinkStage = FsmStateBlink.Idle;
            return true;
        }

        /// <summary>
        /// 获取信号塔元件映射PLC IO的输出状态。
        /// </summary>
        /// <returns></returns>
        public bool GetValue()
        {
            return _doLight != null && _doLight.Value;
        }

        /// <summary>
        /// 设置信号塔元件动作。
        /// <para>请参考<see cref="SignalTowerActions"/>枚举获取支持的动作类型。</para>
        /// </summary>
        /// <param name="action">信号灯的动作。</param>
        public void SetAction(STAction action)
        {
            if (action == null)
            {
                _action = null;
                return;
            }

            // 不要重复设置状态
            if (_action == null || action.CompareTo(_action) != 0)
                _action = action;
            else
                return;
            
            // 如果没有指定动作，则默认关闭组件
            if (_action == null)
            {
                LOG.Debug($"{this} Action cleared");
                _blinkCycleDownCounter = 0;
                _doLight.Value = false;
                Reset(); // 复位状态机
                return;
            }

            LOG.Debug($"{this} Set event {action.EventName}");

            // 执行动作
            switch (_action.Output)
            {
                case SignalTowerActions.On:
                    _blinkCycleDownCounter = 0;
                    _doLight.Value = true;
                    break;

                case SignalTowerActions.Off:
                    _blinkCycleDownCounter = 0;
                    _doLight.Value = false;
                    Reset(); // 复位状态机
                    break;

                case SignalTowerActions.Customized:
                    // 当工作在闪烁状态时，如果没有指定闪烁模式，则创建一个默认模式。
                    _blinkPattern = _action.BlinkPattern;
                    _blinkPattern ??= new STBlinkPattern();

                    // PLC控制模式
                    if (_blinkPattern.IsPlcMode && _aoBlinkFreqHz != null)
                    {
                        _aoBlinkFreqHz.Value = _blinkPattern.FrequencyHz;
                    }
                    else
                    {
                        // 当循环次数小于等于0时，无限循环。
                        _blinkCycleDownCounter = _blinkPattern.TotalCycles <= 0 ? int.MaxValue : _blinkPattern.TotalCycles;

                        // 解析闪烁模式数据。
                        if (!STBlinkPattern.GetBlinkData(_blinkPattern, out _blinkData, out var reason))
                        {
                            // 如果解析闪烁数据，如果解析错误，使用默认的闪烁模式。
                            LOG.Error($"Unable to set {Type} to {action}, {reason}");
                            STBlinkPattern.GetBlinkData(STBlinkPattern.GetDefaultPattern(), out _blinkData, out _);
                            Debug.Assert(_blinkData != null); 
                        }

                        // 使能状态机
                        //! 注意：此步需放在最后，因为状态机异步使能，相关数据可能还未准备好
                        _blinkStage = FsmStateBlink.Init;
                    }

                    break;

                case SignalTowerActions.Unknown:
                    LOG.Error($"{Type} Undefined output status");
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// 复位信号塔元件。
        /// </summary>
        public void Reset()
        {
            // 复位后状态机回到Idle状态
            _blinkStage = FsmStateBlink.Idle;

            if (_action != null)
                _action.Output = SignalTowerActions.Off;

            _blinkCycleDownCounter = 0;
            _doLight.Value = false;
            
            _qBlinkData?.Clear();
            _timBlinkOn?.Stop();
            _timBlinkOff?.Stop();

            LOG.Debug($"{this} was turned off and entering into IDLE state.");
        }

        /// <summary>
        /// 终止信号塔元件。
        /// </summary>
        public void Terminate()
        {
            _doLight = null;
            _timBlinkOn.Stop();
            _timBlinkOff.Stop();
        }

        
        /// <inheritdoc />
        public override string ToString()
        {
            return $"{Module}.{_lightType}";
        }

        #endregion
    }
}
