#include <Block/SpriteBlock/FarmPlant/FarmPlant.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/FarmLand/FarmLand.hpp>

FarmPlant::FarmPlant(std::string name, std::string displayName, const std::vector<std::string> &stateNames) :
    SpriteBlock(name, displayName, 4.0f, stateNames)
{}

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

bool FarmPlant::canPlace(LocalWorld &world, IWorldActor &actor, glm::ivec3 pos, Side side, uint8_t &meta)
{
    // Only allow placing plants on farmland.
    Block *belowUs = world.getBlockAt(pos + glm::ivec3(0, 0, -1));
    if (belowUs == nullptr || belowUs->name != "main:farmland")
    {
        return false;
    }

    return true;
}

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

void FarmPlant::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 FarmPlant::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 = getGrowthStageTicks(nutrition) + world.generateRandom() % growthTimeVariation;
    world.sendSignal(pos, sigGrow, ticks);
}

void FarmPlant::onSignal(LocalWorld &world, glm::ivec3 pos, uint32_t code)
{
    if (code == sigGrow)
    {
        uint8_t ourMeta = *world.getBlockMeta(pos);
        int stage = ourMeta & 3;

        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);

        int waterUsage = getWaterUsage();
        if (waterLevel < waterUsage)
        {
            if (stage == 0)
            {
                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));
            }

            world.setBlockMeta(pos, 3);
        }
        else
        {
            if (stage == 0)
            {
                int ticks = getGrowthStageTicks(nutrition) + world.generateRandom() % growthTimeVariation;
                world.sendSignal(pos, sigGrow, ticks);

                ourMeta |= nutrition << 2;
                nutrition--;
            }

            farmMeta = FarmLand::makeUpdatedMeta(farmMeta, nutrition, waterLevel - waterUsage);
            world.setBlockMeta(pos + glm::ivec3(0, 0, -1), farmMeta);
            world.setBlockMeta(pos, ourMeta+1);
            FarmLand::scheduleWaterCheck(world, pos + glm::ivec3(0, 0, -1));
        }
    }
    else
    {
        SpriteBlock::onSignal(world, pos, code);
    }
}

void FarmPlant::getHarvestedStacks(LocalWorld &world, glm::ivec3 pos, std::vector<ItemStack> &stacks)
{
    uint8_t meta = *world.getBlockMeta(pos);
    int stage = meta & 3;

    if (stage == 2)
    {
        int nutrition = (meta >> 2) & 3;
        getFarmHarvest(nutrition, stacks);
    }
    else if (stage == 3)
    {
        // Dead plant; no harvest.
        return;
    }
    else
    {
        // Simply return the seeds.
        SpriteBlock::getHarvestedStacks(world, pos, stacks);
    }
}

bool FarmPlant::canSweepAway()
{
    return true;
}