#include <Block/SpriteBlock/Sapling/Sapling.hpp>
#include <Block/SpriteBlock/SpriteBlock.hpp>
#include <Utilities/Utilities.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/FarmLand/FarmLand.hpp>
#include <LiveSculptor/LiveSculptor.hpp>

Sapling::Sapling(std::string name, std::string displayName, Tree::Sculpture &sculpture) :
    SpriteBlock(name, displayName, 30.0f, {"alive", "dead"}),
    sculpture(sculpture)
{}

bool Sapling::canManuallyDig()
{
    return true;
}

bool Sapling::canPlace(LocalWorld &world, IWorldActor &actor, glm::ivec3 pos, Side side, uint8_t &meta)
{
    Block *belowUs = world.getBlockAt(pos + glm::ivec3(0, 0, -1));
    return belowUs->name == "main:farmland";
}

bool Sapling::isIntersectedBy(World &world, glm::ivec3 pos, const BoundingBox<wfixed> &box)
{
    // Let players walk through farm plants.
    return false;
}

void Sapling::onNeighborUpdated(LocalWorld &world, glm::ivec3 pos, Side side)
{
    // We only care about the block under us.
    if (side != Side::Bottom)
    {
        return;
    }

    // If the block below us is not farmland, break.
    Block *belowUs = world.getBlockAt(pos + glm::ivec3(0, 0, -1));
    if (belowUs->name != "main:farmland")
    {
        world.breakBlock(pos);
    }
}

void Sapling::onPlaced(LocalWorld &world, Chunk &chunk, glm::ivec3 pos)
{
    uint8_t meta = *world.getBlockMeta(pos + glm::ivec3(0, 0, -1));
    int nutrition = FarmLand::getNutritionFromMeta(meta);

    if (nutrition == 0)
    {
        return;
    }

    int ticks = (4 - nutrition) * growthTimePerMalnutrition + world.generateRandom() % growthTimeVariation;
    world.sendSignal(pos, sigGrow, ticks);
}

void Sapling::onSignal(LocalWorld &world, glm::ivec3 pos, uint32_t code)
{
    if (code == sigGrow)
    {
        std::optional<uint8_t> farmMetaOpt = world.getBlockMeta(pos + glm::ivec3(0, 0, -1));
        if (!farmMetaOpt.has_value())
        {
            world.sendSignal(pos, sigGrow, 1);
            return;
        }

        uint8_t farmMeta = *farmMetaOpt;
        int waterLevel = FarmLand::getWaterLevelFromMeta(farmMeta);
        int nutrition = FarmLand::getNutritionFromMeta(farmMeta);

        if (waterLevel == 0 || nutrition == 0)
        {
            world.setBlockMeta(pos, metaDead);
            return;
        }

        uint8_t waterSoakedUp = *world.getBlockMeta(pos) >> 1;
        if (waterSoakedUp == 127)
        {
            LiveSculptor sculptor(world, pos, 128);
            if (!sculptor.isReady())
            {
                world.sendSignal(pos, sigGrow, 1);
                return;
            }

            farmMeta = FarmLand::makeUpdatedMeta(farmMeta, nutrition-1, waterLevel);
            world.setBlockMeta(pos + glm::ivec3(0, 0, -1), farmMeta);
            FarmLand::scheduleWaterCheck(world, pos + glm::ivec3(0, 0, -1));

            float radiusRel = (unormFromBits(world.generateRandom()) + (float) nutrition) / 3.0f;
            float heightRel = (unormFromBits(world.generateRandom()) + (float) nutrition) / 3.0f;
            sculpture.build(sculptor, radiusRel, heightRel, pos);

            return;
        }

        farmMeta = FarmLand::makeUpdatedMeta(farmMeta, nutrition, waterLevel-1);
        world.setBlockMeta(pos + glm::ivec3(0, 0, -1), farmMeta);
        FarmLand::scheduleWaterCheck(world, pos + glm::ivec3(0, 0, -1));

        world.setBlockMeta(pos, (waterSoakedUp+1) << 1);

        int ticks = (4 - nutrition) * growthTimePerMalnutrition + world.generateRandom() % growthTimeVariation;
        world.sendSignal(pos, sigGrow, ticks);
    }
    else
    {
        SpriteBlock::onSignal(world, pos, code);
    }
}

void Sapling::getHarvestedStacks(LocalWorld &world, glm::ivec3 pos, std::vector<ItemStack> &stacks)
{
    uint8_t meta = *world.getBlockMeta(pos);
    if (meta & metaDead)
    {
        return;
    }

    SpriteBlock::getHarvestedStacks(world, pos, stacks);
}