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

namespace combat {
  RitualEngine::RitualEngine(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 RitualEngine::load_state(std::string name) {
    return $states.at(name);
  }

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

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

  void RitualEngine::set_state(RitualAI& 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 RitualEngine::reset(RitualAI& ritual) {
    ritual.start = ritual.original;
  }

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

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

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

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

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

  bool RitualAI::is_combined() {
    dbc::check(!plan.script.empty(), "you are attempting to check an empty plan");
    auto& last = plan.script.back();
    return plan.script.size() > 1 && last.name == "combined";
  }

  RitualAction RitualEngine::finalize(RitualAI& ritual) {
    (void)ritual;

    RitualAction 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 = RitualKind(int(effect["kind"]));
        if(effect.contains("element")) result.element = RitualElement(int(effect["element"]));
      }
    }

    return result;
  }

  void RitualAction::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");
  }

  RitualAction& RitualBelt::get(int index) {
    return equipped.at(index);
  }

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

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

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