Skip to content

Input Events

Input Event State

#pragma once

#include <SDL2/SDL.h>

#include "../math/math.h"

enum class InputEventType {
    MOUSE,
    KEYBOARD,
    JOYSTICK,
};

struct InputEvent {
    InputEventType type;
    Uint32 sdlType;
    // Mouse
    Vector2 mouseMotion;
    bool mousePressed = false;
    Uint8 mouseButton;
    // Joystick
    Uint8 controllerId;
    Sint16 buttonValue;
    bool buttonPressed = false;
    Uint8 hat;
    Uint8 hatValue;
    // Keyboard
    bool keyPressed = false;
    SDL_Scancode keyScancode;
};

class InputEventState {
  public:
    InputEvent ProcessSDLEvent(SDL_Event &event) {
        InputEvent inputEvent;
        inputEvent.sdlType = event.type;
        switch(event.type) {
        // Mouse
        case SDL_MOUSEMOTION:
            inputEvent.type = InputEventType::MOUSE;
            inputEvent.mouseMotion = Vector2(event.motion.x, event.motion.y);
            break;
        case SDL_MOUSEWHEEL:
            inputEvent.type = InputEventType::MOUSE;
            break;
        case SDL_MOUSEBUTTONDOWN:
            inputEvent.type = InputEventType::MOUSE;
            inputEvent.mousePressed = true;
            inputEvent.mouseButton = event.button.button;
            break;
        case SDL_MOUSEBUTTONUP:
            inputEvent.type = InputEventType::MOUSE;
            inputEvent.mousePressed = false;
            inputEvent.mouseButton = event.button.button;
            break;
        // Joystick
        case SDL_JOYBUTTONDOWN:
        case SDL_JOYBUTTONUP:
            inputEvent.type = InputEventType::JOYSTICK;
            inputEvent.controllerId = event.jbutton.which;
            inputEvent.buttonValue = event.jbutton.button;
            inputEvent.buttonPressed = event.jbutton.state == SDL_PRESSED;
            break;
        case SDL_JOYHATMOTION:
            inputEvent.type = InputEventType::JOYSTICK;
            inputEvent.controllerId = event.jhat.which;
            inputEvent.hat = event.jhat.hat;
            inputEvent.hatValue = event.jhat.value;
            break;
        // Keyboard
        case SDL_KEYDOWN:
        case SDL_KEYUP:
            if (!event.key.repeat) {
                inputEvent.type = InputEventType::KEYBOARD;
                inputEvent.keyPressed = event.key.state == SDL_PRESSED;
                inputEvent.keyScancode = event.key.keysym.scancode;
            }
            break;
        default:
            break;
        }
        return inputEvent;
    }
};

Keyboard

#pragma once

#include <string>
#include <map>

#include <SDL2/SDL.h>

const std::string KEYBOARD_KEY_UP{"up"};
const std::string KEYBOARD_KEY_DOWN{"down"};
const std::string KEYBOARD_KEY_LEFT{"left"};
const std::string KEYBOARD_KEY_RIGHT{"right"};
const std::string KEYBOARD_KEY_SPACE{"space"};
const std::string KEYBOARD_KEY_ESC{"esc"};
const std::string KEYBOARD_KEY_RETURN{"return"};

static std::map<std::string, SDL_Scancode> KEYBOARD_SCANCODE_TO_VALUE_MAPPING = {
    {"0", SDL_SCANCODE_0},
    {"1", SDL_SCANCODE_1},
    {"2", SDL_SCANCODE_2},
    {"3", SDL_SCANCODE_3},
    {"4", SDL_SCANCODE_4},
    {"5", SDL_SCANCODE_5},
    {"6", SDL_SCANCODE_6},
    {"7", SDL_SCANCODE_7},
    {"8", SDL_SCANCODE_8},
    {"9", SDL_SCANCODE_9},

    {"a", SDL_SCANCODE_A},
    {"b", SDL_SCANCODE_B},
    {"c", SDL_SCANCODE_C},
    {"d", SDL_SCANCODE_D},
    {"e", SDL_SCANCODE_E},
    {"f", SDL_SCANCODE_F},
    {"g", SDL_SCANCODE_G},
    {"h", SDL_SCANCODE_H},
    {"i", SDL_SCANCODE_I},
    {"j", SDL_SCANCODE_J},
    {"k", SDL_SCANCODE_K},
    {"l", SDL_SCANCODE_L},
    {"m", SDL_SCANCODE_M},
    {"n", SDL_SCANCODE_N},
    {"o", SDL_SCANCODE_O},
    {"p", SDL_SCANCODE_P},
    {"q", SDL_SCANCODE_Q},
    {"r", SDL_SCANCODE_R},
    {"s", SDL_SCANCODE_S},
    {"t", SDL_SCANCODE_T},
    {"u", SDL_SCANCODE_U},
    {"v", SDL_SCANCODE_V},
    {"w", SDL_SCANCODE_W},
    {"x", SDL_SCANCODE_X},
    {"y", SDL_SCANCODE_Y},
    {"z", SDL_SCANCODE_Z},

    {KEYBOARD_KEY_LEFT, SDL_SCANCODE_LEFT},
    {KEYBOARD_KEY_RIGHT, SDL_SCANCODE_RIGHT},
    {KEYBOARD_KEY_UP, SDL_SCANCODE_UP},
    {KEYBOARD_KEY_DOWN, SDL_SCANCODE_DOWN},

    {KEYBOARD_KEY_RETURN, SDL_SCANCODE_RETURN},
    {KEYBOARD_KEY_SPACE, SDL_SCANCODE_SPACE},
    {KEYBOARD_KEY_ESC, SDL_SCANCODE_ESCAPE},

    {"f1", SDL_SCANCODE_F1},
    {"f2", SDL_SCANCODE_F2},
    {"f3", SDL_SCANCODE_F3},
    {"f4", SDL_SCANCODE_F4},
    {"f5", SDL_SCANCODE_F5},
    {"f6", SDL_SCANCODE_F6},
    {"f7", SDL_SCANCODE_F7},
    {"f8", SDL_SCANCODE_F8},
    {"f9", SDL_SCANCODE_F9},
    {"f10", SDL_SCANCODE_F10},
    {"f11", SDL_SCANCODE_F11},
    {"f12", SDL_SCANCODE_F12}
};

class KeyboardAction {
  public:
    bool isActionPressed = false;
    bool isActionJustPressed = false;
    bool isActionJustReleased = false;
    std::vector<SDL_Scancode> values;
    SDL_Scancode lastScancodePressed = SDL_SCANCODE_UNKNOWN;

    void AddValue(const std::string &actionValue) {
        if (KEYBOARD_SCANCODE_TO_VALUE_MAPPING.count(actionValue) > 0) {
            SDL_Scancode valueScancode = KEYBOARD_SCANCODE_TO_VALUE_MAPPING[actionValue];
            values.emplace_back(valueScancode);
        }
    }

    void ProcessInputs(const Uint8* keyboardState) {
        for(SDL_Scancode scancodeValue : values) {
            bool isKeyboardValuePressed = keyboardState[scancodeValue];
            if (isKeyboardValuePressed && !isActionPressed) {
                isActionPressed = true;
                isActionJustPressed = true;
                lastScancodePressed = scancodeValue;
                break;
            } else if (!isKeyboardValuePressed && !isActionJustReleased && isActionPressed && scancodeValue == lastScancodePressed) {
                isActionPressed = false;
                isActionJustPressed = false;
                isActionJustReleased = true;
            }
        }
    }
};

Mouse

#pragma once

#include <string>
#include <vector>
#include <algorithm>

#include <SDL2/SDL.h>

#include "../math/math.h"

#include "input_event_state.h"

const std::string MOUSE_BUTTON_LEFT{"mb_left"};
const std::string MOUSE_BUTTON_RIGHT{"mb_right"};
const std::string MOUSE_BUTTON_MIDDLE{"mb_middle"};

class MouseInput {
  public:
    bool isMouseLeftButtonPressed = false;
    bool isMouseLeftButtonJustPressed = false;
    bool isMouseLeftButtonJustReleased = false;
    bool isMouseRightButtonPressed = false;
    bool isMouseRightButtonJustPressed = false;
    bool isMouseRightButtonJustReleased = false;

    static MouseInput* GetInstance();
    void ClearInputFlags();
    Vector2 GetMousePosition() const;
    void ProcessSDLEvent(InputEvent &inputEvent);

  private:
    static MouseInput *instance;
    Vector2 mousePosition;

    MouseInput() {}
};

class MouseAction {
  public:
    MouseAction() : mouseInput(MouseInput::GetInstance()) {}

    static bool IsMouseValue(const std::string &actionValue) {
        if (actionValue == MOUSE_BUTTON_LEFT || actionValue == MOUSE_BUTTON_RIGHT) {
            return true;
        }
        return false;
    }

    void AddValue(const std::string &actionValue) {
        if (!HasValue(actionValue)) {
            mouseValues.emplace_back(actionValue);
        }
    }

    bool HasValue(const std::string &actionValue) {
        return std::find(mouseValues.begin(), mouseValues.end(), actionValue) != mouseValues.end();
    }

    bool IsActionPressed() {
        for (const std::string &value : mouseValues) {
            if ((value == MOUSE_BUTTON_LEFT && mouseInput->isMouseLeftButtonPressed)
                    || (value == MOUSE_BUTTON_RIGHT && mouseInput->isMouseRightButtonPressed)) {
                return true;
            }
        }
        return false;
    }

    bool IsActionJustPressed() {
        for (const std::string &value : mouseValues) {
            if ((value == MOUSE_BUTTON_LEFT && mouseInput->isMouseLeftButtonJustPressed)
                    || (value == MOUSE_BUTTON_RIGHT && mouseInput->isMouseRightButtonJustPressed)) {
                return true;
            }
        }
        return false;
    }

    bool IsActionJustReleased() {
        for (const std::string &value : mouseValues) {
            if ((value == MOUSE_BUTTON_LEFT && mouseInput->isMouseLeftButtonJustReleased)
                    || (value == MOUSE_BUTTON_RIGHT && mouseInput->isMouseRightButtonJustReleased)) {
                return true;
            }
        }
        return false;
    }

  private:
    std::vector<std::string> mouseValues;
    MouseInput *mouseInput = nullptr;
};
#include "mouse_input.h"

MouseInput* MouseInput::GetInstance() {
    static MouseInput *instance = new MouseInput();
    return instance;
}

void MouseInput::ClearInputFlags() {
    isMouseLeftButtonJustPressed = false;
    isMouseRightButtonJustPressed = false;
    isMouseLeftButtonJustReleased = false;
    isMouseRightButtonJustReleased = false;
}

Vector2 MouseInput::GetMousePosition() const {
    return mousePosition;
}

void MouseInput::ProcessSDLEvent(InputEvent &inputEvent) {
    switch(inputEvent.sdlType) {
    // Mouse
    case SDL_MOUSEMOTION:
        mousePosition = inputEvent.mouseMotion;
        break;
    case SDL_MOUSEWHEEL:
        break;
    case SDL_MOUSEBUTTONDOWN:
        if(inputEvent.mouseButton == SDL_BUTTON_LEFT) {
            isMouseLeftButtonPressed = true;
            isMouseLeftButtonJustPressed = true;
        } else if(inputEvent.mouseButton == SDL_BUTTON_RIGHT) {
            isMouseRightButtonPressed = true;
            isMouseRightButtonJustPressed = true;
        }
        break;
    case SDL_MOUSEBUTTONUP:
        if(inputEvent.mouseButton == SDL_BUTTON_LEFT) {
            isMouseLeftButtonPressed = false;
            isMouseLeftButtonJustPressed = false;
            isMouseLeftButtonJustReleased = true;
        } else if(inputEvent.mouseButton == SDL_BUTTON_RIGHT) {
            isMouseRightButtonPressed = false;
            isMouseRightButtonJustPressed = false;
            isMouseRightButtonJustReleased = true;
        }
        break;
    default:
        break;
    }
}

Joystick

#pragma once

#include <string>
#include <vector>
#include <unordered_map>
#include <algorithm>

#include <SDL2/SDL.h>

#include "input_event_state.h"
#include "../utils/logger.h"

enum class JoystickButtonType : int {
    INVALID = -1,
    BUTTON_A = 0,
    BUTTON_B = 1,
    BUTTON_X = 2,
    BUTTON_Y = 3,
    BUTTON_BACK = 6,
    BUTTON_START = 7,
    BUTTON_LEFT_SHOULDER = 4,
    BUTTON_RIGHT_SHOULDER = 5,
    BUTTON_LEFT_AXIS = 8,
    BUTTON_RIGHT_AXIS = 9,
};

enum class JoystickDeadZone : int {
    AXIS = 10000,
    TRIGGER = 8000,
};

enum class JoystickAxisMotion : Uint8 {
    LEFT_TRIGGER = 2,
    RIGHT_TRIGGER = 5,
    LEFT_HORIZONTAL_AXIS = 0,
    LEFT_VERTICAL_AXIS = 1,
    RIGHT_HORIZONTAL_AXIS = 3,
    RIGHT_VERTICAL_AXIS = 4,
};

const std::string JOYSTICK_BUTTON_A{"joystick_button_a"};
const std::string JOYSTICK_BUTTON_B{"joystick_button_b"};
const std::string JOYSTICK_BUTTON_X{"joystick_button_x"};
const std::string JOYSTICK_BUTTON_Y{"joystick_button_y"};
const std::string JOYSTICK_KEYPAD_UP{"joystick_keypad_up"};
const std::string JOYSTICK_KEYPAD_DOWN{"joystick_keypad_down"};
const std::string JOYSTICK_KEYPAD_RIGHT{"joystick_keypad_right"};
const std::string JOYSTICK_KEYPAD_LEFT{"joystick_keypad_left"};
const std::string JOYSTICK_START{"joystick_button_start"};
const std::string JOYSTICK_BACK{"joystick_button_back"};
const std::string JOYSTICK_LEFT_SHOULDER{"joystick_left_shoulder"};
const std::string JOYSTICK_RIGHT_SHOULDER{"joystick_right_shoulder"};
const std::string JOYSTICK_LEFT_TRIGGER{"joystick_left_trigger"};
const std::string JOYSTICK_RIGHT_TRIGGER{"joystick_right_trigger"};
const std::string JOYSTICK_LEFT_ANALOG{"joystick_left_analog"};
const std::string JOYSTICK_RIGHT_ANALOG{"joystick_right_analog"};
const std::string JOYSTICK_LEFT_ANALOG_UP{"joystick_left_analog_up"};
const std::string JOYSTICK_LEFT_ANALOG_DOWN{"joystick_left_analog_down"};
const std::string JOYSTICK_LEFT_ANALOG_RIGHT{"joystick_left_analog_right"};
const std::string JOYSTICK_LEFT_ANALOG_LEFT{"joystick_left_analog_left"};
const std::string JOYSTICK_RIGHT_ANALOG_UP{"joystick_right_analog_up"};
const std::string JOYSTICK_RIGHT_ANALOG_DOWN{"joystick_right_analog_down"};
const std::string JOYSTICK_RIGHT_ANALOG_RIGHT{"joystick_right_analog_right"};
const std::string JOYSTICK_RIGHT_ANALOG_LEFT{"joystick_right_analog_left"};

static std::unordered_map<std::string, JoystickButtonType> JOYSTICK_NAME_TO_BUTTON_TYPE_MAP = {
    {JOYSTICK_BUTTON_A, JoystickButtonType::BUTTON_A},
    {JOYSTICK_BUTTON_B, JoystickButtonType::BUTTON_B},
    {JOYSTICK_BUTTON_X, JoystickButtonType::BUTTON_X},
    {JOYSTICK_BUTTON_Y, JoystickButtonType::BUTTON_Y},
    {JOYSTICK_START, JoystickButtonType::BUTTON_START},
    {JOYSTICK_BACK, JoystickButtonType::BUTTON_BACK},
    {JOYSTICK_LEFT_SHOULDER, JoystickButtonType::BUTTON_LEFT_SHOULDER},
    {JOYSTICK_RIGHT_SHOULDER, JoystickButtonType::BUTTON_RIGHT_SHOULDER},
    {JOYSTICK_LEFT_ANALOG, JoystickButtonType::BUTTON_LEFT_AXIS},
    {JOYSTICK_RIGHT_ANALOG, JoystickButtonType::BUTTON_RIGHT_AXIS},
};

static std::unordered_map<JoystickButtonType, std::string> JOYSTICK_BUTTON_TYPE_TO_NAME_MAP = {
    {JoystickButtonType::BUTTON_A, JOYSTICK_BUTTON_A},
    {JoystickButtonType::BUTTON_B, JOYSTICK_BUTTON_B},
    {JoystickButtonType::BUTTON_X, JOYSTICK_BUTTON_X},
    {JoystickButtonType::BUTTON_Y, JOYSTICK_BUTTON_Y},
    {JoystickButtonType::BUTTON_START, JOYSTICK_START},
    {JoystickButtonType::BUTTON_BACK, JOYSTICK_BACK},
    {JoystickButtonType::BUTTON_LEFT_SHOULDER, JOYSTICK_LEFT_SHOULDER},
    {JoystickButtonType::BUTTON_RIGHT_SHOULDER, JOYSTICK_RIGHT_SHOULDER},
    {JoystickButtonType::BUTTON_LEFT_AXIS, JOYSTICK_LEFT_ANALOG},
    {JoystickButtonType::BUTTON_RIGHT_AXIS, JOYSTICK_RIGHT_ANALOG},
};

static std::unordered_map<std::string, SDL_GameControllerButton> JOYSTICK_ACTION_BUTTON_MAPPINGS = {
    {JOYSTICK_BUTTON_A, SDL_CONTROLLER_BUTTON_A},
    {JOYSTICK_BUTTON_B, SDL_CONTROLLER_BUTTON_B},
    {JOYSTICK_BUTTON_X, SDL_CONTROLLER_BUTTON_X},
    {JOYSTICK_BUTTON_Y, SDL_CONTROLLER_BUTTON_Y},
    {JOYSTICK_KEYPAD_UP, SDL_CONTROLLER_BUTTON_DPAD_UP},
    {JOYSTICK_KEYPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
    {JOYSTICK_KEYPAD_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
    {JOYSTICK_KEYPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
    {JOYSTICK_START, SDL_CONTROLLER_BUTTON_START},
    {JOYSTICK_BACK, SDL_CONTROLLER_BUTTON_BACK},
    {JOYSTICK_LEFT_SHOULDER, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
    {JOYSTICK_RIGHT_SHOULDER, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
    {JOYSTICK_LEFT_SHOULDER, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
    {JOYSTICK_LEFT_ANALOG, SDL_CONTROLLER_BUTTON_LEFTSTICK},
    {JOYSTICK_RIGHT_ANALOG, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
    // Non Game Controller Button Actions
    {JOYSTICK_LEFT_TRIGGER, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_RIGHT_TRIGGER, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_LEFT_ANALOG_UP, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_LEFT_ANALOG_DOWN, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_LEFT_ANALOG_RIGHT, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_LEFT_ANALOG_LEFT, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_RIGHT_ANALOG_UP, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_RIGHT_ANALOG_DOWN, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_RIGHT_ANALOG_RIGHT, SDL_CONTROLLER_BUTTON_INVALID},
    {JOYSTICK_RIGHT_ANALOG_LEFT, SDL_CONTROLLER_BUTTON_INVALID}
};

struct JoystickInputPressState {
    bool isPressed = false;
    bool isJustPressed = false;
    bool isJustReleased = false;
};

static std::unordered_map<std::string, JoystickInputPressState> JOYSTICK_BUTTON_INPUT_FLAGS = {
    // Button Process
    {JOYSTICK_BUTTON_B,           JoystickInputPressState{}},
    {JOYSTICK_BUTTON_A,           JoystickInputPressState{}},
    {JOYSTICK_BUTTON_X,           JoystickInputPressState{}},
    {JOYSTICK_BUTTON_Y,           JoystickInputPressState{}},
    {JOYSTICK_START,              JoystickInputPressState{}},
    {JOYSTICK_BACK,               JoystickInputPressState{}},
    {JOYSTICK_LEFT_SHOULDER,      JoystickInputPressState{}},
    {JOYSTICK_RIGHT_SHOULDER,     JoystickInputPressState{}},
    {JOYSTICK_LEFT_ANALOG,        JoystickInputPressState{}},
    {JOYSTICK_RIGHT_ANALOG,       JoystickInputPressState{}},
    // Hat Process
    {JOYSTICK_KEYPAD_LEFT,        JoystickInputPressState{}},
    {JOYSTICK_KEYPAD_RIGHT,       JoystickInputPressState{}},
    {JOYSTICK_KEYPAD_UP,          JoystickInputPressState{}},
    {JOYSTICK_KEYPAD_DOWN,        JoystickInputPressState{}},
    // Axis Process
    {JOYSTICK_LEFT_ANALOG_LEFT,   JoystickInputPressState{}},
    {JOYSTICK_LEFT_ANALOG_RIGHT,  JoystickInputPressState{}},
    {JOYSTICK_LEFT_ANALOG_UP,     JoystickInputPressState{}},
    {JOYSTICK_LEFT_ANALOG_DOWN,   JoystickInputPressState{}},
    {JOYSTICK_RIGHT_ANALOG_LEFT,  JoystickInputPressState{}},
    {JOYSTICK_RIGHT_ANALOG_RIGHT, JoystickInputPressState{}},
    {JOYSTICK_RIGHT_ANALOG_UP,    JoystickInputPressState{}},
    {JOYSTICK_RIGHT_ANALOG_DOWN,  JoystickInputPressState{}},
    {JOYSTICK_LEFT_TRIGGER,       JoystickInputPressState{}},
    {JOYSTICK_RIGHT_TRIGGER,      JoystickInputPressState{}},
};

class JoystickInput {
  public:
    static JoystickInput* GetInstance();
    void ProcessSDLEvent(InputEvent &inputEvent);
    void LoadJoysticks();
    void ClearInputFlags();
    bool IsJoystickValue(const std::string &value) const;
    bool IsActionPressed(const std::string &value);
    bool IsActionJustPressed(const std::string &value);
    bool IsActionJustReleased(const std::string &value);

  private:
    SDL_Joystick *joystickController = nullptr;
    SDL_GameController *gameController = nullptr;
    Logger *logger = nullptr;

    JoystickInput();
    ~JoystickInput();
    void ProcessButtonPress(InputEvent &inputEvent);
    void ProcessButtonRelease(InputEvent &inputEvent);
    void ProcessJoyhatMotion(InputEvent &inputEvent);
    void ProcessAxisMotion();
};

class JoystickAction {
  public:
    JoystickAction() : joystickInput(JoystickInput::GetInstance()) {}

    bool IsJoystickValue(const std::string &actionValue) {
        return joystickInput->IsJoystickValue(actionValue);
    }

    void AddValue(const std::string &actionValue) {
        if (!HasValue(actionValue)) {
            joystickValues.emplace_back(actionValue);
        }
    }

    bool HasValue(const std::string &actionValue) {
        return std::find(joystickValues.begin(), joystickValues.end(), actionValue) != joystickValues.end();
    }

    bool IsActionPressed() {
        for (const std::string &value : joystickValues) {
            if (joystickInput->IsActionPressed(value)) {
                return true;
            }
        }
        return false;
    }

    bool IsActionJustPressed() {
        for (const std::string &value : joystickValues) {
            if (joystickInput->IsActionJustPressed(value)) {
                return true;
            }
        }
        return false;
    }

    bool IsActionJustReleased() {
        for (const std::string &value : joystickValues) {
            if (joystickInput->IsActionJustReleased(value)) {
                return true;
            }
        }
        return false;
    }

  private:
    JoystickInput *joystickInput = nullptr;
    std::vector<std::string> joystickValues;
};
#include "joystick_input.h"

JoystickInput::JoystickInput() : logger(Logger::GetInstance()) {}

JoystickInput::~JoystickInput() {
    SDL_JoystickClose(joystickController);
    SDL_GameControllerClose(gameController);
}

void JoystickInput::ProcessButtonPress(InputEvent &inputEvent) {
    const std::string &buttonValue = JOYSTICK_BUTTON_TYPE_TO_NAME_MAP[(JoystickButtonType) inputEvent.buttonValue];
    JOYSTICK_BUTTON_INPUT_FLAGS[buttonValue].isPressed = true;
    JOYSTICK_BUTTON_INPUT_FLAGS[buttonValue].isJustPressed = true;
}

void JoystickInput::ProcessButtonRelease(InputEvent &inputEvent) {
    const std::string &buttonValue = JOYSTICK_BUTTON_TYPE_TO_NAME_MAP[(JoystickButtonType) inputEvent.buttonValue];
    JOYSTICK_BUTTON_INPUT_FLAGS[buttonValue].isPressed = false;
    JOYSTICK_BUTTON_INPUT_FLAGS[buttonValue].isJustReleased = true;
}

void JoystickInput::ProcessJoyhatMotion(InputEvent &inputEvent) {
    if (inputEvent.hatValue & SDL_HAT_LEFT) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_LEFT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_LEFT].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_LEFT].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_LEFT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_LEFT].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_LEFT].isJustReleased = true;
        }
    }
    if (inputEvent.hatValue & SDL_HAT_RIGHT) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_RIGHT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_RIGHT].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_RIGHT].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_RIGHT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_RIGHT].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_RIGHT].isJustReleased = true;
        }
    }
    if (inputEvent.hatValue & SDL_HAT_UP) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_UP].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_UP].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_UP].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_UP].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_UP].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_UP].isJustReleased = true;
        }
    }
    if (inputEvent.hatValue & SDL_HAT_DOWN) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_DOWN].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_DOWN].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_DOWN].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_DOWN].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_DOWN].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_KEYPAD_DOWN].isJustReleased = true;
        }
    }
}

void JoystickInput::ProcessAxisMotion() {
    // LEFT AXIS
    // Horizontal
    Sint16 leftHorizontalValue = SDL_JoystickGetAxis(joystickController, (Uint8) JoystickAxisMotion::LEFT_HORIZONTAL_AXIS);
    if (leftHorizontalValue < -(Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_LEFT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_LEFT].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_LEFT].isJustPressed = true;
        }
    } else if (leftHorizontalValue > (Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_RIGHT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_RIGHT].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_RIGHT].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_LEFT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_LEFT].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_LEFT].isJustReleased = true;
        }
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_RIGHT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_RIGHT].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_RIGHT].isJustReleased = true;
        }
    }
    // Vertical
    Sint16 leftVerticalValue = SDL_JoystickGetAxis(joystickController, (Uint8) JoystickAxisMotion::LEFT_VERTICAL_AXIS);
    if (leftVerticalValue < -(Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_UP].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_UP].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_UP].isJustPressed = true;
        }
    } else if (leftVerticalValue > (Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_DOWN].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_DOWN].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_DOWN].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_UP].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_UP].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_UP].isJustReleased = true;
        }
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_DOWN].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_DOWN].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_ANALOG_DOWN].isJustReleased = true;
        }
    }

    // RIGHT AXIS
    // Horizontal
    Sint16 rightHorizontalValue = SDL_JoystickGetAxis(joystickController, (Uint8) JoystickAxisMotion::RIGHT_HORIZONTAL_AXIS);
    if (rightHorizontalValue < -(Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_LEFT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_LEFT].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_LEFT].isJustPressed = true;
        }
    } else if (rightHorizontalValue > (Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_RIGHT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_RIGHT].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_RIGHT].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_LEFT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_LEFT].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_LEFT].isJustReleased = true;
        }
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_RIGHT].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_RIGHT].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_RIGHT].isJustReleased = true;
        }
    }
    // Vertical
    Sint16 rightVerticalValue = SDL_JoystickGetAxis(joystickController, (Uint8) JoystickAxisMotion::RIGHT_VERTICAL_AXIS);
    if (rightVerticalValue < -(Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_UP].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_UP].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_UP].isJustPressed = true;
        }
    } else if (rightVerticalValue > (Uint8) JoystickDeadZone::AXIS) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_DOWN].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_DOWN].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_DOWN].isJustPressed = true;
        }
    } else {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_UP].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_UP].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_UP].isJustReleased = true;
        }
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_DOWN].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_DOWN].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_ANALOG_DOWN].isJustReleased = true;
        }
    }

    // Left Trigger
    Sint16 leftTriggerValue = SDL_JoystickGetAxis(joystickController, (Uint8) JoystickAxisMotion::LEFT_TRIGGER);
    if (leftTriggerValue < -(Uint8) JoystickDeadZone::TRIGGER) {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_TRIGGER].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_TRIGGER].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_TRIGGER].isJustReleased = true;
        }
    } else if (leftTriggerValue > (Uint8) JoystickDeadZone::TRIGGER) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_TRIGGER].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_TRIGGER].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_LEFT_TRIGGER].isJustPressed = true;
        }
    }
    // Right Trigger
    Sint16 rightTriggerValue = SDL_JoystickGetAxis(joystickController, (Uint8) JoystickAxisMotion::RIGHT_TRIGGER);
    if (rightTriggerValue < -(Uint8) JoystickDeadZone::TRIGGER) {
        if (JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_TRIGGER].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_TRIGGER].isPressed = false;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_TRIGGER].isJustReleased = true;
        }
    } else if (rightTriggerValue > (Uint8) JoystickDeadZone::TRIGGER) {
        if (!JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_TRIGGER].isPressed) {
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_TRIGGER].isPressed = true;
            JOYSTICK_BUTTON_INPUT_FLAGS[JOYSTICK_RIGHT_TRIGGER].isJustPressed = true;
        }
    }
}

JoystickInput* JoystickInput::GetInstance() {
    static JoystickInput *instance = new JoystickInput();
    return instance;
}

void JoystickInput::ProcessSDLEvent(InputEvent &inputEvent) {}

void JoystickInput::LoadJoysticks() {
    int result = SDL_GameControllerAddMappingsFromFile("assets/resources/game_controller_db.txt");
    assert(result != -1 && "Failed to load game controller db text file!");
    if (SDL_NumJoysticks() > 0) {
        joystickController = SDL_JoystickOpen(0);
        assert(joystickController != nullptr && "JoystickController didn't properly load!");
        gameController = SDL_GameControllerOpen(0);
        assert(gameController != nullptr && "GameController didn't properly load!");
    } else {
        logger->Warn("No joystick plugged in, not loading joysticks!");
    }
}

void JoystickInput::ClearInputFlags() {
    for (const auto &pair : JOYSTICK_BUTTON_INPUT_FLAGS) {
        JOYSTICK_BUTTON_INPUT_FLAGS[pair.first].isJustPressed = false;
        JOYSTICK_BUTTON_INPUT_FLAGS[pair.first].isJustReleased = false;
    }
}

bool JoystickInput::IsJoystickValue(const std::string &value) const {
    return false;
}

bool JoystickInput::IsActionPressed(const std::string &value) {
    return false;
}

bool JoystickInput::IsActionJustPressed(const std::string &value) {
    return false;
}

bool JoystickInput::IsActionJustReleased(const std::string &value) {
    return false;
}