#include "world/GameTown.h"
#include "character/CharacterObject.h"
#include <SDL2/SDL.h>
#include <algorithm>
#include <cmath>

namespace frostbite2D {

namespace {

Vec2 RoundWorldPoint(const Vec2& pos) {
  return Vec2(static_cast<float>(std::lround(pos.x)),
              static_cast<float>(std::lround(pos.y)));
}

} // namespace

GameTown::GameTown() {
  EnableEventReceive();
}

bool GameTown::Init(int index, const std::string& townPath) {
  game::TownConfig config;
  if (!game::loadTownConfig(townPath, config)) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GameTown: failed to init town %d (%s)",
                 index, townPath.c_str());
    return false;
  }

  name_ = config.townName;
  enteringTitle_ = config.enteringTitle;
  enteringCutscene_ = config.enteringCutscene;
  needQuestId_ = config.needQuestId;
  needLevel_ = config.needLevel;
  mapList_.clear();
  curMapIndex_ = -1;
  sariaRoomId_ = -1;
  sariaRoomPos_ = Vec2(-1.0f, -1.0f);

  for (const auto& [areaId, areaConfig] : config.areas) {
    auto map = MakePtr<GameMap>();
    if (!map->LoadMap(areaConfig.mapPath)) {
      continue;
    }

    MapInfo info;
    info.areaId = areaId;
    info.map = map;
    info.type = areaConfig.mapType;
    info.generatePos = areaConfig.generatePos;
    mapList_.push_back(info);

    if (info.type == "gate") {
      sariaRoomId_ = areaId;
      sariaRoomPos_ = areaConfig.generatePos;
    }
  }

  std::sort(mapList_.begin(), mapList_.end(),
            [](const MapInfo& a, const MapInfo& b) { return a.areaId < b.areaId; });
  return !mapList_.empty();
}

RefPtr<GameMap> GameTown::GetArea(int index) const {
  auto it = std::find_if(mapList_.begin(), mapList_.end(),
                         [index](const MapInfo& info) { return info.areaId == index; });
  if (it == mapList_.end()) {
    return nullptr;
  }
  return it->map;
}

RefPtr<GameMap> GameTown::GetCurrentArea() const {
  if (curMapIndex_ == -1) {
    return nullptr;
  }

  return GetArea(curMapIndex_);
}

void GameTown::AddCharacter(RefPtr<Actor> actor, int areaIndex) {
  int targetMapIndex = areaIndex;
  if (areaIndex == -2) {
    if (sariaRoomId_ != -1) {
      targetMapIndex = sariaRoomId_;
    } else {
      targetMapIndex = mapList_.empty() ? -1 : mapList_.front().areaId;
      SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                  "GameTown: no gate area configured, fallback to first area %d",
                  targetMapIndex);
    }
  }

  auto mapIt = std::find_if(mapList_.begin(), mapList_.end(),
                            [targetMapIndex](const MapInfo& info) {
                              return info.areaId == targetMapIndex;
                            });
  if (mapIt == mapList_.end()) {
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "GameTown: target area %d not found, fallback to first map", targetMapIndex);
    if (mapList_.empty()) {
      return;
    }
    mapIt = mapList_.begin();
  }

  if (curMapIndex_ != -1) {
    auto oldMap = GetArea(curMapIndex_);
    if (oldMap) {
      RemoveChild(oldMap);
    }
  }

  if (actor && actor->GetParent()) {
    actor->GetParent()->RemoveChild(actor);
  }

  AddChild(mapIt->map);
  cameraController_.SetMap(mapIt->map.Get());
  cameraController_.SetTarget(actor.Get());
  cameraController_.SnapToDefaultFocus();

  if (actor) {
    cameraController_.SetDebugEnabled(false);
    if (areaIndex == -2 && sariaRoomPos_.x >= 0.0f && sariaRoomPos_.y >= 0.0f) {
      Vec2 spawnPos = RoundWorldPoint(sariaRoomPos_);
      if (auto* character = dynamic_cast<CharacterObject*>(actor.Get())) {
        character->SetCharacterPosition(spawnPos);
      } else {
        actor->SetPosition(spawnPos);
      }
    }
    mapIt->map->AddObject(actor);
    cameraController_.SetFocus(actor->GetPosition());
  } else {
    cameraController_.SetDebugEnabled(true);
  }

  mapIt->map->Enter();

  curMapIndex_ = mapIt->areaId;
}

void GameTown::Update(float deltaTime) {
  Actor::Update(deltaTime);
  cameraController_.Update(deltaTime);
}

} // namespace frostbite2D
