#include "character/CharacterInputRouter.h"
#include <SDL2/SDL_gamecontroller.h>
#include <algorithm>
#include <cmath>

namespace frostbite2D {
namespace {

constexpr std::uint32_t kKeyboardRunDoubleTapWindowMs = 250;
constexpr float kAnalogRunThreshold = 0.75f;

float applyDeadZone(float value, float deadZone) {
  return std::fabs(value) >= deadZone ? value : 0.0f;
}

} // namespace

void CharacterInputRouter::QueueAction(const std::string& actionId) {
  if (actionId.empty()) {
    return;
  }
  if (std::find(actionRequests_.begin(), actionRequests_.end(), actionId)
      == actionRequests_.end()) {
    actionRequests_.push_back(actionId);
  }
}

bool CharacterInputRouter::IsDirectionPressed(KeyboardMoveDirection direction) const {
  switch (direction) {
    case KeyboardMoveDirection::Left:
      return left_;
    case KeyboardMoveDirection::Right:
      return right_;
    case KeyboardMoveDirection::Up:
      return up_;
    case KeyboardMoveDirection::Down:
      return down_;
    default:
      return false;
  }
}

bool CharacterInputRouter::HasDirectionalInput() const {
  return left_ || right_ || up_ || down_;
}

void CharacterInputRouter::RefreshKeyboardMoveMode() {
  if (!HasDirectionalInput()) {
    keyboardMoveMode_ = CharacterMoveMode::None;
    runDirection_ = KeyboardMoveDirection::None;
    return;
  }

  if (runDirection_ != KeyboardMoveDirection::None && IsDirectionPressed(runDirection_)) {
    keyboardMoveMode_ = CharacterMoveMode::Run;
    return;
  }

  runDirection_ = KeyboardMoveDirection::None;
  keyboardMoveMode_ = CharacterMoveMode::Walk;
}

bool CharacterInputRouter::OnKeyDown(const KeyEvent& event) {
  auto handleDirectionPress = [&](KeyboardMoveDirection direction,
                                  bool& pressedFlag,
                                  std::uint32_t& lastTapTimestamp) {
    pressedFlag = true;
    std::uint32_t now = event.getTimestamp();
    bool isDoubleTap = lastTapTimestamp > 0
        && now >= lastTapTimestamp
        && now - lastTapTimestamp <= kKeyboardRunDoubleTapWindowMs;
    lastTapTimestamp = now;

    if (isDoubleTap) {
      keyboardMoveMode_ = CharacterMoveMode::Run;
      runDirection_ = direction;
    } else if (keyboardMoveMode_ != CharacterMoveMode::Run
        || !IsDirectionPressed(runDirection_)) {
      keyboardMoveMode_ = CharacterMoveMode::Walk;
      runDirection_ = KeyboardMoveDirection::None;
    }
    return true;
  };

  switch (event.getKeyCode()) {
    case KeyCode::a:
    case KeyCode::Left:
      return handleDirectionPress(
          KeyboardMoveDirection::Left, left_, leftLastTapTimestamp_);
    case KeyCode::d:
    case KeyCode::Right:
      return handleDirectionPress(
          KeyboardMoveDirection::Right, right_, rightLastTapTimestamp_);
    case KeyCode::w:
    case KeyCode::Up:
      return handleDirectionPress(
          KeyboardMoveDirection::Up, up_, upLastTapTimestamp_);
    case KeyCode::s:
    case KeyCode::Down:
      return handleDirectionPress(
          KeyboardMoveDirection::Down, down_, downLastTapTimestamp_);
    case KeyCode::x:
      QueueAction("attack");
      return true;
    case KeyCode::c:
      jumpPressed_ = true;
      return true;
    case KeyCode::z:
      QueueAction("skill_1");
      return true;
    case KeyCode::LShift:
      QueueAction("dash");
      return true;
    default:
      return false;
  }
}

bool CharacterInputRouter::OnKeyUp(const KeyUpEvent& event) {
  auto handleDirectionRelease = [&](bool& pressedFlag) {
    pressedFlag = false;
    RefreshKeyboardMoveMode();
    return true;
  };

  switch (event.getKeyCode()) {
    case KeyCode::a:
    case KeyCode::Left:
      return handleDirectionRelease(left_);
    case KeyCode::d:
    case KeyCode::Right:
      return handleDirectionRelease(right_);
    case KeyCode::w:
    case KeyCode::Up:
      return handleDirectionRelease(up_);
    case KeyCode::s:
    case KeyCode::Down:
      return handleDirectionRelease(down_);
    default:
      return false;
  }
}

bool CharacterInputRouter::OnJoystickAxis(const JoystickAxisEvent& event) {
  if (event.getAxis() == SDL_CONTROLLER_AXIS_LEFTX) {
    leftStickX_ = applyDeadZone(event.getNormalizedValue(), joystickDeadZone_);
    return true;
  }
  if (event.getAxis() == SDL_CONTROLLER_AXIS_LEFTY) {
    leftStickY_ = applyDeadZone(event.getNormalizedValue(), joystickDeadZone_);
    return true;
  }
  return false;
}

bool CharacterInputRouter::OnJoystickButtonDown(
    const JoystickButtonDownEvent& event) {
  switch (event.getButton()) {
    case SDL_CONTROLLER_BUTTON_A:
      jumpPressed_ = true;
      return true;
    case SDL_CONTROLLER_BUTTON_X:
      QueueAction("attack");
      return true;
    case SDL_CONTROLLER_BUTTON_B:
    case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
      QueueAction("skill_1");
      return true;
    case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
      QueueAction("dash");
      return true;
    default:
      return false;
  }
}

bool CharacterInputRouter::OnJoystickButtonUp(
    const JoystickButtonUpEvent& event) {
  (void)event;
  return false;
}

void CharacterInputRouter::EmitCommands(CharacterCommandBuffer& commandBuffer) {
  Vec2 moveAxis(leftStickX_, leftStickY_);
  CharacterMoveMode moveMode = CharacterMoveMode::None;
  float stickMagnitudeSquared = moveAxis.lengthSquared();
  if (stickMagnitudeSquared >= joystickDeadZone_ * joystickDeadZone_) {
    float stickMagnitude = std::sqrt(stickMagnitudeSquared);
    if (stickMagnitude > 1.0f) {
      moveAxis = moveAxis.normalized();
      stickMagnitude = 1.0f;
    }
    moveMode = stickMagnitude >= kAnalogRunThreshold
        ? CharacterMoveMode::Run
        : CharacterMoveMode::Walk;
  } else {
    moveAxis.x = (right_ ? 1.0f : 0.0f) - (left_ ? 1.0f : 0.0f);
    moveAxis.y = (down_ ? 1.0f : 0.0f) - (up_ ? 1.0f : 0.0f);
    if (moveAxis.lengthSquared() > 1.0f) {
      moveAxis = moveAxis.normalized();
    }
    if (moveAxis.lengthSquared() >= 0.0001f) {
      moveMode = keyboardMoveMode_ == CharacterMoveMode::Run
          ? CharacterMoveMode::Run
          : CharacterMoveMode::Walk;
    }
  }
  commandBuffer.Submit(
      {CharacterCommandType::Move, moveAxis, 0.0f, moveMode, std::string()});

  if (jumpPressed_) {
    commandBuffer.Submit(
        {CharacterCommandType::JumpPressed, Vec2::Zero(), 0.0f,
         CharacterMoveMode::None, std::string()});
  }
  for (const auto& actionId : actionRequests_) {
    commandBuffer.Submit(
        {CharacterCommandType::ActionPressed, Vec2::Zero(), 0.0f,
         CharacterMoveMode::None, actionId});
  }

  ClearFrameState();
}

void CharacterInputRouter::ClearFrameState() {
  jumpPressed_ = false;
  actionRequests_.clear();
}

} // namespace frostbite2D
