#ifndef MENGINEERING_TABLE_HPP_
#define MENGINEERING_TABLE_HPP_

#include <Machine/Machine.hpp>
#include <ItemStack/ItemStack.hpp>
#include <Item/IInventory.hpp>
#include <Machine/IElectricMachine.hpp>
#include <Machine/IDataMachine.hpp>
#include <Machine/IBeltMachine.hpp>
#include <vector>

/**
 * @brief The engineering table machine.
 */
class MEngineeringTable final :
    public Machine,
    public IInventory,
    public IElectricMachine,
    public IDataMachine,
    public IBeltMachine
{
public:
    enum class Trigger
    {
        NotEnoughPower = 0,
    };

    typedef std::array<std::optional<DataLink::Channel>, 1> ChannelTable;
    typedef std::vector<std::optional<DataLink::Channel>> ItemInputChannelTable;

private:
    std::vector<ItemStack> stacks;
    int progress;
    bool isSwitchedOnPriv;
    std::vector<ItemStack> inputStacks;
    ItemStack outputStack;
    int recipeUpdateCounter;
    std::unordered_map<Trigger, DataLink::Channel> channelMap;
    ItemInputChannelTable inputItemChannelTable;

    ItemStack& rechargeSlot();
    Side getEnergyInputSide();
    bool haveEnoughInputItem(ItemStack &inputStack);
    bool canMakeNext();
    void makeNext();
    void checkTrigger(DataLink::ChannelBitmap &outputs, Trigger trigger);
    bool isTriggerActive(Trigger trigger);
    int getPowerUsage();
    bool cycle();

public:
    struct Verbs
    {
        static constexpr std::string_view TurnOn = "TurnOn";
        static constexpr std::string_view TurnOff = "TurnOff";
    };

    static constexpr int energyCapacity = 1000;
    static constexpr int completedProgress = 60;

    MEngineeringTable(LocalWorld &world, glm::ivec3 pos);

    /**
     * @brief Return the amount of energy stored (J).
     */
    int getStoredEnergy();

    /**
     * @brief Get the current progress.
     */
    int getProgress();

    /**
     * @brief Returns true if the engineering table is switched on.
     */
    bool isSwitchedOn();

    /**
     * @brief Get the input stacks of the current recipe.
     */
    std::vector<ItemStack> getInputStacks();

    /**
     * @brief Get the output stack of the current recipe.
     */
    ItemStack getOutputStack();

    /**
     * @brief Get the recipe update counter.
     */
    int getRecipeUpdateCounter();

    /**
     * @brief Set the switch.
     */
    void setSwitch(bool on);

    /**
     * @brief Set the recipe to the specified ID.
     */
    void setRecipe(uint32_t id);

    /**
     * @brief Returns the output channel used by the specified trigger.
     */
    std::optional<DataLink::Channel> getTriggerOutputChannel(Trigger trigger);

    /**
     * @brief Synchronize the specified channel table.
     * 
     * Returns true if any of the values needed changing.
     */
    bool syncChannelTable(ChannelTable &table);

    /**
     * @brief Map the specified trigger to the specified output channel.
     */
    void setTriggerToChannel(Trigger trigger, std::optional<DataLink::Channel> channel);

    /**
     * @brief Returns the output channel used to signal the specified item index as missing.
     */
    std::optional<DataLink::Channel> getInputItemOutputChannel(size_t index);

    /**
     * @brief Synchronize the specified item input channel table.
     * 
     * Returns true if any of the values needed changing.
     */
    bool syncItemInputChannelTable(ItemInputChannelTable &table);

    /**
     * @brief Map the specified inputItem to the specified output channel.
     */
    void setInputItemToChannel(int recipeUpdateCounter, size_t index, std::optional<DataLink::Channel> channel);

    virtual void serializeState(std::vector<uint8_t> &outBytes) override;
    virtual void deserializeState(const uint8_t *data, size_t size) override;
    virtual int getNumSlots() override;
    virtual ItemStack getStackInSlot(int slot) override;
    virtual void setStackInSlot(int slot, ItemStack stack) override;
    virtual bool isAllowedStackInSlot(int slot, ItemStack stack) override;
    virtual void update() override;
    virtual bool canConnectElectricWire(Side side) override;
    virtual Side getDataInputSide() override;
    virtual DataLink::ChannelBitmap getActiveChannels() override;
    virtual void onDataInputVerb(std::string verb) override;
    virtual void onItemStackEntering(Side side, ItemStack &stack) override;
    virtual ItemStack tryExtractItem(Side side, ItemFilter &filter, uint32_t modeCode) override;
    virtual bool canConnectExtractor(Side side) override;
    virtual void getExtractionModes(Side side, std::vector<ExtractionModeSpec> &specs) override;
    virtual void saveMainConfig(MachineConfig &outConfig) override;
    virtual void loadMainConfig(const MachineConfig &config, bool consumed) override;
};

DERIVE_STATIC_CAST_SERIALIZER(MEngineeringTable::Trigger, uint8_t);

#endif