using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace ZB.MOM.WW.MxGateway.Worker.Sta;

/// <summary>Pumps Windows messages on the STA thread to allow MXAccess COM events to deliver.</summary>
public sealed class StaMessagePump
{
    private const uint Infinite = 0xFFFFFFFF;
    private const uint PmRemove = 0x0001;

    /// <summary>Waits for a command wake event or Windows messages, pumping any pending messages.</summary>
    /// <param name="commandWakeEvent">Event to signal when work is available.</param>
    /// <param name="timeout">Maximum time to wait; InfiniteTimeSpan waits indefinitely.</param>
    public void WaitForWorkOrMessages(WaitHandle commandWakeEvent, TimeSpan timeout)
    {
        if (commandWakeEvent is null)
        {
            throw new ArgumentNullException(nameof(commandWakeEvent));
        }

        uint timeoutMilliseconds = ToTimeoutMilliseconds(timeout);

        SafeWaitHandle safeHandle = commandWakeEvent.SafeWaitHandle;
        IntPtr[] handles = [safeHandle.DangerousGetHandle()];
        uint result = StaNativeMethods.MsgWaitForMultipleObjectsEx(
            (uint)handles.Length,
            handles,
            timeoutMilliseconds,
            StaNativeMethods.QsAllInput,
            StaNativeMethods.MwmoInputAvailable);

        if (result == StaNativeMethods.MsgWaitFailed)
        {
            throw new InvalidOperationException(
                "The worker STA message pump failed while waiting for command work or Windows messages.");
        }
    }

    /// <summary>Pumps and dispatches all pending Windows messages, returning the count processed.</summary>
    /// <returns>The number of messages pumped and dispatched.</returns>
    public int PumpPendingMessages()
    {
        int pumpedMessages = 0;

        while (PeekMessage(out NativeMessage message, IntPtr.Zero, 0, 0, PmRemove))
        {
            TranslateMessage(ref message);
            DispatchMessage(ref message);
            pumpedMessages++;
        }

        return pumpedMessages;
    }

    private static uint ToTimeoutMilliseconds(TimeSpan timeout)
    {
        if (timeout == Timeout.InfiniteTimeSpan)
        {
            return Infinite;
        }

        if (timeout <= TimeSpan.Zero)
        {
            return 0;
        }

        return timeout.TotalMilliseconds >= uint.MaxValue
            ? uint.MaxValue - 1
            : (uint)Math.Ceiling(timeout.TotalMilliseconds);
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool PeekMessage(
        out NativeMessage message,
        IntPtr windowHandle,
        uint messageFilterMin,
        uint messageFilterMax,
        uint removeMessage);

    [DllImport("user32.dll")]
    private static extern bool TranslateMessage(ref NativeMessage message);

    [DllImport("user32.dll")]
    private static extern IntPtr DispatchMessage(ref NativeMessage message);

    [StructLayout(LayoutKind.Sequential)]
    private struct NativeMessage
    {
        public IntPtr WindowHandle;
        public uint Message;
        public UIntPtr WParam;
        public IntPtr LParam;
        public uint Time;
        public NativePoint Point;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct NativePoint
    {
        public int X;
        public int Y;
    }
}
