using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using BepInEx.Common;
using BepInEx.Logging;
namespace BepInEx
{
    /// 
    /// A helper class to handle persistent data.
    /// 
    public static class Config
    {
        private static readonly Dictionary> cache = new Dictionary>();
        private static string configPath => Path.Combine(Common.Utility.PluginsDirectory, "config.ini");
        private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
        private static void RaiseConfigReloaded()
        {
            ConfigReloaded?.Invoke();
        }
		/// 
		/// An event that is fired every time the config is reloaded.
		/// 
        public static event Action ConfigReloaded;
        /// 
        /// If enabled, writes the config to disk every time a value is set.
        /// 
        public static bool SaveOnConfigSet { get; set; } = true;
        static Config()
        {
            if (File.Exists(configPath))
            {
                ReloadConfig();
            }
            else
            {
                SaveConfig();
            }
        }
	    /// 
	    /// Returns the value of the key if found, otherwise returns the default value.
	    /// 
	    /// The key to search for.
	    /// The default value to return if the key is not found.
	    /// The section of the config to search the key for.
	    /// The value of the key.
	    public static string GetEntry(string key, string defaultValue = "", string section = "")
        {
	        try
	        {
				key = Sanitize(key);
				section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
				if (!cache.TryGetValue(section, out Dictionary subdict))
				{
					SetEntry(key, defaultValue, section);
					return defaultValue;
				}
				if (subdict.TryGetValue(key, out string value))
					return value;
				SetEntry(key, defaultValue, section);
				return defaultValue;
			}
	        catch (Exception ex)
	        {
		        Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to read config entry!");
		        Logger.Log(LogLevel.Error, ex);
		        return defaultValue;
	        }
        }
        /// 
        /// Reloads the config from disk. Unwritten changes are lost.
        /// 
        public static void ReloadConfig()
        {
            cache.Clear();
            string currentSection = "";
            foreach (string rawLine in File.ReadAllLines(configPath))
            {
                string line = rawLine.Trim();
                bool commentIndex = line.StartsWith(";") || line.StartsWith("#");
                if (commentIndex) //trim comment
                    continue;
                if (line.StartsWith("[") && line.EndsWith("]")) //section
                {
                    currentSection = line.Substring(1, line.Length - 2);
                    continue;
                }
                string[] split = line.Split('='); //actual config line
                if (split.Length != 2)
                    continue; //empty/invalid line
                if (!cache.ContainsKey(currentSection))
                    cache[currentSection] = new Dictionary();
                cache[currentSection][split[0]] = split[1];
            }
            RaiseConfigReloaded();
        }
        /// 
        /// Writes the config to disk.
        /// 
        public static void SaveConfig()
        {
            using (StreamWriter writer = new StreamWriter(File.Create(configPath), System.Text.Encoding.UTF8))
                foreach (var sectionKv in cache)
                {
                    writer.WriteLine($"[{sectionKv.Key}]");
                    foreach (var entryKv in sectionKv.Value)
                        writer.WriteLine($"{entryKv.Key}={entryKv.Value}");
                    writer.WriteLine();
                }
        }
        /// 
        /// Sets the value of the key in the config.
        /// 
        /// The key to set the value to.
        /// The value to set.
        public static void SetEntry(string key, string value, string section = "")
        {
	        try
	        {
		        key = Sanitize(key);
		        section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
		        if (!cache.TryGetValue(section, out Dictionary subdict))
		        {
			        subdict = new Dictionary();
			        cache[section] = subdict;
		        }
		        subdict[key] = value;
		        if (SaveOnConfigSet)
			        SaveConfig();
	        }
	        catch (Exception ex)
	        {
		        Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to save config entry!");
		        Logger.Log(LogLevel.Error, ex);
	        }
        }
       
        /// 
        /// Returns wether a value is currently set.
        /// 
        /// The key to check against
        /// The section to check in
        /// True if the key is present
        public static bool HasEntry(string key, string section = "")
        {
            key = Sanitize(key);
            section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
            return cache.ContainsKey(section) && cache[section].ContainsKey(key);
        }
        /// 
        /// Removes a value from the config.
        /// 
        /// The key to remove
        /// The section to remove from
        /// True if the key was removed
        public static bool UnsetEntry(string key, string section = "")
        {
            key = Sanitize(key);
            section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
            if (!HasEntry(key, section))
                return false;
            cache[section].Remove(key);
            return true;
        }
		/// 
		/// Replaces any potentially breaking input with underscores.
		/// 
		/// The text to sanitize.
		/// Sanitized text.
        public static string Sanitize(string text)
        {
            return sanitizeKeyRegex.Replace(text, "_");
        }
        #region Extensions
        public static string GetEntry(this BaseUnityPlugin plugin, string key, string defaultValue = "")
        {
            return GetEntry(key, defaultValue, MetadataHelper.GetMetadata(plugin).GUID);
        }
        public static void SetEntry(this BaseUnityPlugin plugin, string key, string value)
        {
            SetEntry(key, value, MetadataHelper.GetMetadata(plugin).GUID);
        }
        public static bool HasEntry(this BaseUnityPlugin plugin, string key)
        {
            return HasEntry(key, MetadataHelper.GetMetadata(plugin).GUID);
        }
        #endregion Extensions
    }
}