using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDck.Configuration;
using TweetDck.Core.Handling;
using TweetDck.Resources;
using TweetDck.Core.Utils;
using TweetDck.Plugins;

namespace TweetDck.Core{
    sealed partial class FormNotification : Form{
        private const string NotificationScriptFile = "notification.js";

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

        public Func<bool> CanMoveWindow = () => true;

        private readonly Form owner;
        private readonly PluginManager plugins;
        private readonly ChromiumWebBrowser browser;

        private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
        private readonly bool autoHide;
        private int timeLeft, totalTime;
        
        private readonly NativeMethods.HookProc mouseHookDelegate;
        private IntPtr mouseHook;

        private bool? prevDisplayTimer;
        private int? prevFontSize;

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

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

        private readonly string notificationJS;
        private readonly string pluginJS;

        protected override bool ShowWithoutActivation{
            get{
                return true;
            }
        }

        public bool FreezeTimer { get; set; }
        public bool ContextMenuOpen { get; set; }
        public string CurrentUrl { get; private set; }

        public EventHandler Initialized;
        private bool isInitialized;

        private static int BaseClientWidth{
            get{
                int level = TweetNotification.FontSizeLevel;
                return level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
            }
        }

        private static int BaseClientHeight{
            get{
                int level = TweetNotification.FontSizeLevel;
                return level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
            }
        }

        public FormNotification(FormBrowser owner, PluginManager pluginManager, bool autoHide){
            InitializeComponent();

            Text = Program.BrandName;

            this.owner = owner;
            this.plugins = pluginManager;
            this.autoHide = autoHide;

            owner.FormClosed += (sender, args) => Close();

            notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
            pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);

            browser = new ChromiumWebBrowser("about:blank"){
                MenuHandler = new ContextMenuNotification(this, autoHide),
                LifeSpanHandler = new LifeSpanHandler()
            };

            browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
            browser.FrameLoadEnd += Browser_FrameLoadEnd;
            browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
            browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);

            panelBrowser.Controls.Add(browser);

            if (autoHide){
                Program.UserConfig.MuteToggled += Config_MuteToggled;
                Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
            }

            mouseHookDelegate = MouseHookProc;

            Disposed += FormNotification_Disposed;
        }

        protected override void WndProc(ref Message m){
            if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
                return;
            }

            base.WndProc(ref m);
        }

        // mouse wheel hook

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

        private void StopMouseHook(){
            if (mouseHook != IntPtr.Zero){
                NativeMethods.UnhookWindowsHookEx(mouseHook);
                mouseHook = IntPtr.Zero;
            }
        }

        private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
            if (!ContainsFocus && wParam.ToInt32() == NativeMethods.WH_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
                // fuck it, Activate() doesn't work with this
                Point prevPos = Cursor.Position;
                Cursor.Position = PointToScreen(new Point(-1, -1));
                NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
                Cursor.Position = prevPos;
            }

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

        // event handlers

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

            timeLeft -= timerProgress.Interval;

            int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime);
            progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));

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

        private void Config_MuteToggled(object sender, EventArgs e){
            if (Program.UserConfig.MuteNotifications){
                HideNotification(true);
            }
            else if (tweetQueue.Count > 0){
                LoadNextNotification();
            }
        }

        private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
            if (e.IsBrowserInitialized && Initialized != null){
                Initialized(this, new EventArgs());
            }
        }

        private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
            if (!e.Frame.IsMain)return;

            if (!isInitialized && !Program.UserConfig.NotificationLegacyLoad){
                isInitialized = true;

                if (Initialized != null){
                    Initialized(this, new EventArgs());
                }
            }
            else if (notificationJS != null && browser.Address != "about:blank"){
                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 FormNotification_FormClosing(object sender, FormClosingEventArgs e){
            if (e.CloseReason == CloseReason.UserClosing){
                HideNotification(false);
                tweetQueue.Clear();
                e.Cancel = true;
            }
        }

        private void FormNotification_Disposed(object sender, EventArgs e){
            browser.Dispose();
            StopMouseHook();
        }

        // notification methods

        public void ShowNotification(TweetNotification notification){
            if (Program.UserConfig.MuteNotifications){
                tweetQueue.Enqueue(notification);
            }
            else{
                tweetQueue.Enqueue(notification);
                UpdateTitle();

                if (!timerProgress.Enabled){
                    LoadNextNotification();
                }
            }
        }

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

        public void HideNotification(bool loadBlank){
            if (loadBlank || Program.UserConfig.NotificationLegacyLoad){
                browser.LoadHtml("", "about:blank");
            }

            Location = new Point(-32000, -32000);
            progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
            timerProgress.Stop();

            StopMouseHook();
        }

        public void OnNotificationReady(){
            UpdateTitle();
            MoveToVisibleLocation();
            timerProgress.Start();
        }

        public void FinishCurrentTweet(){
            if (tweetQueue.Count > 0){
                LoadNextNotification();
            }
            else if (autoHide){
                HideNotification(true);
            }
            else{
                timerProgress.Stop();
            }
        }

        private void LoadNextNotification(){
            LoadTweet(tweetQueue.Dequeue());
        }

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

            browser.LoadHtml(tweet.GenerateHtml(), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks);

            if (Program.UserConfig.NotificationLegacyLoad){
                OnNotificationReady();
            }
        }

        private void MoveToVisibleLocation(){
            UserConfig config = Program.UserConfig;

            if (RequiresResize){
                RequiresResize = false;

                if (config.DisplayNotificationTimer){
                    ClientSize = new Size(BaseClientWidth, BaseClientHeight+4);
                    progressBarTimer.Visible = true;
                }
                else{
                    ClientSize = new Size(BaseClientWidth, BaseClientHeight);
                    progressBarTimer.Visible = false;
                }

                panelBrowser.Height = BaseClientHeight;
            }
            
            Screen screen = Screen.FromControl(owner);

            if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
                screen = Screen.AllScreens[config.NotificationDisplay-1];
            }
            
            bool needsReactivating = Location.X == -32000;
            int edgeDist = config.NotificationEdgeDistance;

            switch(config.NotificationPosition){
                case TweetNotification.Position.TopLeft:
                    Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
                    break;

                case TweetNotification.Position.TopRight:
                    Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
                    break;

                case TweetNotification.Position.BottomLeft:
                    Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
                    break;

                case TweetNotification.Position.BottomRight:
                    Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
                    break;

                case TweetNotification.Position.Custom:
                    if (!config.IsCustomNotificationPositionSet){
                        config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
                        config.Save();
                    }

                    Location = config.CustomNotificationPosition;
                    break;
            }

            if (needsReactivating){
                NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
            }

            StartMouseHook();
        }

        private void UpdateTitle(){
            Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName;
        }

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