using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Management;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Updates;
using TweetDuck.Updates.Events;

namespace TweetDuck.Core{
    sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
        public enum ThrottleBehavior{ // keep order
            Minimized, Covered, Unfocused
        }

        private static UserConfig Config => Program.UserConfig;

        public bool IsWaiting{
            set{
                if (value){
                    browser.Enabled = false;
                    Cursor = Cursors.WaitCursor;
                }
                else{
                    browser.Enabled = true;
                    Cursor = Cursors.Default;

                    if (Focused){ // re-focus browser only if the window or a child is activated
                        browser.Focus();
                    }
                }
            }
        }

        public string UpdateInstallerPath { get; private set; }
        private bool ignoreUpdateCheckError;
        
        public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;

        private readonly TweetDeckBrowser browser;
        private readonly PluginManager plugins;
        private readonly UpdateHandler updates;
        private readonly FormNotificationTweet notification;
        private readonly ContextMenu contextMenu;

        private bool isLoaded;
        private FormWindowState prevState;
        
        private TweetScreenshotManager notificationScreenshotManager;
        private VideoPlayer videoPlayer;
        private AnalyticsManager analytics;

        public FormBrowser(){
            InitializeComponent();

            Text = Program.BrandName;

            this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
            this.plugins.Reloaded += plugins_Reloaded;
            this.plugins.Executed += plugins_Executed;
            this.plugins.Reload();

            this.notification = new FormNotificationTweet(this, plugins);
            this.notification.Show();
            
            this.browser = new TweetDeckBrowser(this, new TweetDeckBridge.Browser(this, notification));
            this.contextMenu = ContextMenuBrowser.CreateMenu(this);

            this.plugins.Register(browser, PluginEnvironment.Browser, true);

            Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update

            Disposed += (sender, args) => {
                Config.MuteToggled -= Config_MuteToggled;
                Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
                
                browser.Dispose();
                updates.Dispose();
                contextMenu.Dispose();

                notificationScreenshotManager?.Dispose();
                videoPlayer?.Dispose();
                analytics?.Dispose();
            };

            Config.MuteToggled += Config_MuteToggled;

            this.trayIcon.ClickRestore += trayIcon_ClickRestore;
            this.trayIcon.ClickClose += trayIcon_ClickClose;
            Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;

            UpdateTray();
            
            if (Config.MuteNotifications){
                UpdateFormIcon();
            }

            this.updates = new UpdateHandler(browser, Program.InstallerPath);
            this.updates.CheckFinished += updates_CheckFinished;
            this.updates.UpdateAccepted += updates_UpdateAccepted;
            this.updates.UpdateDismissed += updates_UpdateDismissed;

            if (Config.AllowDataCollection){
                analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
            }

            RestoreWindow();
        }

        private void ShowChildForm(Form form){
            form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
            form.Show(this);
        }

        public void ForceClose(){
            trayIcon.Visible = false; // checked in FormClosing event
            Close();
        }

        // window setup

        private void RestoreWindow(){
            Config.BrowserWindow.Restore(this, true);
            prevState = WindowState;
            isLoaded = true;
        }

        private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too
            Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
        }

        private void UpdateTray(){
            trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
        }

        // event handlers

        private void timerResize_Tick(object sender, EventArgs e){
            FormBrowser_ResizeEnd(this, e); // also stops timer
        }

        private void timerThrottle_Tick(object sender, EventArgs e){
            if (!browser.Ready){
                timerThrottle.Interval = 5000;
                return;
            }
            
            bool shouldThrottle;

            switch(Program.SystemConfig.ThrottleBehavior){
                case ThrottleBehavior.Covered:
                    shouldThrottle = WindowState != FormWindowState.Minimized && NativeMethods.IsFormCoveredByLargerWindow(this);
                    break;

                case ThrottleBehavior.Unfocused:
                    shouldThrottle = !FormManager.HasAnyDialogs;
                    break;

                default:
                    timerThrottle.Stop();
                    return;
            }

            if (shouldThrottle){
                browser.SetThrottle(true);
                timerThrottle.Stop();
            }
        }

        private void FormBrowser_Activated(object sender, EventArgs e){
            if (!isLoaded)return;

            trayIcon.HasNotifications = false;

            if (!browser.Enabled){      // when taking a screenshot, the window is unfocused and
                browser.Enabled = true; // the browser is disabled; if the user clicks back into
            }                           // the window, enable the browser again
            
            timerThrottle.Stop();
            browser.SetThrottle(false);
        }

        private void FormBrowser_Deactivate(object sender, EventArgs e){
            timerThrottle.Stop();
            timerThrottle.Interval = Program.SystemConfig.ThrottleBehavior == ThrottleBehavior.Covered ? 30000 : 1000;
            timerThrottle.Start();
        }

        private void FormBrowser_LocationChanged(object sender, EventArgs e){
            if (!isLoaded)return;
            
            timerResize.Stop();
            timerResize.Start();
        }

        private void FormBrowser_Resize(object sender, EventArgs e){
            if (!isLoaded)return;

            if (WindowState != prevState){
                prevState = WindowState;

                if (WindowState == FormWindowState.Minimized){
                    if (Config.TrayBehavior.ShouldHideOnMinimize()){
                        Hide(); // hides taskbar too?! welp that works I guess
                    }
                }
                else{
                    FormBrowser_ResizeEnd(sender, e);
                }
            }
            else{
                timerResize.Stop();
                timerResize.Start();
            }
        }

        private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
            if (!isLoaded)return;

            timerResize.Stop();
            
            if (Location != ControlExtensions.InvisibleLocation){
                Config.BrowserWindow.Save(this);
                Config.Save();
            }
        }

        private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
            if (!isLoaded)return;

            if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){
                Hide(); // hides taskbar too?! welp that works I guess
                e.Cancel = true;
            }
        }

        private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
            if (isLoaded && UpdateInstallerPath == null){
                updates.CleanupDownload();
            }
        }

        private void Config_MuteToggled(object sender, EventArgs e){
            UpdateFormIcon();
            AnalyticsFile.NotificationMutes.Trigger();
        }

        private void Config_TrayBehaviorChanged(object sender, EventArgs e){
            UpdateTray();
        }

        private void trayIcon_ClickRestore(object sender, EventArgs e){
            Show();
            RestoreWindow();
            Activate();
            UpdateTray();
        }

        private void trayIcon_ClickClose(object sender, EventArgs e){
            ForceClose();
        }
        
        private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
            if (e.HasErrors){
                FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
            }

            if (isLoaded){
                browser.ReloadToTweetDeck();
            }
        }
        
        private static void plugins_Executed(object sender, PluginErrorEventArgs e){
            if (e.HasErrors){
                FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
            }
        }

        private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
            e.Result.Handle(update => {
                string tag = update.VersionTag;

                if (tag != Program.VersionTag && tag != Config.DismissedUpdate){
                    updates.PrepareUpdate(update);
                    browser.ShowUpdateNotification(tag, update.ReleaseNotes);
                }
                else{
                    updates.StartTimer();
                }
            }, ex => {
                if (!ignoreUpdateCheckError){
                    Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
                    updates.StartTimer();
                }
            });

            ignoreUpdateCheckError = true;
        }

        private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
            this.InvokeAsyncSafe(() => {
                FormManager.CloseAllDialogs();

                if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
                    Config.DismissedUpdate = null;
                    Config.Save();
                }

                updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
                    UpdateDownloadStatus status = update.DownloadStatus;

                    if (status == UpdateDownloadStatus.Done){
                        UpdateInstallerPath = update.InstallerPath;
                        ForceClose();
                    }
                    else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: "+(update.DownloadError?.Message ?? "unknown error")+"\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
                        BrowserUtils.OpenExternalBrowser(Program.Website);
                        ForceClose();
                    }
                    else{
                        Show();
                    }
                });
            });
        }

        private void updates_UpdateDismissed(object sender, UpdateEventArgs e){
            this.InvokeAsyncSafe(() => {
                Config.DismissedUpdate = e.UpdateInfo.VersionTag;
                Config.Save();
            });
        }

        protected override void WndProc(ref Message m){
            if (isLoaded && m.Msg == Program.WindowRestoreMessage){
                if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
                    trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
                }

                return;
            }
            
            if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
                if (videoPlayer != null && videoPlayer.Running){
                    videoPlayer.Close();
                }
                else{
                    browser.OnMouseClickExtra(m.WParam);
                    AnalyticsFile.BrowserExtraMouseButtons.Trigger();
                }

                return;
            }

            base.WndProc(ref m);
        }

        // bridge methods

        public void PauseNotification(){
            notification.PauseNotification();
        }

        public void ResumeNotification(){
            notification.ResumeNotification();
        }
        
        public void ReinjectCustomCSS(string css){
            browser.ReinjectCustomCSS(css);
        }

        public void ReloadToTweetDeck(){
            #if DEBUG
            Resources.ScriptLoader.HotSwap();
            #endif

            ignoreUpdateCheckError = false;
            browser.ReloadToTweetDeck();
            AnalyticsFile.BrowserReloads.Trigger();
        }

        public void AddSearchColumn(string query){
            browser.AddSearchColumn(query);
        }

        public void TriggerTweetScreenshot(){
            browser.TriggerTweetScreenshot();
        }

        public void ReloadColumns(){
            browser.ReloadColumns();
        }

        public void PlaySoundNotification(){
            browser.PlaySoundNotification();
        }

        public void ApplyROT13(){
            browser.ApplyROT13();
            AnalyticsFile.UsedROT13.Trigger();
        }

        // callback handlers
        
        public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
            if (Config.FirstRun){
                Config.FirstRun = false;
                Config.AllowDataCollection = allowDataCollection;
                Config.Save();

                if (allowDataCollection && analytics == null){
                    analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
                }
            }

            if (showGuide){
                FormGuide.Show();
            }
        }

        public void OpenContextMenu(){
            contextMenu.Show(this, PointToClient(Cursor.Position));
        }

        public void OpenSettings(){
            OpenSettings(null);
        }
        
        public void OpenSettings(Type startTab){
            if (!FormManager.TryBringToFront<FormSettings>()){
                bool prevEnableUpdateCheck = Config.EnableUpdateCheck;

                FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
                
                form.FormClosed += (sender, args) => {
                    if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
                        Config.DismissedUpdate = null;
                        Config.Save();
                        
                        updates.Check(true);
                    }

                    if (!Config.EnableTrayHighlight){
                        trayIcon.HasNotifications = false;
                    }

                    if (Config.AllowDataCollection){
                        if (analytics == null){
                            analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
                        }
                    }
                    else if (analytics != null){
                        analytics.Dispose();
                        analytics = null;
                    }

                    BrowserCache.RefreshTimer();
                    
                    if (form.ShouldReloadBrowser){
                        FormManager.TryFind<FormPlugins>()?.Close();
                        plugins.Reload(); // also reloads the browser
                    }
                    else{
                        browser.UpdateProperties();
                    }

                    notification.RequiresResize = true;
                    form.Dispose();
                };
                
                AnalyticsFile.OpenOptions.Trigger();
                ShowChildForm(form);
            }
        }

        public void OpenAbout(){
            if (!FormManager.TryBringToFront<FormAbout>()){
                AnalyticsFile.OpenAbout.Trigger();
                ShowChildForm(new FormAbout());
            }
        }

        public void OpenPlugins(){
            if (!FormManager.TryBringToFront<FormPlugins>()){
                AnalyticsFile.OpenPlugins.Trigger();
                ShowChildForm(new FormPlugins(plugins));
            }
        }

        public void OpenProfileImport(){
            FormManager.TryFind<FormSettings>()?.Close();

            using(DialogSettingsManage dialog = new DialogSettingsManage(plugins, true)){
                if (dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){
                    BrowserProcessHandler.UpdatePrefs();
                    FormManager.TryFind<FormPlugins>()?.Close();
                    plugins.Reload(); // also reloads the browser
                }
            }
        }

        public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
            if (Config.EnableTrayHighlight && !ContainsFocus){
                trayIcon.HasNotifications = true;
            }
        }

        public void OnTweetSound(){
            AnalyticsFile.SoundNotifications.Trigger();
        }

        public void PlayVideo(string url, string username){
            if (string.IsNullOrEmpty(url)){
                videoPlayer?.Close();
                return;
            }

            if (videoPlayer == null){
                videoPlayer = new VideoPlayer(this);

                videoPlayer.ProcessExited += (sender, args) => {
                    browser.HideVideoOverlay(true);
                };
            }
            
            videoPlayer.Launch(url, username);
            AnalyticsFile.VideoPlays.Trigger();
        }

        public bool ProcessBrowserKey(Keys key){
            if (videoPlayer != null && videoPlayer.Running){
                videoPlayer.SendKeyEvent(key);
                return true;
            }

            return false;
        }

        public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
            Activate();

            if (!browser.IsTweetDeckWebsite){
                FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
                return;
            }

            notification.FinishCurrentNotification();
            browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
            AnalyticsFile.TweetDetails.Trigger();
        }

        public void OnTweetScreenshotReady(string html, int width){
            if (notificationScreenshotManager == null){
                notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
            }

            notificationScreenshotManager.Trigger(html, width);
            AnalyticsFile.TweetScreenshots.Trigger();
        }

        public void DisplayTooltip(string text){
            if (string.IsNullOrEmpty(text)){
                toolTip.Hide(this);
            }
            else{
                Point position = PointToClient(Cursor.Position);
                position.Offset(20, 10);
                toolTip.Show(text, this, position);
            }
        }
    }
}