#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>
#include <Block/SpriteBlock/SpriteBlock.hpp>
#include <BlockAtlas/BlockAtlas.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <Milestone/IndustrialRevolutionMilestone/IndustrialRevolutionMilestone.hpp>
#include <World/LocalWorld/LocalWorld.hpp>

const std::vector<std::string>& ConveyorBelt::getStateNames()
{
    static std::vector<std::string> stateNames;

    if (stateNames.empty())
    {
        for (uint8_t meta = 0; meta < numStates; meta++)
        {
            const char *activeFlag = (meta & metaActive) ? "active" : "inactive";
            Side orientation = getMetaOrientation(meta);
            int frameOffset = getMetaFrameOffset(meta);

            stateNames.push_back(formatString("$+$+$+", activeFlag, orientation.name(), frameOffset));
        }
    }

    return stateNames;
}

void ConveyorBelt::onInteractWithBelt(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey)
{
    if (side != Side::Top)
    {
        SpriteBlock::onInteract(world, player, pos, side, collisionKey);
        return;
    }

    int stackSizeBefore = player.holdingStack().getStackSize();
    world.beltManager.tryInsertInto(pos, Side::Top, player.holdingStack());

    if (stackSizeBefore != player.holdingStack().getMaxStackSize())
    {
        player.achieve(IndustrialRevolutionMilestone::ID, IndustrialRevolutionMilestone::PlaceItemOnBelt);
    }
}

void ConveyorBelt::onInteractWithRollers(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey)
{
    world.beltManager.tryAdvanceBelt(pos);
    player.achieve(IndustrialRevolutionMilestone::ID, IndustrialRevolutionMilestone::NudgeConveyorBelt);
}

ConveyorBelt::ConveyorBelt(std::string name) : SpriteBlock(
    name,
    "Conveyor Belt",
    60.0f,
    getStateNames()
)
{
    withMetalSounds();
}

BlockAtlas::SpriteID ConveyorBelt::getBeltSpriteForMeta(uint8_t meta)
{
    return spriteIds[meta % spriteIds.size()];
}

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

bool ConveyorBelt::canPlace(LocalWorld &world, IWorldActor &actor, glm::ivec3 pos, Side side, uint8_t &meta)
{
    meta = actor.getSideFacing2D().asNumber();
    return true;
}

void ConveyorBelt::onPlaced(LocalWorld &world, Chunk &chunk, glm::ivec3 pos)
{
    uint8_t meta = *world.getBlockMeta(pos);
    world.beltManager.addBeltSegment(pos, Side(getMetaOrientation(meta)));
}

void ConveyorBelt::onRemoved(LocalWorld &world, Chunk &chunk, glm::ivec3 pos, uint8_t meta)
{
    world.beltManager.removeBeltSegment(pos);
}

void ConveyorBelt::onInteract(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey)
{
    if (collisionKey.red)
    {
        onInteractWithRollers(world, player, pos, side, collisionKey);
    }
    else if (collisionKey.green)
    {
        onInteractWithBelt(world, player, pos, side, collisionKey);
    }
    else
    {
        SpriteBlock::onInteract(world, player, pos, side, collisionKey);
    }
}

void ConveyorBelt::onWalkingOver(World &world, glm::ivec3 pos, PhysicsEntity &entity)
{
    world.onWalkingOverBelt(pos, entity);
}

BlockAtlas::SpriteID ConveyorBelt::getBlockSprite(glm::ivec3 pos, uint8_t meta, World &world)
{
    BlockAtlas::SpriteID beltSprite = world.getBeltSprite(pos);
    if (beltSprite != 0)
    {
        return beltSprite;
    }

    return SpriteBlock::getBlockSprite(pos, meta, world);
}