#include "game_level.hpp"
#include "components.hpp"
#include "worldbuilder.hpp"
#include "constants.hpp"
#include "systems.hpp"
#include "components.hpp"
#include "rituals.hpp"
#include "textures.hpp"

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

struct LevelScaling {
  int map_width=20;
  int map_height=20;
};

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

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

  return world;
}

namespace GameDB {
  using std::shared_ptr, std::string, std::make_shared;

  struct LevelDB {
    public:
    std::vector<GameDB::Level> levels;
    size_t current_level = 0;
  };

  shared_ptr<LevelDB> LDB;
  bool initialized = false;

  LevelScaling scale_level() {
    return {
      INITIAL_MAP_W + int(LDB->current_level * 2),
      INITIAL_MAP_H + int(LDB->current_level * 2)
    };
  }

  size_t new_level(std::shared_ptr<DinkyECS::World> prev_world) {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    auto world = clone_load_world(prev_world);

    auto scaling = scale_level();

    auto map = make_shared<Map>(scaling.map_width, scaling.map_height);
    auto collision = std::make_shared<SpatialMap>();

    WorldBuilder builder(*map, *collision);
    builder.generate(*world);

    size_t index = LDB->levels.size();

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

    LDB->levels.emplace_back(index, player.entity, map, world,
        make_shared<LightRender>(map->tiles()), collision);

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

    return index;
  }

  void init() {
    components::init();
    textures::init();

    if(!initialized) {
      LDB = make_shared<LevelDB>();
      initialized = true;
      new_level(NULL);
    }
  }

  shared_ptr<DinkyECS::World> current_world() {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    return current_level().world;
  }

  shared_ptr<boss::UI> create_bossfight() {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    auto prev_world = current_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[LDB->current_level % boss_names.size()];
    auto& boss_data = config.bosses[level_name];

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

    return make_shared<boss::UI>(world, boss_id);
  }

  Level& create_level() {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    dbc::check(LDB->current_level < LDB->levels.size(), "attempt to get next level when at end");

    size_t level = new_level(current_world());
    dbc::check(level == LDB->current_level + 1, "new level index is wrong");

    LDB->current_level++;
    return LDB->levels.at(LDB->current_level);
  }

  Level &current_level() {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    return LDB->levels.at(LDB->current_level);
  }

  components::Position& player_position() {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    auto level = current_level();
    return level.world->get<components::Position>(level.player);
  }

  DinkyECS::Entity the_player() {
    dbc::check(initialized, "Forgot to call GameDB::init()");
    return current_level().player;
  }

  void load_configs(DinkyECS::World &world) {
    world.set_the<GameConfig>({
        settings::get("config"),
        settings::get("enemies"),
        settings::get("items"),
        settings::get("tiles"),
        settings::get("devices"),
        settings::get("bosses"),
        settings::get("rituals")
    });
  }
}
