using System;
using System.Collections.Generic;
using System.Drawing;
using TweetDuck.Configuration.Instance;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;

namespace TweetDuck.Configuration{
    sealed class ConfigManager{
        public UserConfig User { get; }
        public SystemConfig System { get; }
        public PluginConfig Plugins { get; }
        
        public event EventHandler ProgramRestartRequested;

        private readonly FileConfigInstance<UserConfig> infoUser;
        private readonly FileConfigInstance<SystemConfig> infoSystem;
        private readonly PluginConfigInstance infoPlugins;

        private readonly IConfigInstance<BaseConfig>[] infoList;

        public ConfigManager(){
            User = new UserConfig(this);
            System = new SystemConfig(this);
            Plugins = new PluginConfig(this);
            
            infoList = new IConfigInstance<BaseConfig>[]{
                infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
                infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
                infoPlugins = new PluginConfigInstance(Program.PluginConfigFilePath, Plugins)
            };

            // TODO refactor further

            infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);

            infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
                ConvertToString = value => $"{value.X} {value.Y}",
                ConvertToObject = value => {
                    int[] elements = StringUtils.ParseInts(value, ' ');
                    return new Point(elements[0], elements[1]);
                }
            });
            
            infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
                ConvertToString = value => $"{value.Width} {value.Height}",
                ConvertToObject = value => {
                    int[] elements = StringUtils.ParseInts(value, ' ');
                    return new Size(elements[0], elements[1]);
                }
            });
        }

        public void LoadAll(){
            infoUser.Load();
            infoSystem.Load();
            infoPlugins.Load();
        }

        public void SaveAll(){
            infoUser.Save();
            infoSystem.Save();
            infoPlugins.Save();
        }

        public void ReloadAll(){
            infoUser.Reload();
            infoSystem.Reload();
            infoPlugins.Reload();
        }

        private void TriggerProgramRestartRequested(){
            ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
        }

        private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
            Type instanceType = instance.GetType();
            return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
        }

        public abstract class BaseConfig{
            private readonly ConfigManager configManager;

            protected BaseConfig(ConfigManager configManager){
                this.configManager = configManager;
            }

            // Management

            public void Save(){
                configManager.GetInstanceInfo(this).Save();
            }

            public void Reload(){
                configManager.GetInstanceInfo(this).Reload();
            }

            public void Reset(){
                configManager.GetInstanceInfo(this).Reset();
            }

            // Construction methods

            public T ConstructWithDefaults<T>() where T : BaseConfig{
                return ConstructWithDefaults(configManager) as T;
            }

            protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);

            // Utility methods

            protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
                if (!EqualityComparer<T>.Default.Equals(field, value)){
                    field = value;
                    eventHandler?.Invoke(this, EventArgs.Empty);
                }
            }

            protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
                if (!EqualityComparer<T>.Default.Equals(field, value)){
                    field = value;
                    configManager.TriggerProgramRestartRequested();
                }
            }
        }
    }
}