#ifndef MITEM_PROCESSOR_HPP_
#define MITEM_PROCESSOR_HPP_

#include <Machine/Machine.hpp>
#include <ItemStack/ItemStack.hpp>
#include <Item/IInventory.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Machine/IElectricMachine.hpp>
#include <Machine/IDataMachine.hpp>
#include <Machine/IBeltMachine.hpp>
#include <Serialize/Serialize.hpp>
#include <vector>
#include <unordered_map>
#include <array>

/**
 * @brief Base class for item processor machines.
 */
class MItemProcessor :
    public Machine,
    public IInventory,
    public IElectricMachine,
    public IDataMachine,
    public IBeltMachine
{
public:
    enum class Trigger
    {
        InputSlotEmpty = 0,
        NotEnoughPower = 1,
    };

    typedef std::array<std::optional<DataLink::Channel>, 2> ChannelTable;

private:
    std::vector<ItemStack> stacks;
    int progress;
    std::unordered_map<Trigger, DataLink::Channel> channelMap;

    ItemStack& inputSlot();
    ItemStack& outputSlot();
    ItemStack& rechargeSlot();
    bool cycle();
    bool isTriggerActive(Trigger trigger);
    void checkTrigger(DataLink::ChannelBitmap &outputs, Trigger trigger);
    int getEffectivePowerUsage(int efficiency, int speed);
    void calculateStats(int &efficiency, int &speed);
    
protected:
    /**
     * @brief Get the output stack given an input stack in this processor.
     * 
     * Return an empty stack if the input is not valid.
     */
    virtual ItemStack getOutputItem(ItemStack &input) = 0;

    /**
     * @brief Get this machine's power usage (J/t).
     */
    virtual int getPowerUsage() = 0;

public:
    static constexpr int upgradeSlotBase = 3;
    static constexpr int energyCapacity = 10000;

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

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

    /**
     * @brief Get the progress amount corresponding to completion.
     */
    virtual int getCompletedProgress() = 0;

    /**
     * @brief Return the current stored energy.
     */
    int getStoredEnergy();

    /**
     * @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);

    virtual void serializeState(std::vector<uint8_t> &outBytes) override;
    virtual void deserializeState(const uint8_t *data, size_t size) override;
    virtual void saveMainConfig(MachineConfig &config) override;
    virtual void loadMainConfig(const MachineConfig &config, bool consumed) 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 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;
};

DERIVE_STATIC_CAST_SERIALIZER(MItemProcessor::Trigger, uint8_t);

#endif