#include "levelmanager.hpp"
#include "worldbuilder.hpp"
#include "constants.hpp"
#include "save.hpp"
#include "systems.hpp"
#include "components.hpp"
#include "rituals.hpp"

using lighting::LightRender;
using std::shared_ptr, std::make_shared;
using namespace components;

LevelManager::LevelManager() {
  components::configure($components);
  create_level();
}

LevelScaling LevelManager::scale_level() {
  return {
    20 + (5 * int($current_level)),
    15 + (5 * int($current_level))
  };
}


inline shared_ptr<DinkyECS::World> clone_load_world(shared_ptr<DinkyECS::World> prev_world)
{
  auto world = make_shared<DinkyECS::World>();

  if(prev_world != nullptr) {
    prev_world->clone_into(*world);
  } else {
    save::load_configs(*world);
  }

  return world;
}

shared_ptr<gui::BossFightUI> LevelManager::create_bossfight(shared_ptr<DinkyECS::World> prev_world) {
  dbc::check(prev_world != nullptr, "Starter world for boss fights can't be null.");
  auto world = clone_load_world(prev_world);
  auto& config = prev_world->get_the<GameConfig>();

  // BUG: the jank is too strong here
  auto boss_names = config.bosses.keys();
  auto& level_name = boss_names[$current_level % boss_names.size()];
  auto& boss_data = config.bosses[level_name];

  auto boss_id = world->entity();
  components::configure_entity($components, *world, boss_id, boss_data["components"]);

  return make_shared<gui::BossFightUI>(world, boss_id);
}

DinkyECS::Entity LevelManager::spawn_enemy(std::string named) {
  (void)named;
  auto& level = current();

  auto &config = level.world->get_the<GameConfig>();
  auto entity_data = config.enemies[named];

  WorldBuilder builder(*level.map, $components);

  auto entity_id = builder.configure_entity_in_map(*level.world, entity_data, 0);

  auto& entity_pos = level.world->get<Position>(entity_id);
  auto player_pos = level.world->get<Position>(level.player);

  for(matrix::box it{level.map->walls(),
      player_pos.location.x, player_pos.location.y, 1}; it.next();)
  {
    if(level.map->can_move({it.x, it.y})) {
      // this is where we move it closer to the player
      entity_pos.location.x = it.x;
      entity_pos.location.y = it.y;
      break;
    }
  }

  level.collision->insert(entity_pos.location, entity_id);

  return entity_id;
}

size_t LevelManager::create_level(shared_ptr<DinkyECS::World> prev_world) {
  auto world = clone_load_world(prev_world);
  auto scaling = scale_level();

  auto map = make_shared<Map>(scaling.map_width, scaling.map_height);
  WorldBuilder builder(*map, $components);
  builder.generate(*world);

  size_t index = $levels.size();

  auto collider = make_shared<SpatialMap>();
  // not sure if this is still needed
  System::init_positions(*world, *collider);

  auto player = world->get_the<Player>();

  $levels.emplace_back(index, player.entity, map, world,
      make_shared<LightRender>(map->width(), map->height()),
      collider);

  dbc::check(index == $levels.size() - 1, "Level index is not the same as $levels.size() - 1, off by one error");
  return index;
}

GameLevel &LevelManager::next() {
  dbc::check($current_level < $levels.size(), "attempt to get next level when at end");
  $current_level++;
  return $levels.at($current_level);
}

GameLevel &LevelManager::previous() {
  dbc::check($current_level > 0, "attempt to go to previous level when at 0");
  $current_level--;
  return $levels.at($current_level);
}

GameLevel &LevelManager::current() {
  return $levels.at($current_level);
}

GameLevel &LevelManager::get(size_t index) {
  return $levels.at(index);
}
