#ifndef FLUID_COMPONENT_HPP_
#define FLUID_COMPONENT_HPP_

#include <unordered_map>
#include <string>
#include <array>
#include <optional>

#include <MachineComponent/MachineComponent.hpp>
#include <Machine/IFluidMachine.hpp>
#include <Side/Side.hpp>

/**
 * @brief The fluid component which allows a machine to store fluids. 
 */
class FluidComponent : public MachineComponent
{
private:
    uint8_t sideBitmap = 0;
    std::unordered_map<std::string, int> fluidAmounts;
    std::array<std::optional<std::string>, 6> defaultFluidOnSide;
    IFluidMachine &fluidMachine;

public:
    static std::string tagName;

    FluidComponent(Machine &machine);

    /**
     * @brief Get the default fluid to be pulled from the specified side.
     */
    std::optional<std::string> getDefaultFluidOnSide(Side side);

    /**
     * @brief Set the default fluid to be pulled from the specified side.
     */
    void setDefaultFluidOnSide(Side side, std::optional<std::string> fluidName);

    /**
     * @brief Try to pull fluid from the specified side.
     * 
     * @param side The side to pull from.
     * @param fluidName The fluid name (e.g. "main:water").
     * @param amount The maximum amount to pull (ml).
     * @return The amount actually pulled (ml).
     */
    int pullFluid(Side side, std::string fluidName, int amount);

    /**
     * @brief Try to push fluid into the specified side.
     * 
     * @param side The side to push into.
     * @param fluidName The fluid name (e.g. "main:water").
     * @param amount The maximum amount to push (ml).
     * @return The amount actually pushed (ml).
     */
    int pushFluid(Side side, std::string fluidName, int amount);

    /**
     * @brief Get the amount of the specified fluid currently stored (ml).
     */
    int getFluidAmount(std::string fluidName);

    /**
     * @brief Try refilling fluids from the specified stack.
     */
    void refillFrom(ItemStack &stack);

    /**
     * @brief Drain the specified fluid into the specified stack.
     */
    void drainInto(ItemStack &stack, std::string fluidName);

    /**
     * @brief Drain the specified amount of fluid from this machine.
     * 
     * @param fluidName The fluid to drain (e.g. "main:water").
     * @param amount The maximum amount to drain (ml).
     * @return The amount actually drained (ml).
     */
    int drainFluid(std::string fluidName, int amount);

    /**
     * @brief Add the specified amount of fluid to this machine.
     * 
     * @param fluidName The fluid name to add to (e.g. "main:water").
     * @param amount The amount of fluid to add (ml).
     */
    void addFluid(std::string fluidName, int amount);

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

#endif