#include "InputManager.h"

#include <ranges>

using namespace std;
using namespace quokka_gfx;
//--------------------------------------------------------------
/* Check if a key is being held down */
bool InputManager::isKeyDown(const SDL_Scancode code) const
{
  if (!m_currentKeys.contains(code))
    return false;
  return m_currentKeys.at(code);
}
//--------------------------------------------------------------
/* Check if a key is pressed (transition from up to down on this frame) */
bool InputManager::isKeyPressed(const SDL_Scancode code) const
{
  if (!isKeyDown(code))
    return false;

  if (!m_previousKeys.contains(code))
    return true;

  return !m_previousKeys.at(code);
}
//--------------------------------------------------------------
/* Check if a key is released (transition from down to up on this frame) */
bool InputManager::isKeyReleased(const SDL_Scancode code) const
{
  if (isKeyDown(code))
    return false;

  if (!m_previousKeys.contains(code))
    return false;

  return m_previousKeys.at(code);
}
//--------------------------------------------------------------
/* Get a list of keys that are currently being held down */
void InputManager::getHoldingKeys(KeyList &keyList) const
{
  keyList.clear();

  // Create a view of the keys that are currently held down
  auto holdingView = m_currentKeys | views::filter([](const auto &pair)
                                                   { return pair.second; }) |
                     views::keys;

  // Copy the keys from the view to the keyList
  ranges::copy(holdingView, back_inserter(keyList));
}
//--------------------------------------------------------------
/* Get a list of keys that were pressed in the current frame */
void InputManager::getPressedKeys(KeyList &keyList) const
{
  keyList.clear();

  auto pressedView = m_currentKeys | views::filter([this](const auto &pair)
                                                   {
                       const auto& [scancode, isDown] = pair;
                       if (!isDown) 
                         return false;
                       
                       auto it = m_previousKeys.find(scancode);
                       const bool wasDown = (it != m_previousKeys.end()) ? it->second : false;
                       return !wasDown; }) |
                     views::keys;

  ranges::copy(pressedView, back_inserter(keyList));
}
//--------------------------------------------------------------
/* Get a list of keys that were released in the current frame */
void InputManager::getReleasedKeys(KeyList &keyList) const
{
  keyList.clear();

  auto releasedView = m_previousKeys | std::views::filter([this](const auto &pair)
                                                          {
                        const auto& [scancode, wasDown] = pair;
                        if (!wasDown) 
                          return false;

                        auto it = m_currentKeys.find(scancode);
                        const bool isDown = (it != m_currentKeys.end()) ? it->second : false;
                        return !isDown; }) |
                      std::views::keys;

  std::ranges::copy(releasedView, back_inserter(keyList));
}
//--------------------------------------------------------------
/* Get the current mouse position */
FPos InputManager::getMousePosition() const
{
  return m_mousePosition;
}
//--------------------------------------------------------------
/* Check if a mouse button is being held down */
bool InputManager::isMouseButtonDown(const MouseButton button) const
{
  const auto it = m_currentMouseButtons.find(button);
  return (it != m_currentMouseButtons.end()) ? it->second : false;
}
//--------------------------------------------------------------
/* Check if a mouse button is pressed (transition from up to down on this frame) */
bool InputManager::isMouseButtonPressed(const MouseButton button) const
{
  if (!isMouseButtonDown(button))
    return false;

  if (!m_previousMouseButtons.contains(button))
    return true;

  return !m_previousMouseButtons.at(button);
}
//--------------------------------------------------------------
/* Check if a mouse button is released (transition from down to up on this frame) */
bool InputManager::isMouseButtonReleased(const MouseButton button) const
{
  if (isMouseButtonDown(button))
    return false;

  if (!m_previousMouseButtons.contains(button))
    return false;

  return m_previousMouseButtons.at(button);
}
//--------------------------------------------------------------
/* Check if the mouse wheel has been scrolled in the current frame */
bool InputManager::isMouseWheeling() const
{
  return (m_wheelX != 0.0f || m_wheelY != 0.0f);
}
//--------------------------------------------------------------
/* Get the mouse wheel delta for horizontal scrolling */
float InputManager::getMouseWheelX() const
{
  return m_wheelX;
}
//--------------------------------------------------------------
/* Get the mouse wheel delta for vertical scrolling */
float InputManager::getMouseWheelY() const
{
  return m_wheelY;
}
//--------------------------------------------------------------
/* Get the ID of the window currently focused for keyboard input */
uint32_t InputManager::getFocusedWindowID() const
{
  return m_focusedWindowID;
}
//--------------------------------------------------------------
/* Get the ID of the window currently hovered by the mouse */
uint32_t InputManager::getHoveredWindowID() const
{
  return m_hoveredWindowID;
}
//--------------------------------------------------------------
/* Update the input states (to be called each frame) */
void InputManager::updateStates()
{
  // Clear the mouse wheel delta for the current frame
  clearWheelDelta();

  // Update the previous key states to the current key states for the next frame
  m_previousKeys = m_currentKeys;

  // Update the previous mouse button states to the current mouse button states for the next frame
  m_previousMouseButtons = m_currentMouseButtons;
}
//--------------------------------------------------------------
/* Process an SDL event to update input states */
void InputManager::processEvent(const SDL_Event &event)
{
  // Process events
  if (event.type == SDL_EVENT_WINDOW_FOCUS_GAINED)
  {
    m_focusedWindowID = event.window.windowID;
  }
  if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST)
  {
    if (m_focusedWindowID == event.window.windowID)
      m_focusedWindowID = 0;
  }
  else if (event.type == SDL_EVENT_WINDOW_MOUSE_ENTER)
  {
    m_hoveredWindowID = event.window.windowID;
  }
  else if (event.type == SDL_EVENT_WINDOW_MOUSE_LEAVE)
  {
    m_hoveredWindowID = 0;
  }
  else if (event.type == SDL_EVENT_KEY_DOWN)
  {
    m_currentKeys[event.key.scancode] = true;
  }
  else if (event.type == SDL_EVENT_KEY_UP)
  {
    m_currentKeys[event.key.scancode] = false;
  }
  else if (event.type == SDL_EVENT_MOUSE_MOTION)
  {
    m_mousePosition = { .x = event.motion.x, .y = event.motion.y };
  }
  else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
  {
    m_currentMouseButtons[static_cast<MouseButton>(event.button.button)] = true;
  }
  else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP)
  {
    m_currentMouseButtons[static_cast<MouseButton>(event.button.button)] = false;
  }
  else if (event.type == SDL_EVENT_MOUSE_WHEEL)
  {
    processWheelEvent(event.wheel);
  }
}
//--------------------------------------------------------------
/* Clear the mouse wheel delta */
void InputManager::clearWheelDelta()
{
  m_wheelX = 0.0f;
  m_wheelY = 0.0f;
}
//--------------------------------------------------------------
/* Process a mouse wheel event to update the wheel delta */
void InputManager::processWheelEvent(const SDL_MouseWheelEvent &event)
{
  m_wheelX = event.x;     // Positive value for scrolling right, negative for scrolling left
  m_wheelY = event.y;     // Positive value for scrolling up, negative for scrolling down
}
//--------------------------------------------------------------
