#ifndef MUTLI_BLOCK_HPP_
#define MUTLI_BLOCK_HPP_

#include <list>

#include <Block/SpriteBlock/SpriteBlock.hpp>
#include <Machine/Machine.hpp>
#include <MachineComponent/MultiBlockComponent/MultiBlockComponent.hpp>

/**
 * @brief Base class for all interface blocks of multi-block machines.
 */
class MultiBlock : public SpriteBlock
{
private:
    static std::vector<Sprite> makeSprites(std::string pureName, const std::vector<std::string> &stateNames);

public:
    /**
     * @brief Represents the structure of a multi-block machine.
     */
    class Structure
    {
    private:
        struct Element
        {
            int u, v, z;
            std::string blockName;
        };

        std::list<Element> elements;
    
    public:
        /**
         * @brief Possible states of a structure returned by `getStateAt()`.
         */
        enum class State
        {
            /**
             * @brief The structure is positively not in the given location/orientation.
             */
            Absent,

            /**
             * @brief The relevant chunks are not loaded yet.
             */
            NotReady,

            /**
             * @brief The structure is positively present.
             */
            Present,
        };
        
        /**
         * @brief Set the specified coordinates in structure space to the specified block.
         * 
         * @param u The axis that runs right from the interface block.
         * @param v The axis that runs into the machine, from the interface block.
         * @param z The Z axis.
         * @param blockName The block name, e.g. "main:tinCasing".
         */
        void add(int u, int v, int z, std::string blockName);

        /**
         * @brief Get the state of this structure at the specified location.
         */
        State getStateAt(
            World &world,
            glm::ivec3 origin, int orientation,
            MultiBlockComponent *multiBlockComp
        );

        /**
         * @brief Check if this structure at the specified origin would contain the query block.
         */
        bool containsBlock(
            World &world,
            glm::ivec3 origin, int orientation,
            glm::ivec3 queryPos
        );

        static glm::ivec3 getRightVector(int orientation);
        static glm::ivec3 getIntoVector(int orientation);
    };

public:
    /*
        #           MultiBlock Meta Byte
        +-------+---+---+---+---+---+---+---+---+
        | Bit   | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
        +-------+---+---+---+---+---+---+---+---+
        | Field | Used by child classes | orient|
        +-------+---+---+---+---+---+---+---+---+

        orient
            The orientation of the multi-block structure.
    */

    /**
     * @brief The structure of this machine. Initialize this in the constructor.
     */
    Structure structure;

    MultiBlock(std::string name, std::string displayName, std::vector<std::string> stateNames);

    virtual bool canManuallyDig() override;
    virtual bool canPlace(LocalWorld &world, IWorldActor &actor, glm::ivec3 pos, Side side, uint8_t &meta) override;
    virtual bool isProtectorOf(World &world, glm::ivec3 myPos, glm::ivec3 otherPos) override;
    virtual void onPlaced(LocalWorld &world, Chunk &chunk, glm::ivec3 pos) override;

    friend class MultiBlockComponent;
};

#endif
