using SDL2;
using System.Numerics;

namespace Nerfed.Runtime;

internal static class Mouse
{
    public static Vector2 Position { get; private set; }

    private static readonly ButtonState[] buttonStates;
    private static readonly ButtonState[] lastButtonStates;
    private static int wheelX;
    private static int wheelY;

    static Mouse()
    {
        int numButtons = Enum.GetValues<MouseButton>().Length;
        buttonStates = new ButtonState[numButtons];
        lastButtonStates = new ButtonState[numButtons];
    }

    /// /// <summary>
    /// True if the button is pressed or continued to be held this frame.
    /// </summary>
    public static bool IsButtonDown(MouseButton button)
    {
        return buttonStates[(int)button] == ButtonState.Pressed;
    }

    /// <summary>
    /// True if the button is pressed this frame.
    /// </summary>
    public static bool IsButtonPressed(MouseButton button)
    {
        return buttonStates[(int)button] == ButtonState.Pressed && lastButtonStates[(int)button] == ButtonState.Released;
    }

    /// <summary>
    /// True if the button is released or continued to be released this frame.
    /// </summary>
    /// <param name="button"></param>
    /// <returns></returns>
    public static bool IsButtonUp(MouseButton button)
    {
        return buttonStates[(int)button] == ButtonState.Released;
    }

    /// <summary>
    /// True if the button is released this frame.
    /// </summary>
    public static bool IsButtonReleased(MouseButton button)
    {
        return buttonStates[(int)button] == ButtonState.Released && lastButtonStates[(int)button] == ButtonState.Pressed;
    }

    public static int GetWheel()
    {
        return wheelY;
    }

    public static int GetWheelHorizontal()
    {
        return wheelX;
    }

    internal static void Update()
    {
        wheelX = 0;
        wheelY = 0;
        Array.Copy(buttonStates, lastButtonStates, buttonStates.Length);
    }

    internal static void ProcessEvent(ref SDL.SDL_Event ev)
    {
        switch (ev.type)
        {
            case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
                ProcessButtonDownEvent(ref ev.button);
                break;
            case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
                ProcessButtonUpEvent(ref ev.button);
                break;
            case SDL.SDL_EventType.SDL_MOUSEWHEEL:
                ProcessWheelEvent(ref ev.wheel);
                break;
            case SDL.SDL_EventType.SDL_MOUSEMOTION:
                ProcessMotionEvent(ref ev.motion);
                break;
        }
    }

    private static void ProcessButtonDownEvent(ref SDL.SDL_MouseButtonEvent ev)
    {
        if (TryConvertButton(ev.button, out MouseButton button))
        {
            buttonStates[(int)button] = ButtonState.Pressed;
        }
    }

    private static void ProcessButtonUpEvent(ref SDL.SDL_MouseButtonEvent ev)
    {
        if (TryConvertButton(ev.button, out MouseButton button))
        {
            buttonStates[(int)button] = ButtonState.Released;
        }
    }

    private static void ProcessWheelEvent(ref SDL.SDL_MouseWheelEvent ev)
    {
        wheelX = ev.x;
        wheelY = ev.y;
    }

    private static void ProcessMotionEvent(ref SDL.SDL_MouseMotionEvent ev)
    {
        Position = new Vector2(ev.x, ev.y);
    }

    private static bool TryConvertButton(byte rawButton, out MouseButton button)
    {
        MouseButton? result = rawButton switch
        {
            (byte)SDL.SDL_BUTTON_LEFT => MouseButton.Left,
            (byte)SDL.SDL_BUTTON_MIDDLE => MouseButton.Middle,
            (byte)SDL.SDL_BUTTON_RIGHT => MouseButton.Right,
            (byte)SDL.SDL_BUTTON_X1 => MouseButton.XButton1,
            (byte)SDL.SDL_BUTTON_X2 => MouseButton.XButton2,
            _ => null
        };

        button = result.GetValueOrDefault();
        return result.HasValue;
    }
}
