#define DEBUG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Xml;
using Aitex.Common.Util;
using Aitex.Core.RT.ConfigCenter;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.OperationCenter;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using MECF.Framework.Common.FAServices;
using MECF.Framework.Common.MECF.Framework.Common.SCCore;

namespace MECF.Framework.Common.SCCore
{
	public class SystemConfigManager : Singleton<SystemConfigManager>, ISCManager
    {
        #region Variables
		
		private readonly Dictionary<string, SCConfigItem> _items;
        private readonly Dictionary<string, List<Action<object>>> _dicValueChangedCallback;

		private object _itemLocker = new object();

		private string _scConfigFile;

		private readonly string _scDataFile = PathManager.GetCfgDir() + "_sc.data";

		private readonly string _scDataBackupFile = PathManager.GetCfgDir() + "_sc.data.bak";

		private readonly string _scDataErrorFile = PathManager.GetCfgDir() + "_sc.data.err.";

        private readonly string _scConfigChangedBackupPath = PathManager.GetCfgDir() + "ConfigBackup\\";

        public readonly List<ConfigChangedInfo> _itemsChanged;    //系统配置变更项记录

        #endregion

        #region Constructors

        public SystemConfigManager()
        {
            _items = new Dictionary<string, SCConfigItem>(StringComparer.OrdinalIgnoreCase);
            _itemsChanged = new List<ConfigChangedInfo>();
            _dicValueChangedCallback = new Dictionary<string, List<Action<object>>>();
        }

        #endregion

        #region Methods

        public List<VIDItem> VidConfigList
		{
			get
			{
				var list = new List<VIDItem>();
				foreach (var item in _items)
				{
					list.Add(new VIDItem
					{
						DataType = "",
						Description = item.Value.Description,
						Index = 0,
						Name = item.Key,
						Unit = ""
					});
				}
				return list;
			}
		}

		public void Initialize(string scConfigPathName)
		{
			_scConfigFile = scConfigPathName;
			BuildItems(_scConfigFile);
			BackupAndRecoverDataFile();
			CustomData();
			GenerateDataFile();
			foreach (var item in _items)
			{
				CONFIG.Subscribe("", item.Key, () => item.Value.Value);
			}
			OP.Subscribe("System.SetConfig", InvokeSetConfig);

            GetConfigChangedInfo();
            CONFIG.Subscribe("SystemConfig", "DataChangedList", () => _itemsChanged);
            OP.Subscribe("SystemConfig.Confirm", RemoveConfigChangedInfo);
            OP.Subscribe("SystemConfig.Revert", RevertConfig);

            SC.Manager = this;
		}

		/// <summary>
		/// 将变更过的配置还原
		/// </summary>
		/// <param name="arg1"></param>
		/// <param name="arg2"></param>
		/// <returns></returns>
        private bool RevertConfig(string arg1, object[] arg2)
        {
			foreach (var item in _itemsChanged)
			{
                if (_items.ContainsKey(item.Name))
                {
                    if (InitializeItemValue(_items[item.Name], item.OldValue))
                    {
                        GenerateDataFile();
                        EV.PostInfoLog("System", $"SC {item.Name} value changed back to {item.OldValue}");
                        //DataChangedBackup(_items[item.Name], item.OldValue);	//遍历时不能对list进行操作
                    }
                }
            }
            //如果项目中存在前次改动，则再次备份文件
            if (_itemsChanged.Exists(i=>!i.Enable))
            {
				RemoveConfigChangedInfo(null,null);
            }
            _itemsChanged.Clear();
            return true;
        }

        /// <summary>
        /// 从本地获取配置变更记录
        /// 1.获取最近配置文件（上次初始化时保存的backup）（配置visible=false项不做对比）
        /// 2.和当前配置做对比
        /// </summary>
        private void GetConfigChangedInfo()
        {
            if (!Directory.Exists(_scConfigChangedBackupPath))
            {
                Directory.CreateDirectory(_scConfigChangedBackupPath);
            }
            DirectoryInfo folder = new DirectoryInfo(@_scConfigChangedBackupPath);
            List<FileInfo> files = folder.GetFiles("*.configbackup", SearchOption.TopDirectoryOnly).ToList();
			if (files == null || files.Count() == 0)        //找不到文件
			{
				RemoveConfigChangedInfo(null, null);
				return;
			}
            var qry = from x in files
                      orderby x.LastWriteTime
                      select x;
            string newestBackupPath = qry.Last().FullName;
			if (!string.IsNullOrEmpty(newestBackupPath))
			{
				try
				{
					var xmlDocument = new XmlDocument();
					xmlDocument.Load(newestBackupPath);
					var xmlNodeList = xmlDocument.SelectNodes("root/scdata");
					bool isConfigChanged = false;
					foreach (XmlElement item in xmlNodeList)
					{
						var name = item.GetAttribute("name");
						if (_items.ContainsKey(name))
						{
							if (!_items[name].ValueEquals(item.GetAttribute("value")) && _items[name].Visible)
							{
								isConfigChanged = true;
								_itemsChanged.Add(new ConfigChangedInfo
								{
									Name = name,
									OldValue = item.GetAttribute("value"),
									Enable = false
								});
							}
						}
					}
					//if (isConfigChanged)
					//{
					//    File.Copy(_scDataFile, _scConfigChangedBackupPath + "\\config." + 
					//        DateTime.Now.ToString().Replace(":", "").Replace("/", "").Replace(" ", "")+".backup");
					//}
				}
				catch (Exception ex)
				{
					EV.PostInfoLog("System", $"SC configbackup file{newestBackupPath} open failed, reason{ex.ToString()}");
					RemoveConfigChangedInfo(null, null);
					return;
				}
			}
        }

		/// <summary>
		/// 配置有变动时，下次启动软件会弹窗确认按钮按下后会将配置backup，下次启动不再显示此次变更记录
		/// </summary>
		/// <param name="arg1"></param>
		/// <param name="arg2"></param>
		/// <returns></returns>
        private bool RemoveConfigChangedInfo(string arg1, object[] arg2)
        {
            try
            {
                File.Copy(_scDataFile, _scConfigChangedBackupPath + "\\" +
                        DateTime.Now.ToString().Replace(":", "").Replace("/", "").Replace(" ", "") + ".configbackup");
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private bool InvokeSetConfig(string cmd, object[] parameters)
		{
			var scName = (string)parameters[0];
			var scValue = parameters[1];
			if (!ContainsItem(scName))
			{
				EV.PostWarningLog("System", $"Not find SC with name {scName}");
				return false;
			}
			var configValueAsObject = GetConfigValueAsObject(scName);
			if (InitializeItemValue(_items[scName], scValue))
			{
				GenerateDataFile();
				EV.PostInfoLog("System", $"SC {scName} value changed from {configValueAsObject} to {parameters[1]}");
                DataChangedBackup(_items[scName], configValueAsObject);
            }
			return true;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="item"></param>
		/// <param name="oldvalue"></param>
        private void DataChangedBackup(SCConfigItem item, object oldvalue)
        {
            bool isExist = false;
            for (int i = _itemsChanged.Count - 1; i >= 0; i--)
            {
                if (_itemsChanged[i].Name == item.PathName)
                {
                    isExist = true;
                    object newvalue = (item.Type switch
                    {
                        "Bool" => item.BoolValue,
                        "Integer" => item.IntValue,
                        "Double" => item.DoubleValue,
                        "String" => item.StringValue,
                        "Color" => item.ColorValue,
                        _ => null,
                    });
                    if (newvalue.ToString() == _itemsChanged[i].OldValue)
                    {
                        _itemsChanged.Remove(_itemsChanged[i]);
                    }
                    break;
                }
            }
            if (!isExist)
            {
                _itemsChanged.Add(new ConfigChangedInfo
                {
                    Name = item.PathName,
                    OldValue = oldvalue.ToString(),
                    Enable = true
                });
            }

        }

        public void Terminate()
		{
		}

		public string GetFileContent()
		{
			if (!File.Exists(_scConfigFile))
			{
				return "";
			}
			var stringBuilder = new StringBuilder();
			try
			{
				using var streamReader = new StreamReader(_scConfigFile);
				while (!streamReader.EndOfStream)
				{
					stringBuilder.Append(streamReader.ReadLine());
				}
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
				return "";
			}
			return stringBuilder.ToString();
		}

		private void BuildItems(string xmlFile)
		{
			var xmlDocument = new XmlDocument();
			try
			{
				xmlDocument.Load(xmlFile);
				var xmlNodeList = xmlDocument.SelectNodes("root/configs");
				foreach (XmlElement item in xmlNodeList)
				{
					BuildPathConfigs(item.GetAttribute("name"), item);
				}
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		private void BuildPathConfigs(string parentPath, XmlElement configElement)
		{
			var xmlNodeList = configElement.SelectNodes("configs");
			foreach (XmlElement item in xmlNodeList)
			{
				if (string.IsNullOrEmpty(parentPath))
				{
					BuildPathConfigs(item.GetAttribute("name"), item);
				}
				else
				{
					BuildPathConfigs(parentPath + "." + item.GetAttribute("name"), item);
				}
			}
			var xmlNodeList2 = configElement.SelectNodes("config");
			foreach (XmlElement item2 in xmlNodeList2)
			{
				var sCConfigItem = new SCConfigItem
				{
					Default = item2.GetAttribute("default"),
					Name = item2.GetAttribute("name"),
					Description = item2.GetAttribute("description"),
					Max = item2.GetAttribute("max"),
					Min = item2.GetAttribute("min"),
					Parameter = item2.GetAttribute("paramter"),
					Path = parentPath,
					Tag = item2.GetAttribute("tag"),
					Type = item2.GetAttribute("type"),
					Unit = item2.GetAttribute("unit")
                };

				bool visible;
				if (bool.TryParse(item2.GetAttribute("visible"),out visible))
				{
                    sCConfigItem.Visible = visible;
                }

                InitializeItemValue(sCConfigItem, sCConfigItem.Default);
				if (_items.ContainsKey(sCConfigItem.PathName))
				{
					LOG.Error("Duplicated SC item, " + sCConfigItem.PathName);
				}
				_items[sCConfigItem.PathName] = sCConfigItem;
			}
		}

		private void BackupAndRecoverDataFile()
		{
			try
			{
				if (File.Exists(_scDataFile) && IsXmlFileLoadable(_scDataFile))
				{
					File.Copy(_scDataFile, _scDataBackupFile, overwrite: true);
				}
				else if (File.Exists(_scDataBackupFile) && IsXmlFileLoadable(_scDataBackupFile))
				{
					if (File.Exists(_scDataFile))
					{
						File.Copy(_scDataFile, _scDataErrorFile + DateTime.Now.ToString("yyyyMMdd_HHmmss"), overwrite: true);
					}
					File.Copy(_scDataBackupFile, _scDataFile, overwrite: true);
				}
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		private void CustomData()
		{
			var dictionary = new Dictionary<string, string>();
			try
			{
				if (!File.Exists(_scDataFile))
				{
					return;
				}
				var xmlDocument = new XmlDocument();
				xmlDocument.Load(_scDataFile);
				var xmlNodeList = xmlDocument.SelectNodes("root/scdata");
				foreach (XmlElement item in xmlNodeList)
				{
					var attribute = item.GetAttribute("name");
					if (_items.ContainsKey(attribute))
					{
						InitializeItemValue(_items[attribute], item.GetAttribute("value"));
					}
				}
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		private void GenerateDataFile()
		{
			try
			{
				var doc = new XmlDocument();
				doc.LoadXml("<?xml version=\"1.0\" encoding=\"utf-8\"?><root></root>");
				var root = doc.SelectSingleNode("root") as XmlElement;
				foreach (var item in _items)
				{
					var xmlElement2 = doc.CreateElement("scdata");
					xmlElement2.SetAttribute("name", item.Key);
					xmlElement2.SetAttribute("value", item.Value.Value.ToString());
					root.AppendChild(xmlElement2);
				}
				if (File.Exists(_scDataFile) && IsXmlFileLoadable(_scDataFile))
				{
					File.Copy(_scDataFile, _scDataBackupFile, overwrite: true);
				}
				using var fileStream = new FileStream(_scDataFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 1024, FileOptions.WriteThrough);
				fileStream.SetLength(0L);
				var xmlWriterSettings = new XmlWriterSettings();
				xmlWriterSettings.Indent = true;
				xmlWriterSettings.OmitXmlDeclaration = false;
				using var w = XmlWriter.Create(fileStream, xmlWriterSettings);
				doc.Save(w);
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
			}
		}

		private bool IsXmlFileLoadable(string file)
		{
			try
			{
				var xmlDocument = new XmlDocument();
				xmlDocument.Load(file);
			}
			catch (Exception ex)
			{
				LOG.Write(ex);
				return false;
			}
			return true;
		}

		public SCConfigItem GetConfigItem(string name)
		{
			if (!_items.ContainsKey(name))
			{
				return null;
			}
			return _items[name];
		}

		public bool ContainsItem(string name)
		{
			return _items.ContainsKey(name);
		}

		public object GetConfigValueAsObject(string name)
		{
			var configItem = GetConfigItem(name);
			return configItem.Type switch
			{
				"Bool" => configItem.BoolValue, 
				"Integer" => configItem.IntValue, 
				"Double" => configItem.DoubleValue, 
				"String" => configItem.StringValue, 
				"Color" => configItem.ColorValue, 
				_ => null, 
			};
		}

		public T GetValue<T>(string name) where T : struct
		{
			try
			{
				if (typeof(T) == typeof(bool))
				{
					return (T)(object)_items[name].BoolValue;
				}
				if (typeof(T) == typeof(int))
				{
					return (T)(object)_items[name].IntValue;
				}
				if (typeof(T) == typeof(double))
				{
					return (T)(object)_items[name].DoubleValue;
				}
				if (typeof(T) == typeof(Color))
				{
					return (T)(object)_items[name].ColorValue;
				}
			}
			catch (KeyNotFoundException)
			{
				EV.PostAlarmLog("System", "Can not find system config item " + name);
				return default(T);
			}
			catch (Exception)
			{
				EV.PostAlarmLog("System", "Can not get valid system config item value " + name);
				return default(T);
			}
			Debug.Assert(condition: false, "unsupported type");
			return default(T);
		}

		public string GetStringValue(string name)
		{
			if (!_items.ContainsKey(name))
			{
				return null;
			}
			return _items[name].StringValue;
		}

		public T SafeGetValue<T>(string name, T defaultValue) where T : struct
		{
			try
			{
				if (typeof(T) == typeof(bool))
				{
					return (T)(object)_items[name].BoolValue;
				}
				if (typeof(T) == typeof(int))
				{
					return (T)(object)_items[name].IntValue;
				}
				if (typeof(T) == typeof(double))
				{
					return (T)(object)_items[name].DoubleValue;
				}

				if (typeof(T) == typeof(Color))
					return (T)(object)_items[name].ColorValue;
			}
			catch (KeyNotFoundException)
			{
				return defaultValue;
			}
			catch (Exception)
			{
				return defaultValue;
			}
			Debug.Assert(condition: false, "unsupported type");
			return defaultValue;
		}

		public string SafeGetStringValue(string name, string defaultValue)
		{
			if (!_items.ContainsKey(name))
			{
				return defaultValue;
			}
			return _items[name].StringValue;
		}

		public List<SCConfigItem> GetItemList()
		{
			return _items.Values.ToList();
		}

		private bool InitializeItemValue(SCConfigItem scItem, object value)
		{
			return scItem?.SafeSetValue(value) ?? false;
		}

		public void SetItemValue(string name, object value, bool temporarily)
		{
			Debug.Assert(_items.ContainsKey(name), "can not find sc name, " + name);
			if (!_items.ContainsKey(name))
				return;

			if (_items[name].SetValue(value) && !temporarily)
			{
				GenerateDataFile();
			}
		}

        /// <inheritdoc/>
        public void SetItemValue(string name, bool value, bool temporarily)
		{
			Debug.Assert(_items.ContainsKey(name), "can not find sc name, " + name);
			Debug.Assert(_items[name].Type == "Bool", "sc type not bool, defined as" + _items[name].Type);
			
			if (_items[name].SetValue(value) && !temporarily)
			{
				GenerateDataFile();
			}
		}

        /// <inheritdoc/>
        public void SetItemValue(string name, int value, bool temporarily)	
		{
			Debug.Assert(_items.ContainsKey(name), "can not find sc name, " + name);
			Debug.Assert(_items[name].Type == "Integer", "sc type not bool, defined as" + _items[name].Type);
			if (_items[name].SetValue(value) && !temporarily)
			{
				GenerateDataFile();
			}
		}

        /// <inheritdoc/>
        public void SetItemValue(string name, double value, bool temporarily)
		{
			Debug.Assert(_items.ContainsKey(name), "can not find sc name, " + name);
			Debug.Assert(_items[name].Type == "Double", "sc type not bool, defined as" + _items[name].Type);
			if (_items[name].SetValue(value) && !temporarily)
			{
				GenerateDataFile();
			}
		}

        /// <inheritdoc/>
        public void SetItemValue(string name, string value, bool temporarily)
		{
			Debug.Assert(_items.ContainsKey(name), "can not find sc name, " + name);
			if (_items[name].SetValue(value) && !temporarily)
			{
				GenerateDataFile();
			}
		}
        
		/// <inheritdoc/>
		public void SetItemValue(string name, Color value, bool temporarily)
		{
			Debug.Assert(_items.ContainsKey(name), "can not find sc name, " + name);
			if (_items[name].SetValue(value) && !temporarily)
			{
				GenerateDataFile();
			}
		}

        /// <summary>
        ///  <inheritdoc/>
        /// </summary>
        /// <param name="name"></param>
        /// <param name="callback"></param>
        public void RegisterValueChangedCallback(string name, Action<object> callback)
        {
            if (_items.TryGetValue(name, out var scItem))
            {
                scItem.OnValueChanged += (sender, newValue) =>
                {
                    try
                    {
                        callback.Invoke(newValue);
                    }
                    catch (Exception ex)
                    {
                        LOG.Error($"Failed to call OnValueChanged of {name}, {ex.Message}", ex);
                    }
                };
            }
            else
            {
                LOG.Error($"Failed to register value changed callback due to SC Path '{name}' not found");
            }
        }

        #endregion
    }
}
