#include <Block/SpriteBlock/BeltMotor/BeltMotor.hpp>
#include <Block/SpriteBlock/SpriteBlock.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <MachineComponent/BeltComponent/BeltComponent.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Block/SpriteBlock/ConveyorBelt/ConveyorBelt.hpp>

const std::vector<std::string>& BeltMotor::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 BeltMotor::onInteractWithRollers(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey)
{
    uint8_t oldMeta = *world.getBlockMeta(pos);

    auto beltMotor = std::dynamic_pointer_cast<MBeltMotor>(world.getMachine(pos));
    auto &beltComponent = beltMotor->getComponent<BeltComponent>();

    std::shared_ptr<Belt> belt = beltComponent.getOutputBelt(getMetaOrientation(oldMeta));
    belt->advance(world);

    world.onBeltUpdated(*belt, false);

    uint8_t newMeta = oldMeta & 3;
    newMeta |= (belt->beltOffset & 3) << 2;

    world.setBlockMeta(pos, newMeta);
}

void BeltMotor::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;
    }

    world.beltManager.tryInsertInto(pos, Side::Top, player.holdingStack());
}

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

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

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

Sound& BeltMotor::getBackgroundSound(uint8_t meta)
{
    if (meta & metaActive)
    {
        return Sound::motor;
    }

    return Sound::NONE;
}

void BeltMotor::onWalkingOver(World &world, glm::ivec3 pos, PhysicsEntity &entity)
{
    uint8_t meta = *world.getBlockMeta(pos);
    if ((meta & metaActive) == 0)
    {
        return;
    }

    Side orient = getMetaOrientation(meta);
    entity.restVel = glm::dvec3(orient.out()) * ConveyorBelt::entityPushSpeed;
}

void BeltMotor::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);
    }
}