#ifndef PATHFINDING_HPP_INCLUDED
#define PATHFINDING_HPP_INCLUDED

#include "../../Core/Types/Tile.hpp"
#include <vector>
#include <functional>

/**
 * @brief A class allowing easy pathfinding within the local region
 */
class Pathfinding
{
    public:

        enum COLLISION_FLAG
        {
            OPEN            = 0,
            CLOSED          = 0xFFFFFF,
            UNINITIALIZED   = 0x1000000,
            OCCUPIED        = 0x100,
            SOLID           = 0x20000,
            BLOCKED         = 0x200000,

            NORTH           = 0x2,
            EAST            = 0x8,
            SOUTH           = 0x20,
            WEST            = 0x80,

            NORTHEAST       = 0x4,
            SOUTHEAST       = 0x10,
            SOUTHWEST       = 0x40,
            NORTHWEST       = 0x1
        };

        typedef enum PATHFINDER
        {
            /** <A HREF="https://en.wikipedia.org/wiki/Breadth-first_search">Breadth-first search algorithim</A> */
            BREADTH_FIRST_SEARCH
        } PATHFINDER;

        enum PATHFIND_OPTION
        {
            /** Checks collision */
            CHECK_COLLISION = (1 << 1),
            /** Checks whitelist */
            CHECK_WHITELIST = (1 << 2),
            /** Checks blacklist */
            CHECK_BLACKLIST = (1 << 3),
            /** The passed goal is a local tile, the function won't normalize it */
            GOAL_IS_LOCAL   = (1 << 4),
            /** Returns a path as local tiles */
            RETURN_LOCAL    = (1 << 5)
        };

        /**
         * @brief A subclass used to store information about tile nodes generated by the Pathfinding class
         */
        class TileNode
        {
            public:
                /** @brief The local region X coordinate */
                std::int32_t X;
                /**  @brief The local region Y coordinate */
                std::int32_t Y;
                /** @brief The collision flag associated with the node */
                std::int32_t Flag;
                /** @brief <B> For pathfinding use only </B> */
                bool Inspected;
                /** @brief True if this node matches the whitelisted tiles set in Pathfinding  */
                bool MatchesWhitelist;
                /** @brief True if this node matches the blacklisted tiles set in Pathfinding  */
                bool MatchesBlacklist;
                /** @brief <B> For pathfinding use only </B>  */
                std::int32_t Parent[2];
                TileNode();
                TileNode(std::int32_t X, std::int32_t Y, std::int32_t Flag);
                /** @brief True if the node is occupied or blocked according to its collision flag*/
                bool IsBlocked() const;
                /** @brief Adds ClientX/Y to the local X and Y coordinates of the node, returning a world tile  */
                Tile ToWorldTile() const;
                bool operator==(const TileNode& N) const;
                operator bool() const;
        };

        /** @brief Returns the region ID the local player is in */
        static std::int32_t GetCurrentRegion();

        /** @brief Clears the internal cached nodes, and generates new ones based on the local region  */
        static void GenerateNodes();
        /**
         * @brief Sets the internal Whitelist tiles
         * @note This will set the internal Whitelist object to the one passed
         * */
        static void SetWhitelist(const std::vector<Tile>& W);
        /**
         * @brief Sets the internal Blacklist tiles
         * @note This will set the internal Blacklist object to the one passed
         * */
        static void SetBlacklist(const std::vector<Tile>& B);
        /** @brief Returns the internal TileNode cache generated by GenerateNodes() */
        static std::vector<std::vector<TileNode>> GetNodes();
        /** @brief Returns the internal Whitelist cache */
        static std::vector<Tile> GetWhitelist();
        /** @brief Returns the internal Blacklist cache */
        static std::vector<Tile> GetBlacklist();

        static Tile FindWalkableTile(const Tile& T);
        static Tile FindWalkableTile(const Tile& T, const Tile& Min, const Tile& Max);
        static std::vector<Tile> FindWalkableTiles(const Tile& T);
        static std::vector<Tile> FindWalkableTiles(const Tile& T, const Tile& Min, const Tile& Max);

        static std::vector<Tile> FindPathTo(    const Tile& Goal,
                                                std::int32_t Options = CHECK_COLLISION | CHECK_WHITELIST | CHECK_BLACKLIST,
                                                PATHFINDER Finder = BREADTH_FIRST_SEARCH    );

        static std::vector<Tile> FindPathTo(    const std::vector<Tile>& Goals,
                                                std::int32_t Options = CHECK_COLLISION | CHECK_WHITELIST | CHECK_BLACKLIST,
                                                PATHFINDER Finder = BREADTH_FIRST_SEARCH    );

        static std::vector<Pathfinding::TileNode> FindNodePathTo(   const Tile& Goal,
                                                                    std::int32_t Options = CHECK_COLLISION | CHECK_WHITELIST | CHECK_BLACKLIST,
                                                                    PATHFINDER Finder = BREADTH_FIRST_SEARCH    );

        static std::vector<Pathfinding::TileNode> FindNodePathTo(   const std::vector<Tile>& Goals,
                                                                    std::int32_t Options = CHECK_COLLISION | CHECK_WHITELIST | CHECK_BLACKLIST,
                                                                    PATHFINDER Finder = BREADTH_FIRST_SEARCH    );
    private:
        static std::vector<std::vector<TileNode>> Nodes;
        static std::vector<Tile> Whitelist;
        static std::vector<Tile> Blacklist;

        static bool IsInside(std::uint32_t X, std::uint32_t Y);
        static std::vector<Pathfinding::TileNode*> GetNeighborsTo(std::uint32_t X, std::uint32_t Y, bool CheckCollision = true);

        static std::vector<Pathfinding::TileNode> Finder_BFS(   std::uint32_t StartX, std::uint32_t StartY,
                                                                std::uint32_t EndX, std::uint32_t EndY,
                                                                std::int32_t Options );
        static std::vector<Pathfinding::TileNode> Finder_BFS(   std::uint32_t StartX, std::uint32_t StartY,
                                                                const std::vector<std::pair<std::uint32_t, std::uint32_t>>& Ends,
                                                                std::int32_t Options );
};

#endif // PATHFINDING_HPP_INCLUDED
