#ifndef MCHEMICAL_REACTOR_HPP_
#define MCHEMICAL_REACTOR_HPP_

#include <Machine/IBeltMachine.hpp>
#include <Machine/Machine.hpp>
#include <ItemStack/ItemStack.hpp>
#include <BlockRegistry/BlockRegistry.hpp>
#include <FluidRegistry/FluidRegistry.hpp>
#include <Item/IInventory.hpp>
#include <Item/IEnergyContainingItem.hpp>
#include <Item/IFluidContainingItem.hpp>
#include <Machine/IElectricMachine.hpp>
#include <vector>
#include <unordered_map>

class IChemicalReactorComponent;

/**
 * @brief The chemical reactor machine.
 */
class MChemicalReactor :
    public Machine,
    public IInventory,
    public IElectricMachine,
    public IBeltMachine
{
private:
    std::vector<IChemicalReactorComponent*> components;
    std::vector<IEnergyContainingItem*> energyContainers;
    std::vector<IFluidContainingItem*> fluidContainers;
    std::vector<ItemStack> stacks;
    bool isSwitchedOnPriv;
    int temperature;
    int bufferedEnergy;
    int ticks;

    /**
     * @brief Maps the fluid types strings to the stored amount.
     */
    std::unordered_map<std::string, int> bufferedFluid;

public:
    /**
     * @brief Represents a chemical reactor recipe.
     */
    struct Recipe
    {
        /**
         * @brief Returns true if the recipe can be made out of the items in the chamber.
         */
        bool canBeMade(MChemicalReactor &reactor);

        /**
         * @brief Use the resources and make the item in the chamber.
         */
        void make(MChemicalReactor &reactor);

        /**
         * @brief Returns true if this recipe matches the query.
         */
        bool matchesQuery(std::string query);

        /**
         * @brief The minimum temperature.
         */
        int minTemp;

        /**
         * @brief Number of ticks between each run of this recipe.
         */
        int ticksBetweenRuns;

        /**
         * @brief The fluid type required.
         */
        std::string fluidType;

        /**
         * @brief The amount of fluid required.
         */
        int fluidAmount;

        /**
         * @brief The inputs stacks.
         */
        std::vector<ItemStack> inputs;

        /**
         * @brief The output stacks.
         */
        ItemStack output;
    };

    /**
     * @brief The list of chemical reactor recipes.
     */
    static std::vector<Recipe> recipes;

    /**
     * @brief Initialize the recipes.
     */
    static void initRecipes();

    /**
     * @brief Return recipes matching a query.
     * 
     * @param query The query.
     * @return Vector of pointers to the recipes.
     */
    static std::vector<Recipe*> queryRecipes(std::string query);

    static constexpr int maxTemperature = 10000;
    static constexpr float coolingRate = 0.1f;

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

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

    /**
     * @brief Set whether the reactor is switched on.
     */
    void setSwitch(bool on);

    /**
     * @brief Get the temperature of the reactor.
     */
    int getTemperature();

    /**
     * @brief Heat up by the specified temperature.
     * 
     * @param dt The amount to heat up by.
     */
    void heatUp(int dt);

    /**
     * @brief Drain energy from the reactor.
     * 
     * @param energy The maximum amount of energy to drain (J).
     * @return The amount of energy actually drained (J).
     */
    int drainEnergy(int energy);

    /**
     * @brief Return energy to the reactor.
     * 
     * @param energy The amount of energy to return.
     */
    void returnEnergy(int energy);

    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 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 saveMainConfig(MachineConfig &config) override;
    virtual void loadMainConfig(const MachineConfig &config, bool consumed) override;
};

#endif