#include "camera/GameCameraController.h"
#include "map/GameMap.h"
#include <SDL2/SDL.h>
#include <cmath>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h>
#include <algorithm>

namespace frostbite2D {

namespace {

constexpr int kDebugStickDeadzone = 8000;

float normalizeControllerAxis(int16 value) {
  float magnitude = static_cast<float>(value);
  if (std::abs(magnitude) <= static_cast<float>(kDebugStickDeadzone)) {
    return 0.0f;
  }

  float sign = magnitude < 0.0f ? -1.0f : 1.0f;
  float normalized =
      (std::abs(magnitude) - static_cast<float>(kDebugStickDeadzone)) /
      (32767.0f - static_cast<float>(kDebugStickDeadzone));
  return std::clamp(normalized, 0.0f, 1.0f) * sign;
}

}

GameCameraController::~GameCameraController() {
  closeDebugController();
}

void GameCameraController::SetMap(GameMap* map) {
  map_ = map;
  initialized_ = false;
}

void GameCameraController::SetTarget(Actor* target) {
  target_ = target;
}

void GameCameraController::ClearTarget() {
  target_ = nullptr;
}

void GameCameraController::SetDebugEnabled(bool enabled) {
  debugEnabled_ = enabled;
  if (debugEnabled_) {
    openDebugController();
  } else {
    closeDebugController();
  }
}

void GameCameraController::SetZoom(float zoom) {
  zoom_ = std::max(zoom, 0.01f);
  if (map_) {
    focus_ = map_->ClampCameraFocus(focus_, zoom_);
  }
  applyFocus();
}

void GameCameraController::SetFocus(const Vec2& focus) {
  focus_ = map_ ? map_->ClampCameraFocus(focus, zoom_) : focus;
  initialized_ = true;
  applyFocus();
}

void GameCameraController::SnapToDefaultFocus() {
  if (!map_) {
    return;
  }

  focus_ = map_->ClampCameraFocus(map_->GetDefaultCameraFocus(), zoom_);
  initialized_ = true;
  applyFocus();
}

void GameCameraController::Update(float deltaTime) {
  if (!map_) {
    return;
  }

  if (!initialized_) {
    SnapToDefaultFocus();
  }

  if (target_) {
    focus_ = target_->GetPosition();
  } else if (debugEnabled_) {
    updateDebugInput(deltaTime);
  }

  focus_ = map_->ClampCameraFocus(focus_, zoom_);
  applyFocus();
}

void GameCameraController::openDebugController() {
  if (debugController_ && SDL_GameControllerGetAttached(debugController_)) {
    return;
  }

  closeDebugController();

  int joystickCount = SDL_NumJoysticks();
  for (int i = 0; i < joystickCount; ++i) {
    if (!SDL_IsGameController(i)) {
      continue;
    }

    debugController_ = SDL_GameControllerOpen(i);
    if (debugController_) {
      return;
    }
  }
}

void GameCameraController::closeDebugController() {
  if (!debugController_) {
    return;
  }

  SDL_GameControllerClose(debugController_);
  debugController_ = nullptr;
}

void GameCameraController::updateDebugInput(float deltaTime) {
  const uint8* keyboardState = SDL_GetKeyboardState(nullptr);
  float moveX = 0.0f;
  float moveY = 0.0f;

  if (keyboardState) {
    if (keyboardState[SDL_SCANCODE_A]) {
      moveX -= 1.0f;
    }
    if (keyboardState[SDL_SCANCODE_D]) {
      moveX += 1.0f;
    }
    if (keyboardState[SDL_SCANCODE_W]) {
      moveY -= 1.0f;
    }
    if (keyboardState[SDL_SCANCODE_S]) {
      moveY += 1.0f;
    }
  }

  if (debugEnabled_ && !debugController_) {
    openDebugController();
  }

  if (debugController_ && !SDL_GameControllerGetAttached(debugController_)) {
    closeDebugController();
    openDebugController();
  }

  if (debugController_ && SDL_GameControllerGetAttached(debugController_)) {
    moveX += normalizeControllerAxis(
        SDL_GameControllerGetAxis(debugController_, SDL_CONTROLLER_AXIS_LEFTX));
    moveY += normalizeControllerAxis(
        SDL_GameControllerGetAxis(debugController_, SDL_CONTROLLER_AXIS_LEFTY));
  }

  moveX = std::clamp(moveX, -1.0f, 1.0f);
  moveY = std::clamp(moveY, -1.0f, 1.0f);

  if (moveX == 0.0f && moveY == 0.0f) {
    return;
  }

  focus_.x += moveX * debugMoveSpeed_ * deltaTime;
  focus_.y += moveY * debugMoveSpeed_ * deltaTime;
}

void GameCameraController::applyFocus() const {
  Camera* camera = Renderer::get().getCamera();
  if (!camera || !map_) {
    return;
  }

  camera->setZoom(zoom_);
  float halfWidth = camera->getVisibleWidth() * 0.5f;
  float halfHeight = camera->getVisibleHeight() * 0.5f;
  Vec2 cameraPos(focus_.x - halfWidth, focus_.y - halfHeight);
  camera->setPosition(cameraPos);
  map_->ApplyCameraFocus(focus_);
}

} // namespace frostbite2D
