#ifndef WORLD_GRID_NODE_HPP_
#define WORLD_GRID_NODE_HPP_

#include <Utilities/CircularInt.hpp>
#include <atomic>
#include <memory>
#include <vector>
#include <string>

#include <Utilities/Extern.hpp>
#include <BoundingBox/BoundingBox.hpp>
#include <Frustum/Frustum.hpp>
#include <AtomicDataStore/AtomicDataStore.hpp>
#include <DataUpdatePlanner/DataUpdatePlanner.hpp>

class Chunk;
class World;
class WorldDisplay;
class WorldComponent;
class LocalWorld;

/**
 * @brief Represents a node of the multi-level world grid.
 */
class WorldGridNode : public std::enable_shared_from_this<WorldGridNode>
{
private:
    std::weak_ptr<WorldGridNode> parent;
    glm::ivec3 base;
    std::vector<std::shared_ptr<WorldGridNode>> children;
    int numChildren = 0;
    int level;
    uint32_t parentIndex;
    BoundingBox<cint32> bbox;

    std::atomic<bool> loadingInProgress = true;
    std::atomic<bool> knownReady = false;

    std::vector<std::unique_ptr<WorldComponent>> components;

    static constexpr uint32_t extractIndex(int level, uint32_t coord, int leftShift)
    {
        return ((coord >> (4 * level)) & 0xF) << leftShift;
    }

    static constexpr int32_t getLevelBaseMask(int level)
    {
        return ~((UINT32_C(1) << (4 * level + 4)) - 1);
    }

public:
    bool needsCompUpdate = false;

    /**
     * @brief The chunk. Only applicable to level 0 nodes.
     */
    std::shared_ptr<Chunk> chunk;

    /**
     * @brief Construct the grid.
     * 
     * @param parent The parent node (or `nullptr`).
     * @param base The base of this node in world coordinates.
     * @param parentIndex The index given to us by the parent.
     */
    WorldGridNode(std::shared_ptr<WorldGridNode> parent, glm::ivec3 base, uint32_t parentIndex);

    /**
     * @brief Get the parent grid node.
     * 
     * This may be `nullptr` if we are the top-level node, or if we are currently
     * detached.
     */
    std::shared_ptr<WorldGridNode> getParent();

    /**
     * @brief Get the index of the child which exists at the specified position.
     */
    uint16_t getChildIndex(glm::ivec3 pos);

    /**
     * @brief Get the child corresponding to the specified coordinates.
     * 
     * If the child does not exist, and `create` is false, `nullptr` is returned.
     * If `create` is true, then the child is created, inserted into the grid, and
     * returned.
     * 
     * The child returned is the **immediate** child, this function does not walk
     * recursively.
     * 
     * @param world The world.
     * @param pos The position to walk into.
     * @param create Should the child be created if it doesn't exist?
     * @return The child, or `nullptr`.
     */
    std::shared_ptr<WorldGridNode> walkDown(World &world, glm::ivec3 pos, bool create);

    /**
     * @brief Returns the base coordinates.
     */
    glm::ivec3 getBase();

    /**
     * @brief Returns this node's level.
     */
    int getLevel();

    /**
     * @brief Returns true if this grid node contains the specified position.
     */
    bool containsPos(glm::ivec3 pos);

    /**
     * @brief Return the base directory for this node.
     */
    std::string getBaseDir();

    /**
     * @brief Increase the retain count of the chunk, and load it if necessary.
     * 
     * This can only be called on level 0 nodes.
     */
    void retainChunk(World &world, float relativeDeadline);

    /**
     * @brief Un-retain the chunk.
     * 
     * This can only be called on level 0 nodes.
     */
    void unretainChunk(World &world);

    /**
     * @brief Update chunk retain counts within a box.
     * 
     * @param world The world.
     * @param box The box within which chunks will be loaded.
     * @param delta -1 to un-retain, 1 to retain.
     * @param onHit Called on every hit in the box.
     */
    void updateChunkCountsWithinBox(
        World &world,
        const BoundingBox<cint32> &box,
        int delta,
        const std::function<void(Chunk&)> &onHit
    );

    /**
     * @brief Returns true if this node is ready (all data is loaded, including parent nodes).
     */
    bool isReady();

    /**
     * @brief Mark this world grid node as loaded.
     */
    void markLoaded();

    /**
     * @brief Load component data from the specified manager.
     * 
     * This is called from an async task.
     */
    void loadComponents(AtomicDataStore &manager);

    /**
     * @brief Returns a reference to the specified component.
     */
    template<typename TComponent>
    TComponent& getComponent()
    {
        return dynamic_cast<TComponent&>(*components[TComponent::compIndex]);
    }

    /**
     * @brief Get the component with the same type as `comp`, dynamically cast to a child class.
     */
    template<typename TComponent>
    TComponent& getComponent(const TComponent &comp)
    {
        return dynamic_cast<TComponent&>(*components[comp.nodeComponentIndex]);
    }

    /**
     * @brief Update the components in this chunk.
     * 
     * @param world The world.
     * @param planner The update planner.
     */
    void updateComponents(LocalWorld &world, DataUpdatePlanner &planner);

    /**
     * @brief Get the index of this node in the parent.
     */
    size_t getParentIndex();

    /**
     * @brief Returns true if this is the root node.
     */
    bool isRoot();

    /**
     * @brief Dump the components of this node.
     */
    void dumpComponents();

    /**
     * @brief Purge this node.
     * 
     * If this node has children, nothing is done. If it does not, then it gets removed
     * from its parent (if any), and the parent is also purged.
     */
    void purge();
};

#endif