using SDL2;

namespace Nerfed.Runtime;

public class GamePad
{
    private const int maxGamePads = 4;
    private static readonly GamePad[] gamePads = new GamePad[maxGamePads];
    private static readonly GamePad dummy = new GamePad();

    public IntPtr Handle { get; private set; }
    public bool IsConnected => Handle != IntPtr.Zero;
    public int JoystickInstanceId { get; private set; } = -1;

    private readonly ButtonState[] buttonStates;
    private readonly ButtonState[] lastButtonStates;

    static GamePad()
    {
        for (int i = 0; i < maxGamePads; i++)
        {
            gamePads[i] = new GamePad();
        }
    }

    private GamePad()
    {
        int gamePadButtonCount = Enum.GetValues<GamePadButton>().Length;
        buttonStates = new ButtonState[gamePadButtonCount];
        lastButtonStates = new ButtonState[gamePadButtonCount];
    }

    public bool IsButtonDown(GamePadButton button)
    {
        return buttonStates[(int)button] == ButtonState.Pressed;
    }

    private void Register(IntPtr handle)
    {
        Handle = handle;
        IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(handle);
        JoystickInstanceId = SDL.SDL_JoystickInstanceID(joystickHandle);
        Log.Info("Controller connected");
    }

    private void UnRegister()
    {
        Handle = IntPtr.Zero;
        JoystickInstanceId = -1;
        Log.Info("Controller disconnected");
    }

    public static GamePad Get(int slot)
    {
        return slot >= 0 && slot < maxGamePads ? gamePads[slot] : dummy;
    }

    internal static void Update()
    {
        for (int i = 0; i < maxGamePads; i++)
        {
            GamePad gamePad = gamePads[i];
            if (gamePad.IsConnected)
            {
                Array.Copy(gamePad.buttonStates, gamePad.lastButtonStates, gamePad.buttonStates.Length);
            }
        }
    }

    internal static void ProcessEvent(ref SDL.SDL_Event ev)
    {
        switch (ev.type)
        {
            case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED:
                ProcessDeviceAdded(ref ev.cdevice);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED:
                ProcessDeviceRemoved(ref ev.cdevice);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN:
                ProcessButtonDown(ref ev.cbutton);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP:
                ProcessButtonUp(ref ev.cbutton);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERAXISMOTION:
                ProcessAxisMotion(ref ev.caxis);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADDOWN:
                ProcessTouchPadDown(ref ev.ctouchpad);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADUP:
                ProcessTouchPadUp(ref ev.ctouchpad);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADMOTION:
                ProcessTouchPadMotion(ref ev.ctouchpad);
                break;
            case SDL.SDL_EventType.SDL_CONTROLLERSENSORUPDATE:
                ProcessSensorUpdate(ref ev.sensor);
                break;
        }
    }

    private static void ProcessDeviceAdded(ref SDL.SDL_ControllerDeviceEvent ev)
    {
        int index = ev.which;
        if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
        {
            int slot = -1;
            for (int i = 0; i < maxGamePads; i++)
            {
                if (!gamePads[i].IsConnected)
                {
                    slot = i;
                    break;
                }
            }

            if (slot == -1)
            {
                Log.Warning("Too many gamepads connected");
                return;
            }

            IntPtr handle = SDL.SDL_GameControllerOpen(index);
            if (handle == IntPtr.Zero)
            {
                Log.Error($"Failed to open gamepad: {SDL.SDL_GetError()}");
                return;
            }

            gamePads[slot].Register(handle);
        }
    }

    private static GamePad GetByJoystickId(int joystickId)
    {
        for (int slot = 0; slot < maxGamePads; slot++)
        {
            GamePad gamePad = gamePads[slot];
            if (gamePad.IsConnected && gamePad.JoystickInstanceId == joystickId)
            {
                return gamePad;
            }
        }

        return null;
    }

    private static void ProcessDeviceRemoved(ref SDL.SDL_ControllerDeviceEvent ev)
    {
        GamePad gamePad = GetByJoystickId(ev.which);
        gamePad?.UnRegister();
    }

    private static void ProcessButtonDown(ref SDL.SDL_ControllerButtonEvent ev)
    {
        if (TryConvertButton(ev.button, out GamePadButton button))
        {
            GamePad gamePad = GetByJoystickId(ev.which);
            gamePad.buttonStates[(int)button] = ButtonState.Pressed;
        }
    }

    private static void ProcessButtonUp(ref SDL.SDL_ControllerButtonEvent ev)
    {
        if (TryConvertButton(ev.button, out GamePadButton button))
        {
            GamePad gamePad = GetByJoystickId(ev.which);
            gamePad.buttonStates[(int)button] = ButtonState.Released;
        }
    }

    private static void ProcessAxisMotion(ref SDL.SDL_ControllerAxisEvent ev)
    {

    }

    private static void ProcessTouchPadDown(ref SDL.SDL_ControllerTouchpadEvent ev)
    {

    }

    private static void ProcessTouchPadUp(ref SDL.SDL_ControllerTouchpadEvent ev)
    {

    }

    private static void ProcessTouchPadMotion(ref SDL.SDL_ControllerTouchpadEvent ev)
    {

    }

    private static void ProcessSensorUpdate(ref SDL.SDL_SensorEvent ev)
    {

    }

    private static bool TryConvertButton(byte rawButton, out GamePadButton button)
    {
        GamePadButton? result = rawButton switch
        {
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A => GamePadButton.A,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B => GamePadButton.B,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X => GamePadButton.X,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y => GamePadButton.Y,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK => GamePadButton.Back,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START => GamePadButton.Start,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK => GamePadButton.LeftStick,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK => GamePadButton.RightStick,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER => GamePadButton.LeftShoulder,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER => GamePadButton.RightShoulder,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP => GamePadButton.DPadUp,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN => GamePadButton.DPadDown,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT => GamePadButton.DPadLeft,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT => GamePadButton.DPadRight,
            (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD => GamePadButton.BigButton,
            _ => null
        };

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