#include "combat/battle.hpp"

namespace combat {
  void BattleEngine::add_enemy(Combatant enemy) {
    $combatants.try_emplace(enemy.entity, enemy);

    if(enemy.is_host) {
      dbc::check($host_combat == nullptr, "added the host twice!");
      $host_combat = enemy.combat;
    }
  }

  bool BattleEngine::player_request(const std::string& request) {
    auto action = ai::load_action(request);
    bool can_go = player_pending_ap() >= action.cost;

    if(can_go) {
      $player_requests.try_emplace(request, action);
    }

    return can_go;
  }

  void BattleEngine::clear_requests() {
    $player_requests.clear();
  }

  void BattleEngine::ap_refresh() {
    for(auto& [entity, enemy] : $combatants) {
      if(enemy.combat->ap < enemy.combat->max_ap) {
        int new_ap = std::min(enemy.combat->max_ap, enemy.combat->ap_delta + enemy.combat->ap);
        enemy.combat->ap = new_ap;
      }
    }
  }

  int BattleEngine::player_pending_ap() {
    dbc::check($host_combat != nullptr, "didn't set host before checking AP");
    int pending_ap = $host_combat->ap;

    for(auto& [name, action] : $player_requests) {
      pending_ap -= action.cost;
    }

    return pending_ap;
  }

  bool BattleEngine::plan() {
    using enum BattleHostState;

    int active = 0;
    bool had_host = false;

    for(auto& [entity, enemy] : $combatants) {
      //NOTE: this is just for asserting I'm using things right
      if(enemy.is_host) had_host = true;

      enemy.ai->update();
      active += enemy.ai->active();

      if(enemy.ai->active()) {
        for(auto& action : enemy.ai->plan.script) {
          BattleHostState host_state = not_host;

          if(action.cost > enemy.combat->ap) {
            host_state = out_of_ap;
          } else if(enemy.is_host) {
            host_state = $player_requests.contains(action.name) ? agree : disagree;
          }

          if(host_state != out_of_ap) {
            enemy.combat->ap -= action.cost;
          }

          $pending_actions.emplace_back(enemy, action.name, action.cost, host_state);
        }

        dbc::check(enemy.combat->ap >= 0, "enemy's AP went below 0");
        dbc::check(enemy.combat->ap <= enemy.combat->max_ap, "enemy's AP went above max");
      }
    }

    dbc::check(had_host, "FAIL, you forgot to set enemy.is_host=true for one entity");
    if($pending_actions.size() > 0) {
      std::sort($pending_actions.begin(), $pending_actions.end(),
          [](const auto& a, const auto& b) -> bool
      {
        return a.cost > b.cost;
      });
    }

    return active > 0;
  }

  std::optional<BattleResult> BattleEngine::next() {
    if($pending_actions.size() == 0) return std::nullopt;

    auto ba = $pending_actions.back();
    $pending_actions.pop_back();
    return std::make_optional(ba);
  }

  void BattleEngine::dump() {
    for(auto& [entity, enemy] : $combatants) {
      fmt::println("\n\n###### ENTITY #{}", entity);
      enemy.ai->dump();
    }
  }

  void BattleEngine::set(DinkyECS::Entity entity, const std::string& state, bool setting) {
    dbc::check($combatants.contains(entity), "invalid combatant given to BattleEngine");
    auto& action = $combatants.at(entity);
    action.ai->set_state(state, setting);
  }

  void BattleEngine::set_all(const std::string& state, bool setting) {
    for(auto& [ent, action] : $combatants) {
      action.ai->set_state(state, setting);
    }
  }

  Combatant& BattleEngine::get_enemy(DinkyECS::Entity entity) {
    dbc::check($combatants.contains(entity), "invalid combatant given to BattleEngine");

    return $combatants.at(entity);
  }
}
