#ifndef CHARACTERAIMODULE_H
#define CHARACTERAIMODULE_H
#include <vector>
#include <flecs.h>
#include <nlohmann/json.hpp>
#include "goap.h"

namespace ECS
{

struct TownAI;

struct Blackboard {
    struct UpdateBit {
        Ogre::String checkValue;
        int recover;
        int minValue;
        int maxValue;
        int threshold;
        Ogre::String writeValue;
    };
    int object;
    int index;
    flecs::entity town;
    std::shared_ptr<std::mutex> mutex;
    static std::unordered_map<std::string, size_t> mapping;

private:
    // nlohmann::json stats;
    std::bitset<64> bits, mask;
    std::vector<goap::BaseAction<Blackboard> *> actionRef;
    int actionRefCount;
    int actionRefPtr;
    Ogre::Vector3 position;
    std::vector<size_t> points;
    std::vector<float> distances;
    void _actionRefResize(int count);
    void _actionRefAddActions(
        const std::vector<goap::BaseAction<Blackboard> *> &actions);
    void populate(const nlohmann::json &stats, std::unordered_map<std::string, size_t>& mapping);

public:
    Blackboard();
    Blackboard(const nlohmann::json &stats);
    bool operator==(const Blackboard &other) const;
    bool operator!=(const Blackboard &other) const;
    void apply(const Blackboard &other);
    Ogre::String dumpActions();
    void printActions();
    void actionRefResize(int count);
    void actionRefAddAction(goap::BaseAction<Blackboard> *action);
    void actionRefAddActions(
        const std::vector<goap::BaseAction<Blackboard> *> &actions);
    void actionRefAddActions(goap::BaseAction<Blackboard> **actions,
                 int count);
    const goap::BaseAction<Blackboard> *const *getActionsData() const
    {
        return actionRef.data();
    }
    goap::BaseAction<Blackboard> **getActionsData()
    {
        return actionRef.data();
    }
    int getActionsCount() const
    {
        return actionRefCount;
    }
#if 0
    void commit()
    {
        populate(stats, mapping);
    }
#endif
    void dump_bits() const
    {
        std::cout << "bits: " << bits << std::endl;
        std::cout << "mask: " << mask << std::endl;
    }
    bool is_valid() const
    {
        return /* !stats.is_null() && */ mask != 0;
    }
    void updateBits(const TownAI &ai, const nlohmann::json &memory, const nlohmann::json &props);

private:
    static bool is_satisfied_by(const nlohmann::json &current,
                    const nlohmann::json &target,
                    float epsilon = 1e-4);

public:
    int distance_to(const Blackboard &goal) const;
    void setPosition(const Ogre::Vector3 &position);
    const Ogre::Vector3 &getPosition() const
    {
        return position;
    }
    void query_ai();
};

struct TownNPCs;
struct ActionNodeList;

struct TownAI {
    std::shared_ptr<std::mutex> mutex;
    std::vector<goap::BasePlanner<Blackboard,
                      goap::BaseAction<Blackboard> >::BaseGoal>
        goals;
    std::vector<goap::BaseAction<Blackboard> *> actions;
    std::shared_ptr<
        goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> > >
        planner;
    std::unordered_map<int, Blackboard> blackboards;
    typedef goap::BasePlanner<
        Blackboard, goap::BaseAction<Blackboard> >::BaseGoal goal_t;
    typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> >
        planner_t;
    struct Plan {
        const goal_t *goal;
        std::vector<goap::BaseAction<Blackboard> *> plan;
    };
    std::unordered_map<int, std::vector<struct Plan> > plans;
    std::unordered_map<int, std::vector<goap::BaseAction<Blackboard> *> >
        nodeActions;
    struct Condition {
        std::string key;
        std::function<bool(const nlohmann::json &data)> condition;
    };
    struct MemoryUpdate {
        std::string name;
        std::function<int(int index, const std::string &name, int value)>
            update;
    };

    struct BitEvolutionBuilder {
        std::vector<Condition> conditions;
        BitEvolutionBuilder *
        addRule(const std::string &key,
            std::function<bool(const nlohmann::json &data)>
                condition)
        {
            conditions.push_back({ key, condition });
            return this;
        }
        std::vector<Condition> build()
        {
            return conditions;
        }
    };
    struct MemoryUpdateBuilder {
        std::vector<MemoryUpdate> memoryUpdates;
        MemoryUpdateBuilder &
        addUpdate(const std::string &name,
              std::function<int(int index, const std::string &name,
                        int value)>
                  update)
        {
            memoryUpdates.push_back({ name, update });
            return *this;
        }
        std::vector<MemoryUpdate> build()
        {
            return memoryUpdates;
        }
    };

    std::vector<Condition> conditions;
    std::unordered_map<int, nlohmann::json> memory;
    std::vector<MemoryUpdate> memoryUpdates;
};

struct CharacterAIModule {
    CharacterAIModule(flecs::world &ecs);
    void createAI(flecs::entity town);
    void buildPlans(flecs::entity town, const TownNPCs &npcs, TownAI &ai);
    void createBlackboards(flecs::entity town, const TownNPCs &npcs, TownAI &ai);
    void updateBlackboards(flecs::entity town, ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
};

}

#endif // CHARACTERAIMODULE_H
