#pragma once
#include "../../utils/point.h"

#include <SDL2/SDL.h>
#include <unordered_map>
#include <unordered_set>

namespace quokka_gfx
{
class InputProcessing;

//------------------------------------------------------------
class InputManager
{
  friend class InputProcessing;

  public:
  enum class KeyState : uint8_t
  {
    Up = 0,      // Released
    Pressed,     // Pressed (this frame only)
    Held,        // Held
    Released     // Released (this frame only)
  };

  enum class MouseButton : uint8_t
  {
    Left   = SDL_BUTTON_LEFT,
    Middle = SDL_BUTTON_MIDDLE,
    Right  = SDL_BUTTON_RIGHT
  };

  public:
  InputManager()                                   = default;     // Default constructor
  virtual ~InputManager()                          = default;     // Default destructor
  InputManager(const InputManager &obj)            = delete;      // Copy constructor
  InputManager(InputManager &&obj)                 = delete;      // Move constructor
  InputManager &operator=(const InputManager &obj) = delete;      // Copy assignment operator
  InputManager &operator=(InputManager &&obj)      = delete;      // Move assignment operator

  InputProcessing getEventProcessing();     // Get event processing session

  // Keyboard accessors
  [[nodiscard]] KeyState getKeyState(SDL_Scancode key) const;       // Get the current state of a key
  [[nodiscard]] bool     isKeyPressed(SDL_Scancode key) const;      // Check if a key was pressed this frame
  [[nodiscard]] bool     isKeyHeld(SDL_Scancode key) const;         // Check if a key is currently held down
  [[nodiscard]] bool     isKeyReleased(SDL_Scancode key) const;     // Check if a key was released this frame
  [[nodiscard]] bool     isKeyDown(SDL_Scancode key) const;         // Check if a key is currently down
  [[nodiscard]] bool     isKeyUp(SDL_Scancode key) const;           // Check if a key is currently up

  [[nodiscard]] bool isShiftDown() const;      // Check if Shift modifier is currently down
  [[nodiscard]] bool isLShiftDown() const;     // Check if Left Shift modifier is currently down
  [[nodiscard]] bool isRShiftDown() const;     // Check if Right Shift modifier is currently down
  [[nodiscard]] bool isCtrlDown() const;       // Check if Ctrl modifier is currently down
  [[nodiscard]] bool isLCtrlDown() const;      // Check if Left Ctrl modifier is currently down
  [[nodiscard]] bool isRCtrlDown() const;      // Check if Right Ctrl modifier is currently down
  [[nodiscard]] bool isAltDown() const;        // Check if Alt modifier is currently down
  [[nodiscard]] bool isLAltDown() const;       // Check if Left Alt modifier is currently down
  [[nodiscard]] bool isRAltDown() const;       // Check if Right Alt modifier is currently down

  // Mouse accessors
  [[nodiscard]] int   getMouseX() const;            // Get current mouse X position
  [[nodiscard]] int   getMouseY() const;            // Get current mouse Y position
  [[nodiscard]] Point getMousePosition() const;     // Get current mouse position as a rectangle (1x1)
  [[nodiscard]] int   getMouseDeltaX() const;       // Get mouse movement delta X since last frame
  [[nodiscard]] int   getMouseDeltaY() const;       // Get mouse movement delta Y since last frame

  [[nodiscard]] KeyState getMouseButtonState(MouseButton button) const;       // Get the current state of a mouse button
  [[nodiscard]] bool     isMouseButtonPressed(MouseButton button) const;      // Check if a mouse button was pressed this frame
  [[nodiscard]] bool     isMouseButtonHeld(MouseButton button) const;         // Check if a mouse button is currently held down
  [[nodiscard]] bool     isMouseButtonReleased(MouseButton button) const;     // Check if a mouse button was released this frame
  [[nodiscard]] bool     isMouseButtonDown(MouseButton button) const;         // Check if a mouse button is currently down
  [[nodiscard]] bool     isMouseButtonUp(MouseButton button) const;           // Check if a mouse button is currently up

  [[nodiscard]] int getMouseWheelDelta() const;     // Get scroll wheel delta since last frame

  protected:
  // Key states
  struct
  {
    std::unordered_map<SDL_Scancode, KeyState> keyStates;        // Key states (Pressed, Held, Released)
    std::unordered_set<SDL_Scancode>           keysPressed;      // Keys pressed this frame
    std::unordered_set<SDL_Scancode>           keysReleased;     // Keys released this frame
  } m_keys;

  // Mouse states, position and movement
  struct
  {
    std::unordered_map<MouseButton, KeyState> buttonStates;       // Button states (Pressed, Held, Released)
    int                                       posX       = 0;     // Current X position of the mouse
    int                                       posY       = 0;     // Current Y position of the mouse
    int                                       lastPosX   = 0;     // Previous X position of the mouse (for delta calculation)
    int                                       lastPosY   = 0;     // Previous Y position of the mouse (for delta calculation)
    int                                       deltaX     = 0;     // Movement delta X since last frame
    int                                       deltaY     = 0;     // Movement delta Y since last frame
    int                                       wheelDelta = 0;     // Scroll wheel delta since last frame
  } m_mouse;
};
//------------------------------------------------------------

/* --- */

//------------------------------------------------------------
class InputProcessing
{
  public:
  InputProcessing() = delete;                                          // Default constructor
  virtual ~InputProcessing();                                          // Default destructor
  InputProcessing(const InputProcessing &obj)            = delete;     // Copy constructor
  InputProcessing(InputProcessing &&obj)                 = delete;     // Move constructor
  InputProcessing &operator=(const InputProcessing &obj) = delete;     // Copy assignment operator
  InputProcessing &operator=(InputProcessing &&obj)      = delete;     // Move assignment operator

  explicit InputProcessing(InputManager *inputEventHandle);     // Constructor

  void process(const SDL_Event &event) const;     // Process an SDL event

  protected:
  InputManager *m_inputEventHandle = nullptr;

  private:
  void begin() const;     // Called at the beginning of event processing
  void end() const;       // Called at the end of event processing
};
//------------------------------------------------------------
}     // namespace quokka_gfx
