#include <Utilities/CircularInt.hpp>
#include <memory>
#include <WorldGrid/WorldGrid.hpp>
#include <World/World.hpp>
#include <Display/WidgetDisplay/WorldDisplay/WorldDisplay.hpp>
#include <Settings/Settings.hpp>
#include <WorldComponent/WorldComponent.hpp>

void WorldGrid::checkRootNode()
{
    if (rootNode == nullptr)
    {
        rootNode = std::make_shared<WorldGridNode>(nullptr, glm::ivec3(0), 0);
        world.onWorldGridNodeCreated(rootNode);
    }
}

WorldGrid::WorldGrid(World &world) : world(world) {}

std::shared_ptr<Chunk> WorldGrid::getChunk(glm::ivec3 pos, std::shared_ptr<WorldGridNode> hint)
{
    std::shared_ptr<WorldGridNode> node;
    if (hint == nullptr)
    {
        checkRootNode();
        node = rootNode;
    }
    else
    {
        node = hint;
        while (!node->containsPos(pos))
        {
            node = node->getParent();
            if (node == nullptr)
            {
                crash("Unexpected condition: a node without a parent being used as a hint!");
            }
        }
    }

    while (node->getLevel() != 0)
    {
        node = node->walkDown(world, pos, false);
        if (node == nullptr)
        {
            return nullptr;
        }
    }

    return node->chunk;
}

ChunkHandle WorldGrid::requestChunk(glm::ivec3 pos, std::shared_ptr<WorldGridNode> hint, float relativeDeadline)
{
    std::shared_ptr<WorldGridNode> node;
    if (hint == nullptr)
    {
        checkRootNode();
        node = rootNode;
    }
    else
    {
        node = hint;
        while (!node->containsPos(pos))
        {
            node = node->getParent();
            if (node == nullptr)
            {
                crash("Unexpected condition: a node without a parent being used as a hint!");
            }
        }
    }

    while (node->getLevel() != 0)
    {
        node = node->walkDown(world, pos, true);
    }

    node->retainChunk(world, relativeDeadline);
    return ChunkHandle(world, node->chunk);
}

void WorldGrid::updateChunkCountsWithinBox(
    World &world,
    const BoundingBox<cint32> &box,
    int delta,
    const std::function<void(Chunk&)> &onHit
)
{
    checkRootNode();
    rootNode->updateChunkCountsWithinBox(world, box, delta, onHit);
}

std::shared_ptr<WorldGridNode> WorldGrid::getRootNode()
{
    checkRootNode();
    return rootNode;
}