#ifndef ITEM_PROCESSOR_HPP_
#define ITEM_PROCESSOR_HPP_

#include <Block/SpriteBlock/MultiBlock/MultiBlock.hpp>
#include <Block/MachineBlockMixin.hpp>
#include <Machine/Machine.hpp>
#include <Controller/InventoryController/ItemProcessorController/ItemProcessorController.hpp>
#include <Utilities/Utilities.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>
#include <World/LocalWorld/LocalWorld.hpp>

/**
 * @brief The interface block for item processor machines.
 */
template<typename TMachine>
class ItemProcessor : public MultiBlock, public MachineBlockMixin<TMachine>
{
private:
    std::string texName;
    glm::vec3 runningLightColor;

    Sound &backgroundSound;

public:
    /*
        #        Item Processor Meta Byte
        +-------+---+---+---+---+---+---+---+---+
        | Bit   | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
        +-------+---+---+---+---+---+---+---+---+
        | Field |   |   |   |   |   | A | orient|
        +-------+---+---+---+---+---+---+---+---+

        orient
            The orientation of the multi-block structure.
        
        A
            If set, it means the machine is on.
    */

    ItemProcessor(
        std::string name,
        std::string displayName,
        std::string texName,
        std::string casingType,
        glm::vec3 runningLightColor,
        Sound &backgroundSound
    ) :
        MultiBlock(name, displayName, {"off", "on"}),
        texName(texName),
        runningLightColor(runningLightColor),
        backgroundSound(backgroundSound)
    {
        int u, v;
        std::string casingBlockName = formatString("main:$+Casing", casingType);
        
        // Add the roof and floor.
        for (u=-1; u<=1; u++)
        {
            for (v=0; v<=2; v++)
            {
                structure.add(u, v, -1, casingBlockName);
                structure.add(u, v, 1, casingBlockName);
            }
        }

        // Add the left/right walls.
        for (v=0; v<=2; v++)
        {
            structure.add(-1, v, 0, casingBlockName);
            structure.add(1, v, 0, casingBlockName);
        }

        // Add the back.
        structure.add(0, 2, 0, casingBlockName);

        withMetalSounds();
    }

    virtual void getHarvestedStacks(LocalWorld &world, glm::ivec3 pos, std::vector<ItemStack> &stacks) override
    {
        MultiBlock::getHarvestedStacks(world, pos, stacks);
        
        std::shared_ptr<IInventory> inv = std::dynamic_pointer_cast<IInventory>(world.getMachine(pos));
        if (inv != nullptr)
        {
            int i;
            for (i=0; i<inv->getNumSlots(); i++)
            {
                ItemStack stack = inv->getStackInSlot(i);
                if (!stack.isEmpty())
                {
                    stacks.push_back(stack);
                }
            }
        }
    }

    virtual void onInteract(LocalWorld &world, Player &player, glm::ivec3 pos, Side side, const RGBABytes &collisionKey) override
    {
        player.connectController<ItemProcessorController<TMachine>>(
            formatString("main:ItemProcessorController:$+", name),
            world.getMachine(pos)
        );
    }

    virtual glm::vec3 getLightColor(uint8_t meta) override
    {
        if (meta & (1 << 2))
        {
            return runningLightColor;
        }
        else
        {
            return glm::vec3(0.0f);
        }
    }

    virtual glm::ivec3 getLightOffset(uint8_t meta) override
    {
        return glm::ivec3(4) - 4 * Structure::getIntoVector(meta & 3);
    }

    virtual Sound& getBackgroundSound(uint8_t meta) override
    {
        if (meta & (1 << 2))
        {
            return backgroundSound;
        }

        return Sound::NONE;
    }
};

#endif