using System;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;

namespace TweetDuck.Configuration{
    [Serializable]
    sealed class UserConfig{
        private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };

        private class LegacyBinder : SerializationBinder{
            public override Type BindToType(string assemblyName, string typeName){
                return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck"), assemblyName.Replace("TweetDck", "TweetDuck")));
            }
        }

        private const int CurrentFileVersion = 9;

        // START OF CONFIGURATION

        public WindowState BrowserWindow { get; set; }
        public bool DisplayNotificationColumn { get; set; }
        public bool DisplayNotificationTimer { get; set; }
        public bool NotificationTimerCountDown { get; set; }
        public bool NotificationSkipOnLinkClick { get; set; }
        public bool NotificationNonIntrusiveMode { get; set; }

        public TweetNotification.Position NotificationPosition { get; set; }
        public Point CustomNotificationPosition { get; set; }
        public int NotificationEdgeDistance { get; set; }
        public int NotificationDisplay { get; set; }
        public int NotificationIdlePauseSeconds { get; set; }
        public int NotificationDurationValue { get; set; }

        public bool EnableSpellCheck { get; set; }
        public bool ExpandLinksOnHover { get; set; }
        public bool SwitchAccountSelectors { get; set; }
        public bool EnableTrayHighlight { get; set; }

        public bool EnableUpdateCheck { get; set; }
        public string DismissedUpdate { get; set; }
        
        public WindowState PluginsWindow { get; set; }

        public string CustomCefArgs { get; set; }
        public string CustomBrowserCSS { get; set; }
        public string CustomNotificationCSS { get; set; }

        public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;

        public string NotificationSoundPath{
            get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
            set => notificationSoundPath = value;
        }

        public bool MuteNotifications{
            get => muteNotifications;

            set{
                if (muteNotifications != value){
                    muteNotifications = value;
                    MuteToggled?.Invoke(this, new EventArgs());
                }
            }
        }

        public int ZoomLevel{
            get => zoomLevel;

            set{
                if (zoomLevel != value){
                    zoomLevel = value;
                    ZoomLevelChanged?.Invoke(this, new EventArgs());
                }
            }
        }

        public double ZoomMultiplier => zoomLevel/100.0;

        public TrayIcon.Behavior TrayBehavior{
            get => trayBehavior;

            set{
                if (trayBehavior != value){
                    trayBehavior = value;
                    TrayBehaviorChanged?.Invoke(this, new EventArgs());
                }
            }
        }

        // END OF CONFIGURATION
        
        [field:NonSerialized]
        public event EventHandler MuteToggled;
        
        [field:NonSerialized]
        public event EventHandler ZoomLevelChanged;
        
        [field:NonSerialized]
        public event EventHandler TrayBehaviorChanged;

        [NonSerialized]
        private string file;

        private int fileVersion;
        private bool muteNotifications;
        private int zoomLevel;
        private string notificationSoundPath;
        private TrayIcon.Behavior trayBehavior;

        private UserConfig(string file){
            this.file = file;

            BrowserWindow = new WindowState();
            ZoomLevel = 100;
            DisplayNotificationTimer = true;
            NotificationNonIntrusiveMode = true;
            NotificationPosition = TweetNotification.Position.TopRight;
            CustomNotificationPosition = ControlExtensions.InvisibleLocation;
            NotificationEdgeDistance = 8;
            NotificationDurationValue = 25;
            EnableUpdateCheck = true;
            ExpandLinksOnHover = true;
            SwitchAccountSelectors = true;
            EnableTrayHighlight = true;
            PluginsWindow = new WindowState();
        }

        private void UpgradeFile(){
            if (fileVersion == CurrentFileVersion){
                return;
            }

            // if outdated, cycle through all versions
            if (fileVersion == 0){
                DisplayNotificationTimer = true;
                EnableUpdateCheck = true;
                ++fileVersion;
            }

            if (fileVersion == 1){
                ExpandLinksOnHover = true;
                ++fileVersion;
            }

            if (fileVersion == 2){
                BrowserWindow = new WindowState();
                PluginsWindow = new WindowState();
                ++fileVersion;
            }

            if (fileVersion == 3){
                EnableTrayHighlight = true;
                NotificationDurationValue = 25;
                ++fileVersion;
            }

            if (fileVersion == 4){
                ++fileVersion;
            }

            if (fileVersion == 5){
                ++fileVersion;
            }

            if (fileVersion == 6){
                NotificationNonIntrusiveMode = true;
                ++fileVersion;
            }

            if (fileVersion == 7){
                ZoomLevel = 100;
                ++fileVersion;
            }

            if (fileVersion == 8){
                SwitchAccountSelectors = true;
                ++fileVersion;
            }

            // update the version
            fileVersion = CurrentFileVersion;
            Save();
        }

        public bool Save(){
            try{
                string directory = Path.GetDirectoryName(file);
                if (directory == null)return false;

                Directory.CreateDirectory(directory);

                if (File.Exists(file)){
                    string backupFile = GetBackupFile(file);
                    File.Delete(backupFile);
                    File.Move(file, backupFile);
                }

                using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
                    Formatter.Serialize(stream, this);
                }

                return true;
            }catch(Exception e){
                Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
                return false;
            }
        }
        
        public static UserConfig Load(string file){
            UserConfig config = null;
            Exception firstException = null;

            for(int attempt = 0; attempt < 2; attempt++){
                try{
                    using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file), FileMode.Open, FileAccess.Read, FileShare.Read)){
                        if ((config = Formatter.Deserialize(stream) as UserConfig) != null){
                            config.file = file;
                        }
                    }

                    config?.UpgradeFile();
                    break;
                }catch(FileNotFoundException){
                }catch(DirectoryNotFoundException){
                    break;
                }catch(Exception e){
                    if (attempt == 0){
                        firstException = e;
                        Program.Reporter.Log(e.ToString());
                    }
                    else if (firstException != null){
                        Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e);
                    }
                }
            }

            if (firstException != null && config == null){
                Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
            }

            return config ?? new UserConfig(file);
        }

        public static string GetBackupFile(string file){
            return file+".bak";
        }
    }
}