#ifndef MULTI_BLOCK_COMPONENT_HPP_
#define MULTI_BLOCK_COMPONENT_HPP_

#include <DataLink/DataLink.hpp>
#include <FluidLink/FluidLink.hpp>
#include <MachineComponent/MachineComponent.hpp>
#include <Machine/Machine.hpp>
#include <Serialize/Serialize.hpp>
#include <ChunkHandle/ChunkHandle.hpp>
#include <Side/Side.hpp>
#include <ElectricLink/ElectricLink.hpp>
#include <MachineComponent/ChunkMonitorComponent/ChunkMonitorComponent.hpp>

/**
 * @brief Component for multi-block machines.
 */
class MultiBlockComponent : public MachineComponent
{
public:
    /**
     * @brief Enumeration of casing connector types.
     */
    enum class CasingConnectorType : uint8_t
    {
        None = 0,
        EnergyInput = 1,
        EnergyOutput = 2,
        Data = 3,
        FluidInput = 4,
        FluidOutput = 5,

        TypesCount,
    };

    /**
     * @brief Represents a casing connector.
     */
    struct CasingConnector
    {
        CasingConnectorType type;
        int8_t offsetX;
        int8_t offsetY;
        int8_t offsetZ;
        Side side;
    };

private:
    std::vector<CasingConnector> connectors;
    std::vector<ChunkHandle> connectorChunkHandles;
    ChunkMonitorComponent &chunkMonitor;

public:
    static std::string tagName;

    MultiBlockComponent(Machine &machine);

    /**
     * @brief Called when a connector of a given type is placed at an offset from the machine.
     */
    void onConnectorAdded(CasingConnectorType type, int8_t offsetX, int8_t offsetY, int8_t offsetZ, Side side);

    /**
     * @brief Called when the connector at the specified offset from the machine is removed.
     */
    void onConnectorRemoved(int8_t offsetX, int8_t offsetY, int8_t offsetZ);

    /**
     * @brief Try to get the coordinates of a casing connector of the specified type.
     * 
     * Returns `true` if the connector was found, and sets `outPos` to the coordinates of the block immediately
     * after leaving the casing, and `outSide` to the side of that block which connects to the casing.
     */
    bool getCasingConnector(CasingConnectorType type, glm::ivec3 &outPos, Side &outSide);

    /**
     * @brief Returns the electric input on the casing, or `nullptr` if there isn't one.
     */
    std::shared_ptr<ElectricLink> getEnergyInput();

    /**
     * @brief Returns the fluid input on the casing, or `nullptr` if there isn't one.
     */
    std::shared_ptr<FluidLink> getFluidInput();

    /**
     * @brief Returns the data link on the casing, or `nullptr` if there isn't one.
     */
    std::shared_ptr<DataLink> getDataLink();

    /**
     * @brief Return the world coordinates corresponding to UVZ coordinates, relative to the machine.
     */
    glm::ivec3 getCasingRelativePos(glm::ivec3 uvz);

    virtual void serializeState(std::vector<uint8_t> &outBytes) override;
    virtual void deserializeState(const uint8_t *bytes, size_t size) override;
    virtual bool isReady() override;

    friend class MultiBlock;
};

DERIVE_STATIC_CAST_SERIALIZER(MultiBlockComponent::CasingConnectorType, uint8_t);
DERIVE_STRUCT_SERIALIZER(MultiBlockComponent::CasingConnector, self.type, self.offsetX, self.offsetY, self.offsetZ, self.side);

#endif