#include <catch2/catch_test_macros.hpp>
#include "dbc.hpp"
#include "ai.hpp"
#include <iostream>
#include "ai_debug.hpp"
#include "rituals.hpp"

using namespace dbc;
using namespace nlohmann;

TEST_CASE("state and actions work", "[ai]") {
  enum StateNames {
    ENEMY_IN_RANGE,
    ENEMY_DEAD
  };

  ai::State goal;
  ai::State start;
  std::vector<ai::Action> actions;

  // start off enemy not dead and not in range
  start[ENEMY_DEAD] = false;
  start[ENEMY_IN_RANGE] = false;

  // end goal is enemy is dead
  goal[ENEMY_DEAD] = true;

  ai::Action move_closer("move_closer", 10);
  move_closer.needs(ENEMY_IN_RANGE, false);
  move_closer.effect(ENEMY_IN_RANGE, true);

  REQUIRE(move_closer.can_effect(start));
  auto after_move_state = move_closer.apply_effect(start);
  REQUIRE(start[ENEMY_IN_RANGE] == false);
  REQUIRE(after_move_state[ENEMY_IN_RANGE] == true);
  REQUIRE(after_move_state[ENEMY_DEAD] == false);
  // start is clean but after move is dirty
  REQUIRE(move_closer.can_effect(start));
  REQUIRE(!move_closer.can_effect(after_move_state));
  REQUIRE(ai::distance_to_goal(start, after_move_state) == 1);

  ai::Action kill_it("kill_it", 10);
  kill_it.needs(ENEMY_IN_RANGE, true);
  kill_it.needs(ENEMY_DEAD, false);
  kill_it.effect(ENEMY_DEAD, true);

  REQUIRE(!kill_it.can_effect(start));
  REQUIRE(kill_it.can_effect(after_move_state));

  auto after_kill_state = kill_it.apply_effect(after_move_state);
  REQUIRE(!kill_it.can_effect(after_kill_state));
  REQUIRE(ai::distance_to_goal(after_move_state, after_kill_state) == 1);

  kill_it.ignore(ENEMY_IN_RANGE);
  REQUIRE(kill_it.can_effect(after_move_state));

  actions.push_back(kill_it);
  actions.push_back(move_closer);

  REQUIRE(start != goal);
}

TEST_CASE("basic feature tests", "[ai]") {
  enum StateNames {
    ENEMY_IN_RANGE,
    ENEMY_DEAD
  };

  ai::State goal;
  ai::State start;
  std::vector<ai::Action> actions;

  // start off enemy not dead and not in range
  start[ENEMY_DEAD] = false;
  start[ENEMY_IN_RANGE] = false;

  // end goal is enemy is dead
  goal[ENEMY_DEAD] = true;

  ai::Action move_closer("move_closer", 10);
  move_closer.needs(ENEMY_IN_RANGE, false);
  move_closer.effect(ENEMY_IN_RANGE, true);

  ai::Action kill_it("kill_it", 10);
  kill_it.needs(ENEMY_IN_RANGE, true);
  // this is duplicated on purpose to confirm that setting
  // a positive then a negative properly cancels out
  kill_it.needs(ENEMY_DEAD, true);
  kill_it.needs(ENEMY_DEAD, false);

  // same thing with effects
  kill_it.effect(ENEMY_DEAD, false);
  kill_it.effect(ENEMY_DEAD, true);

  // order seems to matter which is wrong
  actions.push_back(kill_it);
  actions.push_back(move_closer);

  auto result = ai::plan_actions(actions, start, goal);
  REQUIRE(result.complete);

  auto state = start;

  for(auto& action : result.script) {
    state = action.apply_effect(state);
  }

  REQUIRE(state[ENEMY_DEAD]);
}


TEST_CASE("ai as a module like sound/sprites", "[ai]") {
  ai::reset();
  ai::init("tests/ai_fixture.json");

  auto start = ai::load_state("test_start");
  auto goal = ai::load_state("test_goal");

  auto a_plan = ai::plan("test1", start, goal);
  REQUIRE(a_plan.complete);

  auto state = start;
  for(auto& action : a_plan.script) {
    fmt::println("ACTION: {}", action.name);
    state = action.apply_effect(state);
  }

  REQUIRE(ai::test(state, "target_dead"));
}

TEST_CASE("ai autowalker ai test", "[ai]") {
  ai::reset();
  ai::init("assets/ai.json");
  auto start = ai::load_state("Host::initial_state");
  auto goal = ai::load_state("Host::final_state");
  int enemy_count = 5;

  ai::set(start, "no_more_enemies", enemy_count == 0);

  // find an enemy and kill them
  auto a_plan = ai::plan("Host::actions", start, goal);
  REQUIRE(!a_plan.complete);

  auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script);
  REQUIRE(ai::test(result, "enemy_found"));
  REQUIRE(!ai::test(result, "no_more_enemies"));

  // health is low, go heal
  ai::set(result, "health_good", false);
  ai::set(result, "in_combat", false);
  ai::set(result, "enemy_found", false);
  ai::set(result, "have_healing", true);
  ai::set(result, "have_item", true);
  REQUIRE(!ai::test(result, "health_good"));

  auto health_plan = ai::plan("Host::actions", result, goal);
  result = ai::dump_script("\n\nWALKER NEED HEALTH", result, health_plan.script);
  REQUIRE(!health_plan.complete);
  REQUIRE(ai::test(result, "health_good"));

  // health is good, enemies dead, go get stuff
  ai::set(result, "no_more_enemies", true);
  REQUIRE(ai::test(result, "no_more_enemies"));

  auto new_plan = ai::plan("Host::actions", result, goal);
  result = ai::dump_script("\n\nWALKER COLLECT ITEMS", result, new_plan.script);
  REQUIRE(ai::test(result, "no_more_items"));
  REQUIRE(ai::test(result, "no_more_enemies"));
}

TEST_CASE("Confirm EntityAI behaves as expected", "[ai]") {
  ai::reset();
  ai::init("assets/ai.json");
  auto ai_start = ai::load_state("Enemy::initial_state");
  auto ai_goal = ai::load_state("Enemy::final_state");

  ai::EntityAI enemy("Enemy::actions", ai_start, ai_goal);

  enemy.set_state("detect_enemy", true);
  enemy.update();
  REQUIRE(enemy.wants_to("find_enemy"));

  enemy.set_state("enemy_found", true);
  enemy.set_state("in_combat", true);
  enemy.update();
  REQUIRE(enemy.wants_to("kill_enemy"));

  enemy.set_state("have_item", true);
  enemy.set_state("have_healing", true);
  enemy.set_state("in_combat", false);
  enemy.set_state("health_good", false);
  enemy.update();
  REQUIRE(enemy.wants_to("use_healing"));

  enemy.set_state("have_healing", false);
  enemy.set_state("tough_personality", true);
  enemy.set_state("in_combat", true);
  enemy.set_state("health_good", true);
  enemy.update();
  REQUIRE(enemy.wants_to("kill_enemy"));

  fmt::println("\n\n\n\n=============================\n\n\n\n");
  enemy.set_state("have_healing", false);
  enemy.set_state("tough_personality", false);
  enemy.set_state("in_combat", true);
  enemy.set_state("health_good", false);
  enemy.update();
  REQUIRE(enemy.wants_to("run_away"));
}
