using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Xml;
namespace Webserver {
	public class WebPermissions {
		private const string permissionsFileName = "webpermissions.xml";
		private static WebPermissions instance;
		private readonly FileSystemWatcher fileWatcher;
		private readonly WebModulePermission defaultModulePermission = new WebModulePermission ("", 0);
		/// 
		///  Registered user/pass admin tokens
		/// 
		private readonly Dictionary adminTokens = new CaseInsensitiveStringDictionary ();
		/// 
		/// Contains all registered modules and their default permission
		/// 
		private readonly Dictionary knownModules = new CaseInsensitiveStringDictionary ();
		/// 
		/// Manually defined module permissions
		/// 
		private readonly Dictionary modulePermissions =
			new CaseInsensitiveStringDictionary ();
		/// 
		/// Public list of all modules, both those with custom permissions as well as those that do not with their default permission
		/// 
		private readonly List allModulesList = new List ();
		private readonly ReadOnlyCollection allModulesListRo;
		private static string SettingsFilePath => GamePrefs.GetString (EnumUtils.Parse ("SaveGameFolder"));
		private static string SettingsFileName => permissionsFileName;
		private static string SettingsFullPath => SettingsFilePath + "/" + SettingsFileName;
		private WebPermissions () {
			allModulesListRo = new ReadOnlyCollection (allModulesList);
			Directory.CreateDirectory (SettingsFilePath);
			Load ();
			fileWatcher = new FileSystemWatcher (SettingsFilePath, SettingsFileName);
			fileWatcher.Changed += OnFileChanged;
			fileWatcher.Created += OnFileChanged;
			fileWatcher.Deleted += OnFileChanged;
			fileWatcher.EnableRaisingEvents = true;
		}
		public static WebPermissions Instance {
			get {
				lock (typeof (WebPermissions)) {
					return instance ??= new WebPermissions ();
				}
			}
		}
#region Admin Tokens
		public void AddAdmin (string _name, string _token, int _permissionLevel, bool _save = true) {
			AdminToken c = new AdminToken (_name, _token, _permissionLevel);
			lock (this) {
				adminTokens [_name] = c;
				if (_save) {
					Save ();
				}
			}
		}
		public void RemoveAdmin (string _name, bool _save = true) {
			lock (this) {
				adminTokens.Remove (_name);
				if (_save) {
					Save ();
				}
			}
		}
		public bool IsAdmin (string _name) {
			return adminTokens.ContainsKey (_name);
		}
		public AdminToken[] GetAdmins () {
			AdminToken[] result = new AdminToken[adminTokens.Count];
			adminTokens.CopyValuesTo (result);
			return result;
		}
		public AdminToken GetWebAdmin (string _name, string _token) {
			if (IsAdmin (_name) && adminTokens [_name].token == _token) {
				return adminTokens [_name];
			}
			return null;
		}
#endregion
#region Modules
		public void AddModulePermission (string _module, int _permissionLevel, bool _save = true) {
			WebModulePermission p = new WebModulePermission (_module, _permissionLevel);
			lock (this) {
				allModulesList.Clear ();
				modulePermissions [_module] = p;
				if (_save) {
					Save ();
				}
			}
		}
		public void AddKnownModule (string _module, int _defaultPermission) {
			if (string.IsNullOrEmpty (_module)) {
				return;
			}
			WebModulePermission p = new WebModulePermission (_module, _defaultPermission);
			lock (this) {
				allModulesList.Clear ();
				knownModules [_module] = p;
			}
		}
		public bool IsKnownModule (string _module) {
			if (string.IsNullOrEmpty (_module)) {
				return false;
			}
			lock (this) {
				return knownModules.ContainsKey (_module);
			}
		}
		public void RemoveModulePermission (string _module, bool _save = true) {
			lock (this) {
				allModulesList.Clear ();
				modulePermissions.Remove (_module);
				if (_save) {
					Save ();
				}
			}
		}
		public IList GetModules () {
			if (allModulesList.Count != 0) {
				return allModulesListRo;
			}
			foreach ((string moduleName, WebModulePermission moduleDefaultPerm) in knownModules) {
				allModulesList.Add (modulePermissions.TryGetValue (moduleName, out WebModulePermission modulePermission)
					? modulePermission
					: moduleDefaultPerm);
			}
			return allModulesListRo;
		}
		public bool ModuleAllowedWithLevel (string _module, int _level) {
			WebModulePermission permInfo = GetModulePermission (_module);
			return permInfo.permissionLevel >= _level;
		}
		public WebModulePermission GetModulePermission (string _module) {
			if (modulePermissions.TryGetValue (_module, out WebModulePermission result)) {
				return result;
			}
			return knownModules.TryGetValue (_module, out result) ? result : defaultModulePermission;
		}
#endregion
#region IO Tasks
		private void OnFileChanged (object _source, FileSystemEventArgs _e) {
			Log.Out ("[Web] [Perms] Reloading " + SettingsFileName);
			Load ();
		}
		public void Load () {
			adminTokens.Clear ();
			modulePermissions.Clear ();
			if (!File.Exists (SettingsFullPath)) {
				Log.Out ($"[Web] [Perms] Permissions file '{SettingsFileName}' not found, creating.");
				Save ();
				return;
			}
			Log.Out ($"[Web] [Perms] Loading permissions file at '{SettingsFullPath}'");
			XmlDocument xmlDoc = new XmlDocument ();
			try {
				xmlDoc.Load (SettingsFullPath);
			} catch (XmlException e) {
				Log.Error ("[Web] [Perms] Failed loading permissions file: " + e.Message);
				return;
			}
			XmlNode adminToolsNode = xmlDoc.DocumentElement;
			if (adminToolsNode == null) {
				Log.Error ("[Web] [Perms] Failed loading permissions file: No DocumentElement found");
				return;
			}
			foreach (XmlNode childNode in adminToolsNode.ChildNodes) {
				switch (childNode.Name) {
					case "admintokens":
						ParseAdminTokens (childNode);
						break;
					case "permissions":
						ParseModulePermissions (childNode);
						break;
				}
			}
			Log.Out ("[Web] [Perms] Loading permissions file done.");
		}
		private void ParseAdminTokens (XmlNode _baseNode) {
			foreach (XmlNode subChild in _baseNode.ChildNodes) {
				if (subChild.NodeType == XmlNodeType.Comment) {
					continue;
				}
				if (subChild.NodeType != XmlNodeType.Element) {
					Log.Warning ($"[Web] [Perms] Unexpected XML node found in 'admintokens' section: {subChild.OuterXml}");
					continue;
				}
				XmlElement lineItem = (XmlElement)subChild;
				if (!lineItem.HasAttribute ("name")) {
					Log.Warning ($"[Web] [Perms] Ignoring admintoken-entry because of missing 'name' attribute: {subChild.OuterXml}");
					continue;
				}
				if (!lineItem.HasAttribute ("token")) {
					Log.Warning ($"[Web] [Perms] Ignoring admintoken-entry because of missing 'token' attribute: {subChild.OuterXml}");
					continue;
				}
				if (!lineItem.HasAttribute ("permission_level")) {
					Log.Warning ($"[Web] [Perms] Ignoring admintoken-entry because of missing 'permission_level' attribute: {subChild.OuterXml}");
					continue;
				}
				string name = lineItem.GetAttribute ("name");
				string token = lineItem.GetAttribute ("token");
				if (!int.TryParse (lineItem.GetAttribute ("permission_level"), out int permissionLevel)) {
					Log.Warning (
						$"[Web] [Perms] Ignoring admintoken-entry because of invalid (non-numeric) value for 'permission_level' attribute: {subChild.OuterXml}");
					continue;
				}
				AddAdmin (name, token, permissionLevel, false);
			}
		}
		private void ParseModulePermissions (XmlNode _baseNode) {
			foreach (XmlNode subChild in _baseNode.ChildNodes) {
				if (subChild.NodeType == XmlNodeType.Comment) {
					continue;
				}
				if (subChild.NodeType != XmlNodeType.Element) {
					Log.Warning ($"[Web] [Perms] Unexpected XML node found in 'permissions' section: {subChild.OuterXml}");
					continue;
				}
				XmlElement lineItem = (XmlElement)subChild;
				if (!lineItem.HasAttribute ("module")) {
					Log.Warning ($"[Web] [Perms] Ignoring permission-entry because of missing 'module' attribute: {subChild.OuterXml}");
					continue;
				}
				if (!lineItem.HasAttribute ("permission_level")) {
					Log.Warning ($"[Web] [Perms] Ignoring permission-entry because of missing 'permission_level' attribute: {subChild.OuterXml}");
					continue;
				}
				if (!int.TryParse (lineItem.GetAttribute ("permission_level"), out int permissionLevel)) {
					Log.Warning (
						$"[Web] [Perms] Ignoring permission-entry because of invalid (non-numeric) value for 'permission_level' attribute: {subChild.OuterXml}");
					continue;
				}
				AddModulePermission (lineItem.GetAttribute ("module"), permissionLevel, false);
			}
		}
		public void Save () {
			XmlDocument xml = new XmlDocument ();
			xml.CreateXmlDeclaration ();
			// xml.AddXmlComment (XmlHeader);
			XmlElement root = xml.AddXmlElement ("webpermissions");
			// AdminTokens
			XmlElement adminTokensElem = root.AddXmlElement ("admintokens");
			adminTokensElem.AddXmlComment ("  ");
			foreach ((string _, AdminToken adminToken) in adminTokens) {
				adminTokensElem.AddXmlElement ("token")
					.SetAttrib ("name", adminToken.name)
					.SetAttrib ("token", adminToken.token)
					.SetAttrib ("permission_level", adminToken.permissionLevel.ToString ());
			}
			XmlElement modulePermissionsElem = root.AddXmlElement ("permissions");
			foreach ((string _, WebModulePermission webModulePermission) in modulePermissions) {
				modulePermissionsElem.AddXmlElement ("permission")
					.SetAttrib ("module", webModulePermission.module)
					.SetAttrib ("permission_level", webModulePermission.permissionLevel.ToString ());
			}
			
			fileWatcher.EnableRaisingEvents = false;
			xml.Save (SettingsFullPath);
			fileWatcher.EnableRaisingEvents = true;
		}
#endregion
		public class AdminToken {
			public readonly string name;
			public readonly int permissionLevel;
			public readonly string token;
			public AdminToken (string _name, string _token, int _permissionLevel) {
				name = _name;
				token = _token;
				permissionLevel = _permissionLevel;
			}
		}
		public struct WebModulePermission {
			public readonly string module;
			public readonly int permissionLevel;
			public WebModulePermission (string _module, int _permissionLevel) {
				module = _module;
				permissionLevel = _permissionLevel;
			}
		}
	}
}