#include "rituals.hpp"
#include "ai_debug.hpp"
#include "ai.hpp"

namespace ritual {
  Engine::Engine(std::string config_path) :
    $config(config_path)
  {
    $profile = $config["profile"];

    auto& actions = $config["actions"];

    for(auto& ac : actions) {
      auto action = ai::config_action($profile, ac);
      $actions.insert_or_assign(action.name, action);
    }

    for(auto& [name, sc] : $config["states"].items()) {
      auto state = ai::config_state($profile, sc);
      $states.insert_or_assign(name, state);
    }

    auto& scripts = $config["scripts"];
    for(auto& [script_name, action_names] : scripts.items()) {
      std::vector<ai::Action> the_script;
      for(auto name : action_names) {
        the_script.push_back($actions.at(name));
      }

      $scripts.insert_or_assign(script_name, the_script);
    }
  }

  ai::State Engine::load_state(std::string name) {
    return $states.at(name);
  }

  void Engine::load_junk(CraftingState& ritual, const JunkItem& item) {
    Config config("assets/rituals.json");
    auto& junk = config["junk"];
    auto& item_desc = junk[item];

    fmt::print("Item {} provides: ", item);
    for(auto& effect : item_desc["provides"]) {
      fmt::print("{} ", (std::string)effect);
      set_state(ritual, effect, true);
    }
    fmt::print("\n");
  }

  ai::Action Engine::load_action(std::string name) {
    return $actions.at(name);
  }

  CraftingState Engine::start() {
    auto start = load_state("initial");
    auto goal = load_state("final");
    return {"actions", start, goal};
  }

  void Engine::set_state(CraftingState& ritual, std::string name, bool setting) {
    dbc::check($profile.contains(name),
        fmt::format("ritual action named {} is not in profile, look in {} config",
          name, $config.$src_path));

    ritual.start.set($profile.at(name), setting);
  }

  void CraftingState::reset() {
    start = original;
    plan.complete = false;
    plan.script.clear();
  }

  void Engine::plan(CraftingState& ritual) {
    ritual.plan = ai::plan_actions($scripts.at(ritual.script), ritual.start, ritual.goal);
  }

  bool CraftingState::will_do(std::string name) {
    if(plan.script.size() == 0) return false;

    return plan.script[0].name == name;
  }

  ai::Action CraftingState::pop() {
    auto result = plan.script.front();
    plan.script.pop_front();
    return result;
  }

  // BUG: maybe this should be called CraftingState instead?
  void CraftingState::dump() {
    ai::dump_script(script, start, plan.script);
  }

  bool CraftingState::is_combined() {
    // it's only combined if it has > 1 and last is combined
    if(plan.script.size() <= 1) return false;

    auto& last = plan.script.back();
    return last.name == "combined";
  }

  Action Engine::finalize(CraftingState& ritual) {
    (void)ritual;

    Action result;
    auto effects = $config["effects"];

    for(auto action : ritual.plan.script) {
      if(effects.contains(action.name)) {
        auto& effect = effects[action.name];
        result.damage += int(effect["damage"]);
        result.probability *= float(effect["probability"]);
        if(effect.contains("kind")) result.kind = Kind(int(effect["kind"]));
        if(effect.contains("element")) result.element = Element(int(effect["element"]));
      }
    }

    return result;
  }

  void Action::dump() {
    fmt::print("ritual has damage {}, prob: {}, kind: {}, element: {}; named: ",
      damage, probability, int(kind), int(element));

    for(auto& name : names) {
      fmt::print("{} ", name);
    }

    fmt::println("\n");
  }

  Action& Belt::get(int index) {
    return equipped.at(index);
  }

  void Belt::equip(int index, Action& action) {
    equipped.insert_or_assign(index, action);
  }

  bool Belt::has(int index) {
    return equipped.contains(index);
  }

  void Belt::unequip(int index) {
    equipped.erase(index);
  }

  int Belt::next() {
    int slot = next_slot % max_slots;
    next_slot++;
    return slot;
  }

  DinkyECS::Entity Blanket::add(JunkItem name) {
    DinkyECS::Entity id = ++entity_counter;

    contents.insert_or_assign(id, name);

    return id;
  }

  std::string& Blanket::get(DinkyECS::Entity ent) {
    return contents.at(ent);
  }

  bool Blanket::has(DinkyECS::Entity ent) {
    return contents.contains(ent);
  }

  void Blanket::remove(DinkyECS::Entity ent) {
    contents.erase(ent);
  }

  void Blanket::select(DinkyECS::Entity ent) {
    selected.insert_or_assign(ent, true);
  }

  void Blanket::deselect(DinkyECS::Entity ent) {
    selected.erase(ent);
  }

  bool Blanket::is_selected(DinkyECS::Entity ent) {
    return selected.contains(ent) && selected.at(ent);
  }

  void Blanket::reset() {
    selected.clear();
  }

  bool Blanket::no_selections() {
    return selected.size() == 0;
  }

  void Blanket::consume_crafting() {
    for(auto [item_id, setting] : selected) {
      contents.erase(item_id);
    }
  }
}