#include "inputManager.h"

#include <ranges>
#include <stdexcept>

using namespace std;
using namespace quokka_gfx;
//------------------------------------------------------------
/* Get event processing session */
InputProcessing InputManager::getEventProcessing()
{
  return InputProcessing(this);
}
//------------------------------------------------------------
/* Get the current state of a key */
InputManager::KeyState InputManager::getKeyState(const SDL_Scancode key) const
{
  const auto it = m_keys.keyStates.find(key);
  return it != m_keys.keyStates.end() ? it->second : KeyState::Up;
}
//------------------------------------------------------------
/* Check if a key was pressed this frame */
bool InputManager::isKeyPressed(const SDL_Scancode key) const
{
  if (!m_keys.keyStates.contains(key))
    return false;
  return m_keys.keyStates.at(key) == KeyState::Pressed;
}
//------------------------------------------------------------
/* Check if a key is currently held down */
bool InputManager::isKeyHeld(const SDL_Scancode key) const
{
  if (!m_keys.keyStates.contains(key))
    return false;
  return m_keys.keyStates.at(key) == KeyState::Held;
}
//------------------------------------------------------------
/* Check if a key was released this frame */
bool InputManager::isKeyReleased(const SDL_Scancode key) const
{
  if (!m_keys.keyStates.contains(key))
    return false;
  return m_keys.keyStates.at(key) == KeyState::Released;
}
//------------------------------------------------------------
/* Check if a key is currently down */
bool InputManager::isKeyDown(const SDL_Scancode key) const
{
  if (!m_keys.keyStates.contains(key))
    return false;
  const auto state = m_keys.keyStates.at(key);
  return state == KeyState::Pressed || state == KeyState::Held;
}
//------------------------------------------------------------
/* Check if a key is currently up */
bool InputManager::isKeyUp(const SDL_Scancode key) const
{
  if (!m_keys.keyStates.contains(key))
    return false;
  const auto state = m_keys.keyStates.at(key);
  return state == KeyState::Up || state == KeyState::Released;
}
//------------------------------------------------------------
/* Check if Shift modifier is currently down */
bool InputManager::isShiftDown() const
{
  return isLShiftDown() || isRShiftDown();
}
//------------------------------------------------------------
/* Check if Left Shift modifier is currently down */
bool InputManager::isLShiftDown() const
{
  return isKeyDown(SDL_SCANCODE_LSHIFT);
}
//------------------------------------------------------------
/* Check if Right Shift modifier is currently down */
bool InputManager::isRShiftDown() const
{
  return isKeyDown(SDL_SCANCODE_RSHIFT);
}
//------------------------------------------------------------
/* Check if Ctrl modifier is currently down */
bool InputManager::isCtrlDown() const
{
  return isLCtrlDown() || isRCtrlDown();
}
//------------------------------------------------------------
/* Check if Left Ctrl modifier is currently down */
bool InputManager::isLCtrlDown() const
{
  return isKeyDown(SDL_SCANCODE_LCTRL);
}
//------------------------------------------------------------
/* Check if Right Ctrl modifier is currently down */
bool InputManager::isRCtrlDown() const
{
  return isKeyDown(SDL_SCANCODE_RCTRL);
}
//------------------------------------------------------------
/* Check if Alt modifier is currently down */
bool InputManager::isAltDown() const
{
  return isLAltDown() || isRAltDown();
}
//------------------------------------------------------------
/* Check if Left Alt modifier is currently down */
bool InputManager::isLAltDown() const
{
  return isKeyDown(SDL_SCANCODE_LALT);
}
//------------------------------------------------------------
/* Check if Right Alt modifier is currently down */
bool InputManager::isRAltDown() const
{
  return isKeyDown(SDL_SCANCODE_RALT);
}
//------------------------------------------------------------
/* Get current mouse X position */
int InputManager::getMouseX() const
{
  return m_mouse.posX;
}
//------------------------------------------------------------
/* Get current mouse Y position */
int InputManager::getMouseY() const
{
  return m_mouse.posY;
}
//------------------------------------------------------------
/* Get current mouse position */
Point InputManager::getMousePosition() const
{
  return Point(m_mouse.posX, m_mouse.posY);
}
//------------------------------------------------------------
/* Get mouse movement delta X since last frame */
int InputManager::getMouseDeltaX() const
{
  return m_mouse.deltaX;
}
//------------------------------------------------------------
/* Get mouse movement delta Y since last frame */
int InputManager::getMouseDeltaY() const
{
  return m_mouse.deltaY;
}
//------------------------------------------------------------
/* Get the current state of a mouse button */
InputManager::KeyState InputManager::getMouseButtonState(const MouseButton button) const
{
  auto it = m_mouse.buttonStates.find(button);
  return it != m_mouse.buttonStates.end() ? it->second : KeyState::Up;
}
//------------------------------------------------------------
/* Get current mouse X position */
bool InputManager::isMouseButtonPressed(const MouseButton button) const
{
  if (!m_mouse.buttonStates.contains(button))
    return false;
  return m_mouse.buttonStates.at(button) == KeyState::Pressed;
}
//------------------------------------------------------------
/* Get current mouse Y position */
bool InputManager::isMouseButtonHeld(const MouseButton button) const
{
  if (!m_mouse.buttonStates.contains(button))
    return false;
  return m_mouse.buttonStates.at(button) == KeyState::Held;
}
//------------------------------------------------------------
/* Get mouse movement delta X since last frame */
bool InputManager::isMouseButtonReleased(const MouseButton button) const
{
  if (!m_mouse.buttonStates.contains(button))
    return false;
  return m_mouse.buttonStates.at(button) == KeyState::Released;
}
//------------------------------------------------------------
/* Get mouse movement delta Y since last frame */
bool InputManager::isMouseButtonDown(const MouseButton button) const
{
  if (!m_mouse.buttonStates.contains(button))
    return false;
  const auto state = m_mouse.buttonStates.at(button);
  return state == KeyState::Pressed || state == KeyState::Held;
}
//------------------------------------------------------------
/* Check if a mouse button is currently up */
bool InputManager::isMouseButtonUp(const MouseButton button) const
{
  if (!m_mouse.buttonStates.contains(button))
    return false;
  const auto state = m_mouse.buttonStates.at(button);
  return state == KeyState::Up || state == KeyState::Released;
}
//------------------------------------------------------------
/* Get scroll wheel delta since last frame */
int InputManager::getMouseWheelDelta() const
{
  return m_mouse.wheelDelta;
}
//------------------------------------------------------------

/* --- */

//------------------------------------------------------------
/* Constructor */
InputProcessing::InputProcessing(InputManager *inputEventHandle)
{
  if (!inputEventHandle)
    throw std::invalid_argument("InputManager pointer cannot be null");
  m_inputEventHandle = inputEventHandle;

  begin();
}
//------------------------------------------------------------
/* Default destructor */
InputProcessing::~InputProcessing()
{
  end();
}
//------------------------------------------------------------
/* Process an SDL event */
void InputProcessing::process(const SDL_Event &event) const
{
  switch (event.type)
  {
    // ==== KEYBOARD ====
    case SDL_KEYDOWN:
      if (!event.key.repeat)     // Skip auto-repeated keydown events
      {
        m_inputEventHandle->m_keys.keyStates[event.key.keysym.scancode] = InputManager::KeyState::Pressed;
        m_inputEventHandle->m_keys.keysPressed.insert(event.key.keysym.scancode);
      }
      break;

    case SDL_KEYUP:
      m_inputEventHandle->m_keys.keyStates[event.key.keysym.scancode] = InputManager::KeyState::Released;
      m_inputEventHandle->m_keys.keysReleased.insert(event.key.keysym.scancode);
      break;

    // ==== MOUSE - MOTION ====
    case SDL_MOUSEMOTION:
      m_inputEventHandle->m_mouse.posX   = event.motion.x;
      m_inputEventHandle->m_mouse.posY   = event.motion.y;
      m_inputEventHandle->m_mouse.deltaX = event.motion.xrel;
      m_inputEventHandle->m_mouse.deltaY = event.motion.yrel;
      break;

    // ==== MOUSE - BOUTONS ====
    case SDL_MOUSEBUTTONDOWN:
      m_inputEventHandle->m_mouse.buttonStates[static_cast<InputManager::MouseButton>(event.button.button)] = InputManager::KeyState::Pressed;
      break;

    case SDL_MOUSEBUTTONUP:
      m_inputEventHandle->m_mouse.buttonStates[static_cast<InputManager::MouseButton>(event.button.button)] = InputManager::KeyState::Released;
      break;

    // ==== MOUSE - WHEEL ====
    case SDL_MOUSEWHEEL:
      m_inputEventHandle->m_mouse.wheelDelta = event.wheel.y;     // positive = towards up, negative = towards down
      break;

      //  // ===== SAISIE DE TEXTE =====
      //  case SDL_TEXTINPUT:
      //    if (m_textInputCallback)
      //    {
      //      m_textInputCallback(event.text.text);
      //    }
      //    break;

    default:
      break;
  }
}
//------------------------------------------------------------
/* Called at the beginning of event processing */
void InputProcessing::begin() const
{
  // This function is called at the beginning of event processing to
  // reset states and prepare for new events

  // Initialize key states for this frame
  m_inputEventHandle->m_keys.keysPressed.clear();
  m_inputEventHandle->m_keys.keysReleased.clear();

  // Initialize mouse parameters
  m_inputEventHandle->m_mouse.lastPosX   = m_inputEventHandle->m_mouse.posX;
  m_inputEventHandle->m_mouse.lastPosY   = m_inputEventHandle->m_mouse.posY;
  m_inputEventHandle->m_mouse.deltaX     = 0;
  m_inputEventHandle->m_mouse.deltaY     = 0;
  m_inputEventHandle->m_mouse.wheelDelta = 0;
}
//------------------------------------------------------------
/* Called at the end of event processing */
void InputProcessing::end() const
{
  // This function is called at the end of event processing

  // Update key states
  for (auto &state : m_inputEventHandle->m_keys.keyStates | views::values)
  {
    if (state == InputManager::KeyState::Pressed)
      state = InputManager::KeyState::Held;
    else if (state == InputManager::KeyState::Released)
      state = InputManager::KeyState::Up;
  }

  // Update mouse button states
  for (auto &state : m_inputEventHandle->m_mouse.buttonStates | views::values)
  {
    if (state == InputManager::KeyState::Pressed)
      state = InputManager::KeyState::Held;
    else if (state == InputManager::KeyState::Released)
      state = InputManager::KeyState::Up;
  }
}
//------------------------------------------------------------
