#include "whitebox_scene.h"

#include "../core/game_config.h"

#include <frostbite2D/core/application.h>
#include <frostbite2D/event/key_event.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h>
#include <algorithm>

namespace ns_game {

void WhiteboxScene::onEnter() {
  frostbite2D::Scene::onEnter();
  level_ = CreateWhiteboxLevel();
  battleZoneStates_.clear();
  battleZoneStates_.resize(level_.battleZones.size());
  activeBattleZoneIndex_ = -1;

  player_ = frostbite2D::MakePtr<PlayerActor>();
  player_->SetTopLeftPosition(level_.playerSpawn);
  player_->SetMovementWorld(&level_.collision);
  AddChild(player_);
  configureCamera();
}

void WhiteboxScene::Update(float deltaTime) {
  frostbite2D::Scene::Update(deltaTime);
  updateBattleZones();
  updateCameraBoundsBlend(deltaTime);
  updateCamera(deltaTime);
}

bool WhiteboxScene::OnEvent(const frostbite2D::Event& event) {
  if (event.getType() == frostbite2D::EventType::KeyDown) {
    const auto& keyEvent = static_cast<const frostbite2D::KeyEvent&>(event);
    if (!keyEvent.isRepeat() && handleDebugKey(keyEvent.getKeyCode())) {
      return true;
    }
  }

  return frostbite2D::Scene::OnEvent(event);
}

void WhiteboxScene::Render() {
  stageRenderer_.Render(level_, debugFlags_);
  frostbite2D::Scene::Render();
  stageRenderer_.RenderForeground(level_);
  const frostbite2D::Rect playerBounds =
      player_ ? player_->GetWorldBounds() : frostbite2D::Rect::Zero();
  const std::vector<BattleZoneDebugInfo> battleZoneDebug =
      buildBattleZoneDebugInfo();
  debugOverlay_.Render(level_, debugFlags_, player_ ? &playerBounds : nullptr,
                       &battleZoneDebug);
}

frostbite2D::Camera* WhiteboxScene::activeCamera() const {
  auto* renderer = frostbite2D::Application::get().getRenderer();
  if (!renderer) {
    return nullptr;
  }
  return renderer->getCamera();
}

std::vector<BattleZoneDebugInfo> WhiteboxScene::buildBattleZoneDebugInfo()
    const {
  std::vector<BattleZoneDebugInfo> debugInfo;
  debugInfo.reserve(battleZoneStates_.size());
  for (size_t i = 0; i < battleZoneStates_.size(); ++i) {
    BattleZoneDebugState state = BattleZoneDebugState::Idle;
    if (battleZoneStates_[i].cleared) {
      state = BattleZoneDebugState::Cleared;
    } else if (activeBattleZoneIndex_ == static_cast<int>(i)) {
      state = BattleZoneDebugState::Active;
    }
    debugInfo.push_back(BattleZoneDebugInfo{i, state});
  }
  return debugInfo;
}

void WhiteboxScene::configureCamera() {
  auto* camera = activeCamera();
  if (!camera) {
    return;
  }

  CameraControllerConfig config;
  config.worldBounds = level_.cameraBounds;
  config.viewportWidth = static_cast<float>(config::kVirtualWidth);
  config.viewportHeight = static_cast<float>(config::kVirtualHeight);
  cameraController_.Configure(config);
  currentCameraBounds_ = level_.cameraBounds;

  if (player_) {
    cameraController_.SnapToTarget(*camera, player_->GetWorldBounds());
  }
}

bool WhiteboxScene::handleDebugKey(frostbite2D::KeyCode keyCode) {
  switch (keyCode) {
    case frostbite2D::KeyCode::F1:
      toggleDebugOverlay();
      return true;
    case frostbite2D::KeyCode::F2:
      clearActiveBattleZone();
      return true;
    case frostbite2D::KeyCode::F3:
      debugFlags_.showCollision = !debugFlags_.showCollision;
      debugFlags_.showStageGrid = debugFlags_.showCollision;
      return true;
    case frostbite2D::KeyCode::F4:
      debugFlags_.showActorBounds = !debugFlags_.showActorBounds;
      return true;
    case frostbite2D::KeyCode::F5:
      debugFlags_.showBattleZones = !debugFlags_.showBattleZones;
      return true;
    case frostbite2D::KeyCode::F6:
      debugFlags_.showSpawnPoints = !debugFlags_.showSpawnPoints;
      return true;
    default:
      return false;
  }
}

void WhiteboxScene::toggleDebugOverlay() {
  debugFlags_.enabled = !debugFlags_.enabled;
}

void WhiteboxScene::beginCameraBoundsBlend(
    const frostbite2D::Rect& targetBounds) {
  cameraBoundsBlendStart_ = currentCameraBounds_;
  cameraBoundsBlendTarget_ = targetBounds;
  cameraBoundsBlendTimer_ = 0.0f;
  cameraBoundsBlendActive_ = true;
}

void WhiteboxScene::clearActiveBattleZone() {
  if (activeBattleZoneIndex_ < 0 ||
      activeBattleZoneIndex_ >= static_cast<int>(battleZoneStates_.size())) {
    return;
  }

  battleZoneStates_[static_cast<size_t>(activeBattleZoneIndex_)].cleared = true;
  activeBattleZoneIndex_ = -1;
  beginCameraBoundsBlend(level_.cameraBounds);
}

void WhiteboxScene::updateCameraBoundsBlend(float deltaTime) {
  if (!cameraBoundsBlendActive_) {
    return;
  }

  cameraBoundsBlendTimer_ += deltaTime;
  const float t = cameraBoundsBlendDuration_ > 0.0f
                      ? std::min(cameraBoundsBlendTimer_ /
                                     cameraBoundsBlendDuration_,
                                 1.0f)
                      : 1.0f;
  const auto lerp = [t](float from, float to) {
    return from + (to - from) * t;
  };

  currentCameraBounds_ = frostbite2D::Rect(
      lerp(cameraBoundsBlendStart_.left(), cameraBoundsBlendTarget_.left()),
      lerp(cameraBoundsBlendStart_.top(), cameraBoundsBlendTarget_.top()),
      lerp(cameraBoundsBlendStart_.width(), cameraBoundsBlendTarget_.width()),
      lerp(cameraBoundsBlendStart_.height(), cameraBoundsBlendTarget_.height()));
  cameraController_.SetWorldBounds(currentCameraBounds_);

  if (t >= 1.0f) {
    cameraBoundsBlendActive_ = false;
  }
}

void WhiteboxScene::updateBattleZones() {
  if (!player_) {
    return;
  }

  const frostbite2D::Rect playerBounds = player_->GetWorldBounds();
  if (activeBattleZoneIndex_ >= 0) {
    const size_t activeIndex = static_cast<size_t>(activeBattleZoneIndex_);
    if (activeIndex < level_.battleZones.size() &&
        !level_.battleZones[activeIndex].trigger.intersects(playerBounds)) {
      clearActiveBattleZone();
    }
    return;
  }

  for (size_t i = 0; i < level_.battleZones.size(); ++i) {
    BattleZoneRuntime& runtime = battleZoneStates_[i];
    if (runtime.cleared ||
        !level_.battleZones[i].trigger.intersects(playerBounds)) {
      continue;
    }

    runtime.triggered = true;
    activeBattleZoneIndex_ = static_cast<int>(i);
    beginCameraBoundsBlend(level_.battleZones[i].cameraBounds);
    break;
  }
}

void WhiteboxScene::updateCamera(float deltaTime) {
  if (!player_) {
    return;
  }

  auto* camera = activeCamera();
  if (!camera) {
    return;
  }

  cameraController_.Update(*camera, player_->GetWorldBounds(), deltaTime);
}

} // namespace ns_game
