using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using NotepadEnhanced.Helpers;
using NotepadEnhanced.Localization;

namespace NotepadEnhanced.AppState
{
    /// <summary>
    /// Provides base functionality for an user/application settings class.
    /// The implementation provides automated binary serialization to the users AppData 
    /// </summary>
    [Serializable]
    public abstract class SettingsBase<T> where T : SettingsBase<T>, new()
    {
        static SettingsBase()
        {
            Instance = new T(); // We have to instantiate to get the SavePath

            // If file exist, load from it, otherwise set this instance properties to default
            if (File.Exists(Instance.SavePath))
            {
                try
                {
                    using (var stream = File.OpenRead(Instance.SavePath))
                        Instance = (T)(new BinaryFormatter().Deserialize(stream));
                }
                catch
                {
                    MsgBox.Show(Strings.SettingsReset);
                    Instance.Reset();
                }
            }
            else Instance.Reset();
        }

        /// <summary>
        /// Deletes the settings file and loads default values
        /// </summary>
        public void Clear()
        {
            string fileName = SavePath;

            if (File.Exists(fileName))
            {
                File.Delete(fileName);
                Reset();
            }
        }

        /// <summary>
        /// Provides implementation for restoring settings to their default values
        /// </summary>
        public abstract void Reset();

        /// <summary>
        /// Gets the save path with the format as such: 
        /// <example>...AppData\Roaming\{AssemblyName}\{TypeName}.dat</example>
        /// </summary>
        protected virtual string SavePath
        {
            get
            {
                string dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
                dir = Path.Combine(dir, AssemblyName);
                dir = Path.Combine(dir, Application.ProductVersion);
                string typeName = this.GetType().Name.Replace('_', '\0');
                return Path.Combine(dir, typeName + ".dat");
            }
        }

        /// <summary>
        /// Gets the save location of these settings
        /// </summary>
        public string GetSaveDirectory()
        {
            return Path.GetDirectoryName(SavePath);
        }

        /// <summary>
        /// Opens the directory in which the settings are saved in windows explorer
        /// </summary>
        public void OpenSettingsDir()
        {
            //Process.Start(Path.GetDirectoryName(SavePath));
        }

        /// <summary>
        /// Saves the settings (to a place specified by the DataSave property). If the 
        /// directory for the settings does not exist, then it is created
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Raises when serialization has failed</exception>
        /// <exception cref="System.IO.EndOfStreamException">Raises when the settings file is corrupt</exception>
        /// <exception cref="System.UnauthorizedAccessException"></exception>
        public void Save()
        {
            string fileName = SavePath;
            string directory = Path.GetDirectoryName(fileName);

            // The directory might not exist already
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);

            using (var fileStreamtream = File.OpenWrite(fileName))
                new BinaryFormatter().Serialize(fileStreamtream, this);
        }

        /// <summary>
        /// Removes a set of characters from the specified string
        /// </summary>
        private static string RemoveChars(string text, params char[] characters)
        {
            for (int i = 0; i < text.Length; i++)
            {
                if (characters.Any(t => text[i] == t))
                    text = text.Remove(i--, 1);
            }

            return text;
        }

        /// <summary>
        /// Gets the assembly name with word seperators removed
        /// </summary>
        private static string AssemblyName
        {
            get
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                // Get rid of word seperation
                return RemoveChars(assembly.GetName().Name, '_', '-', ' ');
            }
        }

        /// <summary>
        /// Gets the only instance of this class
        /// </summary>
        public static T Instance { get; private set; }
    }
}
