#pragma once
#include <vector>
#include "matrix.hpp"
#include <bitset>
#include <limits>
#include <optional>
#include <nlohmann/json.hpp>
#include "config.hpp"

namespace ai {
  // ZED: I don't know if this is the best place for this
  using AIProfile = std::unordered_map<std::string, int>;

  constexpr const int SCORE_MAX = std::numeric_limits<int>::max();

  constexpr const size_t STATE_MAX = 32;

  using State = std::bitset<STATE_MAX>;

  const State ALL_ZERO;
  const State ALL_ONES = ~ALL_ZERO;

  struct Action {
    std::string name;
    int cost = 0;

    State $positive_preconds;
    State $negative_preconds;

    State $positive_effects;
    State $negative_effects;

    Action() {}

    Action(std::string name, int cost) :
      name(name), cost(cost) { }

    void needs(int name, bool val);
    void effect(int name, bool val);
    void ignore(int name);

    bool can_effect(State& state);
    State apply_effect(State& state);

    bool operator==(const Action& other) const {
      return other.name == name;
    }
  };

  using Script = std::deque<Action>;

  const Action FINAL_ACTION("END", SCORE_MAX);

  struct ActionState {
    Action action;
    State state;

    ActionState(Action action, State state) :
      action(action), state(state) {}

    bool operator==(const ActionState& other) const {
      return other.action == action && other.state == state;
    }
  };

  struct ActionPlan {
    bool complete = false;
    Script script;
  };

  bool is_subset(State& source, State& target);

  int distance_to_goal(State from, State to);

  ActionPlan plan_actions(std::vector<Action>& actions, State start, State goal);
}

template<> struct std::hash<ai::Action> {
  size_t operator()(const ai::Action& p) const {
    return std::hash<std::string>{}(p.name);
  }
};

template<> struct std::hash<ai::ActionState> {
  size_t operator()(const ai::ActionState& p) const {
    return std::hash<ai::Action>{}(p.action) ^ std::hash<ai::State>{}(p.state);
  }
};