using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Xml;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using MECF.Framework.Common.IOCore;

namespace MECF.Framework.RT.Core.IoProviders
{
	public class IoProviderManager : Singleton<IoProviderManager>
	{
		private readonly List<IIoProvider> _providers = new ();

		private readonly Dictionary<string, IIoProvider> _dicProviders = new ();

		public List<IIoProvider> Providers => _providers;

		public IIoProvider GetProvider(string name)
		{
			if (_dicProviders.ContainsKey(name))
			{
				return _dicProviders[name];
			}
			return null;
		}

		public void Initialize(string xmlConfigFile, Dictionary<string, Dictionary<int, string>> ioMappingPathFile)
		{
			var xmlDocument = new XmlDocument();
			try
			{
				xmlDocument.Load(xmlConfigFile);
				var xmlNodeIoProviders = xmlDocument.SelectNodes("IoProviders/IoProvider");
				foreach (var node in xmlNodeIoProviders)
				{
					if (!(node is XmlElement nodeProvider) || !(nodeProvider.SelectSingleNode("Parameter") is XmlElement nodeParameter))
					{
						continue;
					}
					var attrModule = nodeProvider.GetAttribute("module").Trim();
					var attrName = nodeProvider.GetAttribute("name").Trim();
					var attrClass = nodeProvider.GetAttribute("class").Trim();
					var attrAssembly = nodeProvider.GetAttribute("assembly").Trim();
					var attrLoadCond = nodeProvider.GetAttribute("load_condition").Trim();
					var isSim = SC.GetConfigItem("System.IsSimulatorMode").BoolValue;
					if ((isSim && attrLoadCond != "0" && attrLoadCond != "2") || (!isSim && attrLoadCond != "1" && attrLoadCond != "2"))
					{
						continue;
					}
					var ioProviderName = attrModule + "." + attrName;
					var type = Assembly.Load(attrAssembly).GetType(attrClass);
					if (type == null)
					{
						throw new Exception(string.Format("ioProvider config file class and assembly not valid," + ioProviderName));
					}
					IIoProvider ioProvider;
					try
					{
						ioProvider = (IIoProvider)Activator.CreateInstance(type);
						_providers.Add(ioProvider);
						_dicProviders[ioProviderName] = ioProvider;
					}
					catch (Exception ex)
					{
						LOG.Write(ex);
						throw new Exception(string.Format("ioProvider can not be created," + ioProviderName));
					}
					var blocks = new List<IoBlockItem>();
					var xmlNodeBlocks = nodeProvider.SelectNodes("Blocks/Block");
					foreach (var nodeBlock in xmlNodeBlocks)
					{
						if (!(nodeBlock is XmlElement xmlNodeBlock))
						{
							continue;
						}
						var ioBlockItem = new IoBlockItem();
						var variable = xmlNodeBlock.GetAttribute("variable");
                        var attrType = xmlNodeBlock.GetAttribute("type");
						var attrOffset = xmlNodeBlock.GetAttribute("offset");
						var attrSize = xmlNodeBlock.GetAttribute("size");
						var attrValueType = xmlNodeBlock.GetAttribute("value_type");

						if (!int.TryParse(attrOffset, out var result))
							continue;

                        ioBlockItem.Variable = variable;
						ioBlockItem.Index = result;
						if (int.TryParse(attrSize, out result))
						{
							ioBlockItem.Size = result;
							switch (attrType.ToLower())
							{
							case "ai":
								ioBlockItem.Type = IoType.AI;
								break;
							case "ao":
								ioBlockItem.Type = IoType.AO;
								break;
							case "di":
								ioBlockItem.Type = IoType.DI;
								break;
							case "do":
								ioBlockItem.Type = IoType.DO;
								break;
							default:
								continue;
							}
							if (ioBlockItem.Type == IoType.AI || ioBlockItem.Type == IoType.AO)
							{
								ioBlockItem.AIOType = ((string.IsNullOrEmpty(attrValueType) || attrValueType.ToLower() != "float") ? typeof(short) : typeof(float));
							}
							blocks.Add(ioBlockItem);
						}
					}
					if (ioMappingPathFile.ContainsKey(ioProviderName))
					{
						ioProvider.Initialize(attrModule, attrName, blocks, Singleton<IoManager>.Instance, nodeParameter, ioMappingPathFile[ioProviderName]);
						ioProvider.Start();
						continue;
					}
					throw new Exception(string.Format("can not find io map config files," + ioProviderName));
				}
			}
			catch (Exception ex2)
			{
				throw new ApplicationException("IoProvider configuration not valid," + ex2.Message);
			}
		}

		public void Initialize(string xmlConfigFile)
		{
			var xmlDoc = new XmlDocument();
			try
			{
				xmlDoc.Load(xmlConfigFile);
				var xmlNodeList = xmlDoc.SelectNodes("IoProviders/IoProvider");
				foreach (var item in xmlNodeList)
				{
					if (!(item is XmlElement xmlElement) || !(xmlElement.SelectSingleNode("Parameter") is XmlElement nodeParameter))
					{
						continue;
					}
					var moduleName = xmlElement.GetAttribute("module").Trim();
					var providerName = xmlElement.GetAttribute("name").Trim();
					var typeName = xmlElement.GetAttribute("class").Trim();
					var assemblyString = xmlElement.GetAttribute("assembly").Trim();
					var loadCond = xmlElement.GetAttribute("load_condition").Trim();
					var isSim = SC.GetConfigItem("System.IsSimulatorMode").BoolValue;
					if ((isSim && loadCond != "0" && loadCond != "2") || (!isSim && loadCond != "1" && loadCond != "2"))
					{
						continue;
					}
					var ioProviderName = moduleName + "." + providerName;
					var type = Assembly.Load(assemblyString).GetType(typeName);
					if (type == null)
					{
						throw new Exception(string.Format("ioProvider config file class and assembly not valid," + ioProviderName));
					}
					IIoProvider ioProvider;
					try
					{
						ioProvider = (IIoProvider)Activator.CreateInstance(type);
						_providers.Add(ioProvider);
						_dicProviders[ioProviderName] = ioProvider;
					}
					catch (Exception ex)
					{
						LOG.Write(ex);
						throw new Exception(string.Format("ioProvider can not be created," + ioProviderName));
					}
					var list = new List<IoBlockItem>();
					var nodeBlocks = xmlElement.SelectNodes("Blocks/Block");
					foreach (var nodeBlock in nodeBlocks)
					{
						if (!(nodeBlock is XmlElement xmlBlock))
						{
							continue;
						}
						var ioBlockItem = new IoBlockItem();
                        var variable = xmlBlock.GetAttribute("variable");
                        var attrType = xmlBlock.GetAttribute("type");
						var attrIndex = xmlBlock.GetAttribute("index");
						var attrSize = xmlBlock.GetAttribute("size");
						var attrValueType = xmlBlock.GetAttribute("value_type");
						if (!int.TryParse(attrIndex, out var index))
						{
							continue;
						}
						ioBlockItem.Index = index;
						ioBlockItem.Variable = variable;
                        if (int.TryParse(attrSize, out var size))
						{
							ioBlockItem.Size = size;
							switch (attrType.ToLower())
							{
							case "ai":
								ioBlockItem.Type = IoType.AI;
								break;
							case "ao":
								ioBlockItem.Type = IoType.AO;
								break;
							case "di":
								ioBlockItem.Type = IoType.DI;
								break;
							case "do":
								ioBlockItem.Type = IoType.DO;
								break;
							default:
								continue;
							}
							if (ioBlockItem.Type == IoType.AI || ioBlockItem.Type == IoType.AO)
							{
								ioBlockItem.AIOType = ((string.IsNullOrEmpty(attrValueType) || attrValueType.ToLower() != "float") ? typeof(short) : typeof(float));
							}
							list.Add(ioBlockItem);
						}
					}
					var ioModule = xmlElement.GetAttribute("map_module").Trim();
					var ioMapFileName = xmlElement.GetAttribute("map_file").Trim();
					var fileInfo = new FileInfo(xmlConfigFile);
					var ioMappingPathFile = fileInfo.Directory.FullName + "\\" + ioMapFileName;
					ioProvider.Initialize(moduleName, providerName, list, Singleton<IoManager>.Instance, nodeParameter, ioMappingPathFile, ioModule);
					ioProvider.Start();
				}
			}
			catch (Exception ex2)
			{
				throw new ApplicationException("IoProvider configuration not valid," + ex2.Message);
			}
		}

		public void Terminate()
		{
			try
			{
				foreach (var provider in _providers)
				{
					provider.Stop();
				}
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		public void Reset()
		{
			try
			{
				foreach (var provider in _providers)
				{
					provider.Reset();
				}
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		/// <summary>
		/// 等待PLC第一次同步数据，以免缓存数据默认值导致误报警。
		/// </summary>
		/// <param name="timeout"></param>
		public void WaitFirstSync(int timeout = 3000)
		{
			var sw = new Stopwatch();
			sw.Start();

			LOG.Info($"{nameof(IoProviderManager)} Waiting for the first data synchronization from the PLC.");
			while (true)
			{
				var notSyncedPlc = _providers.Where(x => x.IsSynced == false).ToArray();
				if (notSyncedPlc.Length == 0)
					break;
				
				if (sw.ElapsedMilliseconds > timeout)
				{
					var plcNames = string.Join(", ", notSyncedPlc.Select(x => $"{x.Module}.{x.Name}").ToArray());
					//EV.PostWarningLog(ModuleName.System.ToString(),
					//	$"The PLC(s) [{plcNames}] has not synchronized data in {timeout}ms, " +
					//	$"which may lead to erroneous warnings or alarms in the system.");
					break;
				}
				
				// keep waiting
				Thread.Sleep(10);
			}
		}
	}
}
