using CefSharp;
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Resources;

namespace TweetDuck.Core.Notification{
    partial class FormNotificationMain : FormNotificationBase{
        private const string NotificationScriptFile = "notification.js";
        private const int TimerBarHeight = 4;

        private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
        private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);

        private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
        private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
        
        private readonly PluginManager plugins;

        protected int timeLeft, totalTime;
        protected bool pausedDuringNotification;
        
        private readonly NativeMethods.HookProc mouseHookDelegate;
        private IntPtr mouseHook;
        private bool blockXButtonUp;

        private bool? prevDisplayTimer;
        private int? prevFontSize;

        public bool RequiresResize{
            get{
                return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
            }

            set{
                if (value){
                    prevDisplayTimer = null;
                    prevFontSize = null;
                }
                else{
                    prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
                    prevFontSize = TweetNotification.FontSizeLevel;
                }
            }
        }

        private int BaseClientWidth{
            get{
                switch(Program.UserConfig.NotificationSize){
                    default:
                        return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));

                    case TweetNotification.Size.Custom:
                        return Program.UserConfig.CustomNotificationSize.Width;
                }
            }
        }

        private int BaseClientHeight{
            get{
                switch(Program.UserConfig.NotificationSize){
                    default:
                        return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));

                    case TweetNotification.Size.Custom:
                        return Program.UserConfig.CustomNotificationSize.Height;
                }
            }
        }

        public Size BrowserSize{
            get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
        }

        public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
            InitializeComponent();

            this.plugins = pluginManager;

            browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
            browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);

            browser.LoadingStateChanged += Browser_LoadingStateChanged;
            browser.FrameLoadEnd += Browser_FrameLoadEnd;

            mouseHookDelegate = MouseHookProc;
            Disposed += (sender, args) => StopMouseHook(true);
        }

        // mouse wheel hook

        private void StartMouseHook(){
            if (mouseHook == IntPtr.Zero){
                mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
            }
        }

        private void StopMouseHook(bool force){
            if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){
                NativeMethods.UnhookWindowsHookEx(mouseHook);
                mouseHook = IntPtr.Zero;
                blockXButtonUp = false;
            }
        }

        private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
            if (nCode == 0){
                int eventType = wParam.ToInt32();

                if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
                    browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None);
                    return NativeMethods.HOOK_HANDLED;
                }
                else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
                    int extraButton = NativeMethods.GetMouseHookData(lParam);

                    if (extraButton == 2){ // forward button
                        this.InvokeAsyncSafe(FinishCurrentNotification);
                    }
                    else if (extraButton == 1){ // back button
                        this.InvokeAsyncSafe(Close);
                    }
                    
                    blockXButtonUp = true;
                    return NativeMethods.HOOK_HANDLED;
                }
                else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
                    blockXButtonUp = false;

                    if (!Visible){
                        StopMouseHook(false);
                    }

                    return NativeMethods.HOOK_HANDLED;
                }
            }

            return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
        }

        // event handlers

        private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
            if (e.CloseReason == CloseReason.UserClosing){
                HideNotification(true);
                e.Cancel = true;
            }
        }

        private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
            if (!e.IsLoading && browser.Address != "about:blank"){
                this.InvokeSafe(() => {
                    Visible = true; // ensures repaint before moving the window to a visible location
                    timerDisplayDelay.Start();
                });
            }
        }

        private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
            if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
                e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification));
                ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);

                if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
                    ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
                    ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
                    plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
                }
            }
        }

        private void timerDisplayDelay_Tick(object sender, EventArgs e){
            OnNotificationReady();
            timerDisplayDelay.Stop();
        }

        private void timerHideProgress_Tick(object sender, EventArgs e){
            if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;

            timeLeft -= timerProgress.Interval;

            int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime);
            progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));

            if (timeLeft <= 0){
                FinishCurrentNotification();
            }
        }

        // notification methods

        public virtual void ShowNotification(TweetNotification notification){
            LoadTweet(notification);
        }

        public void ShowNotificationForSettings(bool reset){
            if (reset){
                LoadTweet(TweetNotification.ExampleTweet);
            }
            else{
                PrepareAndDisplayWindow();
            }

            UpdateTitle();
        }

        public override void HideNotification(bool loadBlank){
            base.HideNotification(loadBlank);
            
            progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
            timerProgress.Stop();
            totalTime = 0;

            StopMouseHook(false);
        }

        public override void FinishCurrentNotification(){
            timerProgress.Stop();
        }

        public override void PauseNotification(){
            if (!IsPaused){
                pausedDuringNotification = IsNotificationVisible;
                timerProgress.Stop();
                StopMouseHook(true);
            }

            base.PauseNotification();
        }

        public override void ResumeNotification(){
            bool wasPaused = IsPaused;
            base.ResumeNotification();

            if (wasPaused && !IsPaused && pausedDuringNotification){
                OnNotificationReady();
            }
        }

        protected override string GetTweetHTML(TweetNotification tweet){
            string html = base.GetTweetHTML(tweet);

            foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
                html = injection.Inject(html);
            }

            return html;
        }

        protected override void LoadTweet(TweetNotification tweet){
            timerProgress.Stop();
            totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
            progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;

            base.LoadTweet(tweet);
        }

        protected override void SetNotificationSize(int width, int height){
            if (Program.UserConfig.DisplayNotificationTimer){
                ClientSize = new Size(width, height+TimerBarHeight);
                progressBarTimer.Visible = true;
            }
            else{
                ClientSize = new Size(width, height);
                progressBarTimer.Visible = false;
            }

            browser.ClientSize = new Size(width, height);
        }
        
        private void PrepareAndDisplayWindow(){
            if (RequiresResize){
                RequiresResize = false;
                SetNotificationSize(BaseClientWidth, BaseClientHeight);
            }
            
            MoveToVisibleLocation();
            StartMouseHook();
        }

        protected override void OnNotificationReady(){
            PrepareAndDisplayWindow();
            timerProgress.Start();
        }
    }
}