mod action;
mod condition;
mod cost;
mod effect;
mod errors;
mod fact;
mod goal;
mod plan;
mod planner;
pub mod prelude;
#[cfg(feature = "simple")]
mod simple;
mod state;
mod value;

pub use action::Action;
pub use condition::{Condition, conditions_met};
pub use cost::Cost;
pub use effect::Effect;
pub use errors::PlannerError;
pub use fact::FactId;
pub use goal::Goal;
pub use plan::Plan;
pub use planner::Planner;
#[cfg(feature = "simple")]
pub use simple::{
	brain::Brain,
	factmap::FactMap,
	factmap::hashfactmap::HashFactMap,
	world::{WORLD_FACTS, WORLD_STATE},
};
pub use state::State;
pub use value::Value;

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn condition_and_effects_work() {
		let health = FactId(0);

		let start = State::new(0).with_fact(health, Value::Int(10));

		let healed = start.with_effects(&[Effect::Add(health, Value::Int(5))]);

		assert!(healed.get(health).eq(&Value::Int(15), &start));

		assert!(Condition::Gt(health, Value::Int(10)).is_satisfied(&healed));
	}

	#[test]
	fn simple_goap_plan() {
		// Facts (...but consider using a FactMap!)
		let has_axe = FactId(0);
		let has_wood = FactId(1);

		// Initial state
		let mut start = State::new(0);
		start.set(has_axe, Value::FALSE);
		start.set(has_wood, Value::FALSE);

		// Actions
		let get_axe = Action {
			name: "Get Axe".into(),
			cost: Cost(1),
			preconditions: vec![],
			effects: vec![Effect::Set(has_axe, Value::TRUE)],
			executors: vec![],
		};

		let chop_wood = Action {
			name: "Chop Wood".into(),
			cost: Cost(2),
			preconditions: vec![Condition::Eq(has_axe, Value::TRUE)],
			effects: vec![Effect::Set(has_wood, Value::TRUE)],
			executors: vec![],
		};

		// Goal
		let mut goal = Goal::new("Get wood");
		goal.push_condition(Condition::Eq(has_wood, Value::TRUE));

		// Planner
		let mut planner = Planner::new();
		planner.push_action(get_axe);
		planner.push_action(chop_wood);

		// Plan!
		let plan = planner
			.plan(&start, &goal, 8)
			.expect("no plan found within 8 actions");

		// Test typical usage:
		for index in 0..plan.len() {
			if let Some(action) = planner.get_plan_action(&plan, index) {
				println!("{}", action.name);
			}
		}

		// Verify plan
		assert_eq!(plan.len(), 2);
		assert_eq!(
			planner
				.get_plan_action(&plan, 0)
				.expect("missing action")
				.name,
			"Get Axe"
		);
		assert_eq!(
			planner
				.get_plan_action(&plan, 1)
				.expect("missing action")
				.name,
			"Chop Wood"
		);
	}

	#[test]
	fn unreachable_goal() {
		// Facts (...but consider using a FactMap!)
		let has_axe = FactId(0);
		let has_wood = FactId(1);

		// Initial state
		let mut start = State::new(0);
		start.set(has_axe, Value::FALSE);
		start.set(has_wood, Value::FALSE);

		// Actions
		let chop_wood = Action {
			name: "Chop Wood".into(),
			cost: Cost(2),
			preconditions: vec![Condition::Eq(has_axe, Value::TRUE)],
			effects: vec![Effect::Set(has_wood, Value::TRUE)],
			executors: vec![],
		};

		// Goal
		let mut goal = Goal::new("Get wood");
		goal.push_condition(Condition::Eq(has_wood, Value::TRUE));

		// Planner
		let mut planner = Planner::new();
		planner.push_action(chop_wood);

		// Plan!
		let plan_result = planner.plan(&start, &goal, 64);
		assert!(plan_result.is_err(), "plan succeeded where it should fail");
	}

	#[test]
	fn simple_goap_plan_reversed() {
		// Facts (...but consider using a FactMap!)
		let has_axe = FactId(0);
		let has_wood = FactId(1);

		// Initial state
		let mut start = State::new(0);
		start.set(has_axe, Value::FALSE);
		start.set(has_wood, Value::FALSE);

		// Actions
		let chop_wood = Action {
			name: "Chop Wood".into(),
			cost: Cost(2),
			preconditions: vec![Condition::Eq(has_axe, Value::TRUE)],
			effects: vec![Effect::Set(has_wood, Value::TRUE)],
			executors: vec![],
		};

		let get_axe = Action {
			name: "Get Axe".into(),
			cost: Cost(1),
			preconditions: vec![],
			effects: vec![Effect::Set(has_axe, Value::TRUE)],
			executors: vec![],
		};

		// Goal
		let mut goal = Goal::new("Get wood");
		goal.push_condition(Condition::Eq(has_wood, Value::TRUE));

		// Planner
		let mut planner = Planner::new();
		planner.push_action(chop_wood);
		planner.push_action(get_axe);

		// Plan!
		let plan = planner
			.plan(&start, &goal, 8)
			.expect("no plan found within 8 actions");

		// Test typical usage:
		for index in 0..plan.len() {
			if let Some(action) = planner.get_plan_action(&plan, index) {
				println!("{}", action.name);
			}
		}

		// Verify plan
		assert_eq!(plan.len(), 2);
		assert_eq!(
			planner
				.get_plan_action(&plan, 0)
				.expect("missing action")
				.name,
			"Get Axe"
		);
		assert_eq!(
			planner
				.get_plan_action(&plan, 1)
				.expect("missing action")
				.name,
			"Chop Wood"
		);
	}
}
