#include "camera_controller.h"

#include <algorithm>
#include <cmath>

namespace ns_game {

namespace {

float clampValue(float value, float minValue, float maxValue) {
  if (maxValue < minValue) {
    return minValue;
  }
  return std::clamp(value, minValue, maxValue);
}

float smoothFactor(float smoothing, float deltaTime) {
  if (smoothing <= 0.0f) {
    return 1.0f;
  }
  return 1.0f - std::exp(-smoothing * std::max(deltaTime, 0.0f));
}

} // namespace

void CameraController::Configure(const CameraControllerConfig& config) {
  config_ = config;
}

void CameraController::SetWorldBounds(const frostbite2D::Rect& worldBounds) {
  const float width = std::max(worldBounds.width(), config_.viewportWidth);
  const float height = std::max(worldBounds.height(), config_.viewportHeight);
  config_.worldBounds =
      frostbite2D::Rect(worldBounds.left(), worldBounds.top(), width, height);
}

void CameraController::SnapToTarget(frostbite2D::Camera& camera,
                                    const frostbite2D::Rect& targetBounds) {
  camera.setPosition(clampToWorld(
      resolveDesiredPosition(camera.getPosition(), targetBounds)));
}

void CameraController::Update(frostbite2D::Camera& camera,
                              const frostbite2D::Rect& targetBounds,
                              float deltaTime) {
  const frostbite2D::Vec2 current = camera.getPosition();
  const frostbite2D::Vec2 desired =
      clampToWorld(resolveDesiredPosition(current, targetBounds));
  const float t = smoothFactor(config_.smoothing, deltaTime);
  camera.setPosition(frostbite2D::Vec2(
      current.x + (desired.x - current.x) * t,
      current.y + (desired.y - current.y) * t));
}

frostbite2D::Vec2 CameraController::resolveDesiredPosition(
    const frostbite2D::Vec2& currentPosition,
    const frostbite2D::Rect& targetBounds) const {
  frostbite2D::Vec2 desired = currentPosition;
  const float targetCenterX = targetBounds.left() + targetBounds.width() * 0.5f;
  const float screenX = targetCenterX - currentPosition.x;
  if (screenX < config_.deadZone.left()) {
    desired.x = targetCenterX - config_.deadZone.left();
  } else if (screenX > config_.deadZone.right()) {
    desired.x = targetCenterX - config_.deadZone.right();
  }
  desired.y = config_.worldBounds.top();
  return desired;
}

frostbite2D::Vec2 CameraController::clampToWorld(
    const frostbite2D::Vec2& position) const {
  return frostbite2D::Vec2(
      clampValue(position.x, config_.worldBounds.left(),
                 config_.worldBounds.right() - config_.viewportWidth),
      clampValue(position.y, config_.worldBounds.top(),
                 config_.worldBounds.bottom() - config_.viewportHeight));
}

} // namespace ns_game
