using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.IOCore;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using MECF.Framework.Common.Equipment;
using MECF.Framework.Common.Event;

namespace Aitex.Core.RT.Device
{
	/// <summary>
	/// 设备类型的基类。
	/// </summary>
	/// <remarks>
	/// 该类描述了系统使用的设备对象。该设备对象为系统中最小控制单元，所有模组（Module）通过此设备对象对实际的硬件设备进行控制。
	/// </remarks>
	public abstract class BaseDevice : IAlarmHandler
	{
        #region Variables

        /// <summary>
        /// 当设备报警状态发生变化时，调用此事件。
        /// </summary>
        public event Action<string, AlarmEventItem> OnDeviceAlarmStateChanged;

        /// <summary>
        /// 设备线程锁。
        /// </summary>
        protected readonly object SyncRoot;

		/// <summary>
		/// 报警信息字典。
		/// </summary>
        protected readonly Dictionary<string, AlarmEventItem> DicAlarms;

		protected readonly List<R_TRIG> RTrigs = new();

        /// <summary>
        /// 记录不存在的{Module}.IsService数据，IoSensor、ErrorDetectableBaseDevice对象使用
        /// </summary>
        protected static readonly HashSet<string> NonExistedServiceData = new();

        protected static readonly object LockNonExistedServiceData = new();

        #endregion

        #region Constructors

        /// <summary>
        /// 创建设备对象的实例。
        /// </summary>
        protected BaseDevice()
        {
            SyncRoot = new object();
            ScBasePath = ModuleName.System.ToString();
            IoBasePath = ModuleName.System.ToString();
            DicAlarms = new Dictionary<string, AlarmEventItem>();
            IsEnabled = true;
        }

        /// <summary>
		/// 创建设备对象的实例。
		/// </summary>
		/// <param name="module">设备所属的模组名称。</param>
		/// <param name="name">设备名称。</param>
		/// <param name="display">设备在用户界面的显示名称。</param>
		/// <param name="id">设备编号。</param>
        protected BaseDevice(string module, string name, string display, string id) : this()
        {
            display = (string.IsNullOrEmpty(display) ? name : display);
            id = (string.IsNullOrEmpty(id) ? name : id);
            Module = module;
            Name = name;
            Display = display;
            DeviceID = id;
            UniqueName = module + "." + name;
        }

        /// <summary>
        /// 根据XML的配置描述创建设备对象的实例。
        /// </summary>
        /// <param name="module">设备所属的模组名称。</param>
        /// <param name="node">配置文件描述本设备的Xml节点。</param>
        /// <param name="ioModule"></param>
        protected BaseDevice(string module, XmlElement node, string ioModule = "") : this()
        {
            var attrModule = node.GetAttribute("module");
            Module = string.IsNullOrEmpty(attrModule) ? module : attrModule;
            Unit = node.GetAttribute("unit");
            Name = node.GetAttribute("id");
            Display = node.GetAttribute("display");
            DeviceID = node.GetAttribute("schematicId");
            UniqueName = module + "." + Name;

            var scBasePath = node.GetAttribute("scBasePath");
            scBasePath = string.IsNullOrEmpty(scBasePath) ? 
                $"{Module}.{Name}" 
                : scBasePath.Replace("{module}", Module);

            ScBasePath = scBasePath;

        }

        #endregion

        #region Properties

        /// <summary>
        /// 设置或返回当前设备是否被使能。
        /// </summary>
        /// <remarks>
        /// 默认情况下，当设备被创建时自动使能；但如果设备对应的Module被配置为UnInstalled，则该设备
        /// 会被其所属Module禁用，以提高RT性能，及避免产生不必要的报警信息。
        /// </remarks>
        public bool IsEnabled { get; set; }

        /// <summary>
        /// 设置或返回当前设备在系统中唯一的名称。
        /// </summary>
        public string UniqueName { get; set; }

        /// <summary>
        /// 设置或返回当前设备所属的模组名称。
        /// </summary>
        /// <remarks>
        /// 关于系统中有效的模组名称，请参考<see cref="ModuleName"/>。
        /// </remarks>
        public string Module { get; set; }

        /// <summary>
        /// 设置或返回当前设备名称。
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 设置或返回当前设备向用户显示的名称。
        /// </summary>
        public string Display { get; set; }

        /// <summary>
        /// 设置或返回当前设备编号。
        /// </summary>
        public string DeviceID { get; set; }

        /// <summary>
		/// 设置或返回当前设备的计量单位。
		/// </summary>
        public string Unit { get; set; }

        /// <summary>
        /// 设置或返回当前设备配置信息位于系统配置的节点名称。
        /// </summary>
        public string ScBasePath { get; set; }

		/// <summary>
		/// 设置或返回当前设备所使用的IO Provider。
		/// </summary>
        public string IoBasePath { get; set; }

        /// <summary>
        /// 设置或返回设备是否产生报警。
        /// </summary>
        public bool HasAlarm =>
            DicAlarms?.Values.FirstOrDefault((AlarmEventItem x) => !x.IsAcknowledged && x.Level == EventLevel.Alarm) !=
            null;

        #endregion

        #region Methods

        /// <summary>
        /// 加载指定的系统配置。
        /// </summary>
        /// <param name="scPath">指定的系统配置路径</param>
        /// <param name="scPathFallback">当指定的系统配置路径不存在时，使用默认路径</param>
        /// <param name="valueFallback">当指定的系统配置值数据类型错误时，使用此默认值</param>
        /// <param name="valueHolder">存放系统配置的变量</param>
        /// <param name="scValueChangedCallback">监测系统配置是否发生变化，当发生变化时，执行此回调函数</param>
        /// <typeparam name="T"></typeparam>
        protected void LoadSC<T>(string scPath, string scPathFallback, T valueFallback, ref T valueHolder, Action<T> scValueChangedCallback = null)
        {
            if (scPath == null) throw new ArgumentNullException(nameof(scPath));
            if (scPathFallback == null) throw new ArgumentNullException(nameof(scPathFallback));
            if (valueFallback == null) throw new ArgumentNullException(nameof(valueFallback));
            if (valueHolder == null) throw new ArgumentNullException(nameof(valueHolder));

            // 读取系统配置
            var path = scPath;
            var scItem = SC.GetConfigItem(path);
            if (scItem == null)
            {
                // 没找到配置，用默认路径找
                path = scPathFallback;
                scItem = SC.GetConfigItem(path);
            }

            if (scItem != null)
            {
                // 找到系统配置，尝试转换并赋值到指定的字段
                try
                {
                    var converter = TypeDescriptor.GetConverter(typeof(T));
                    valueHolder = (T)converter.ConvertFromString(scItem.Value.ToString());

                    // 如果指定了该回调，则表明需要监测当前系统配置值
                    if (scValueChangedCallback != null)
                    {
                        SC.RegisterValueChangedCallback(path, v =>
                        {
                            var val = (T)converter.ConvertFromString(scItem.Value.ToString());
                            scValueChangedCallback(val);
                        });
                    }
                }
                catch (Exception)
                {
                    EV.PostWarningLog(Module,
                        $"System config {path} type mismatched, set to default value");
                    valueHolder = valueFallback;
                }
            }
            else
            {
                // 指定路径和默认路径均找不到配置，使用默认配置
                EV.PostWarningLog(Module,
                    $"System config {path} type mismatched, set to default value");
                valueHolder = valueFallback;
            }
        }

        /// <summary>
        /// 订阅报警事件。
        /// </summary>
        /// <param name="name">报警事件名称。</param>
        /// <param name="description">报警事件描述。</param>
        /// <param name="resetChecker">报警复位检查回调函数。</param>
        /// <param name="level">报警等级，请参考<see cref="EventLevel"/></param>
        /// <returns></returns>
        protected AlarmEventItem SubscribeAlarm(string name, string description, Func<bool> resetChecker, EventLevel level = EventLevel.Alarm)
        {
            var ae = new AlarmEventItem(Module, name, description, resetChecker, this);
            ae.Level = level;

            DicAlarms[name] = ae;
            EV.Subscribe(ae);
            return ae;
        }

        /// <summary>
        /// 清除当前设备的报警信息。
        /// </summary>
        protected void ResetAlarm()
        {
            foreach (var alarm in DicAlarms)
                alarm.Value.Reset();
        }

        /// <summary>
        /// 当前对象的子类实现的扫描任务。
        /// </summary>
        protected virtual void HandleMonitor()
        {
            
        }

        /// <summary>
        /// 执行当前设备扫描周期任务。
        /// </summary>
        public void Monitor()
        {
            // 如果当前设备被标记为禁用，则不要执行周期性扫描任务。
            if(IsEnabled)
                HandleMonitor();
        }

        /// <summary>
        /// 通知当前设备的报警信息发生了变化。
        /// </summary>
        /// <param name="args"></param>
        public void AlarmStateChanged(AlarmEventItem args)
        {
            OnDeviceAlarmStateChanged?.Invoke(UniqueName ?? "", args);
        }

        /// <summary>
        /// 从XML配置描述中解析指定的DO对象。
        /// </summary>
        /// <remarks>
        /// 通过该方法从XML配置中获取指定的DO名称，从系统点表中查找并返回指定名称的DO实例。
        /// </remarks>
        /// <param name="name">XML配置节点中定义DO的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <param name="ioModule">IO Provider名称。</param>
        /// <returns>
        /// 指定名称的<see cref="DOAccessor"/>对象。
        /// <br/>
        /// 如果未找到指定名称的DO，则返回null。
        /// </returns>
        public DOAccessor ParseDoNode(string name, XmlElement node, string ioModule = "")
		{
			if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
			{
				return IO.DO[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
			}
			return null;
		}

        /// <summary>
        /// 从XML配置描述中解析指定的DI对象。
        /// </summary>
        /// <remarks>
        /// 通过该方法从XML配置中获取指定的DI名称，从系统点表中查找并返回指定名称的DI实例。
        /// </remarks>
        /// <param name="name">XML配置节点中定义DI的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <param name="ioModule">IO Provider名称。</param>
        /// <returns>
        /// 指定名称的<see cref="DIAccessor"/>对象。
        /// <br/>
        /// 如果未找到指定名称的DI，则返回null。
        /// </returns>
		public DIAccessor ParseDiNode(string name, XmlElement node, string ioModule = "")
		{
			if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
			{
				return IO.DI[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
			}
			return null;
		}

        /// <summary>
        /// 从XML配置描述中解析指定的AO对象。
        /// </summary>
        /// <remarks>
        /// 通过该方法从XML配置中获取指定的AO名称，从系统点表中查找并返回指定名称的AO实例。
        /// </remarks>
        /// <param name="name">XML配置节点中定义AO的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <param name="ioModule">IO Provider名称。</param>
        /// <returns>
        /// 指定名称的<see cref="AOAccessor"/>对象。
        /// <br/>
        /// 如果未找到指定名称的AO，则返回null。
        /// </returns>
		public AOAccessor ParseAoNode(string name, XmlElement node, string ioModule = "")
		{
			if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
			{
				return IO.AO[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
			}
			return null;
		}

        /// <summary>
        /// 从XML配置描述中解析指定的AI对象。
        /// </summary>
        /// <remarks>
        /// 通过该方法从XML配置中获取指定的AI名称，从系统点表中查找并返回指定名称的AI实例。
        /// </remarks>
        /// <param name="name">XML配置节点中定义AI的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <param name="ioModule">IO Provider名称。</param>
        /// <returns>
        /// 指定名称的<see cref="AIAccessor"/>对象。
        /// <br/>
        /// 如果未找到指定名称的AI，则返回null。
        /// </returns>
		public AIAccessor ParseAiNode(string name, XmlElement node, string ioModule = "")
		{
			if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
			{
				return IO.AI[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
			}
			return null;
		}

        /// <summary>
        /// 从XML配置描述中解析当前设备配置信息位于系统配置中的节点。
        /// </summary>
        /// <param name="name">XML配置节点中定义系统配置节点的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <param name="ioModule">IO Provider名称。</param>
        /// <param name="defaultScPath">如果未找到name指定的系统配置节点，则使用此默认系统配置节点。</param>
        /// <returns>
        /// <see cref="SCConfigItem"/>。
        /// </returns>
        public SCConfigItem ParseScNode(string name, XmlElement node, string ioModule = "", string defaultScPath = "")
		{
			SCConfigItem sCConfigItem = null;
			if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
			{
				sCConfigItem = SC.GetConfigItem(node.GetAttribute(name));
			}
			if (sCConfigItem == null && !string.IsNullOrEmpty(defaultScPath) && SC.ContainsItem(defaultScPath))
			{
				sCConfigItem = SC.GetConfigItem(defaultScPath);
			}
			return sCConfigItem;
		}

        /// <summary>
        /// 从XML配置描述中解析设备名称，并从系统设备列表中查找该设备。
        /// </summary>
        /// <typeparam name="T">待查找的设备的类型。</typeparam>
        /// <param name="name">XML配置节点中定义设备名称的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <returns>
        /// 指定的设备实例。
        /// <br/>
        /// 如果未找到指定的设备，则返回null。
        /// </returns>
		public static T ParseDeviceNode<T>(string name, XmlElement node) where T : class, IDevice
		{
			if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
			{
				return DEVICE.GetDevice<T>(node.GetAttribute(name));
			}
			LOG.Write($"{node.InnerXml}，未定义{name}");
			return null;
		}

        /// <summary>
        /// 从XML配置描述中解析设备名称，并从系统设备列表中查找该设备。
        /// </summary>
        /// <typeparam name="T">待查找的设备的类型。</typeparam>
        /// <param name="module">设备所属模组。</param>
        /// <param name="name">XML配置节点中定义设备名称的属性的名称。</param>
        /// <param name="node">XML配置节点。</param>
        /// <returns>
        /// 指定的设备实例。
        /// <br/>
        /// 如果未找到指定的设备，则返回null。
        /// </returns>
        public static T ParseDeviceNode<T>(string module, string name, XmlElement node) where T : class, IDevice
		{
			var attribute = node.GetAttribute(name);
			if (!string.IsNullOrEmpty(attribute) && !string.IsNullOrEmpty(attribute.Trim()))
			{
				return DEVICE.GetDevice<T>(module + "." + attribute);
			}
			LOG.Write($"{node.InnerXml}，未定义{name}");
			return null;
		}

        #endregion
    }
}
