#ifndef ITEM_RECIPE_HPP_
#define ITEM_RECIPE_HPP_

#include <vector>
#include <string>
#include <ItemStack/ItemStack.hpp>
#include <Item/IInventory.hpp>

/**
 * @brief Represents the construction recipe for an item.
 */
class ItemRecipe final
{
private:
    std::string milestoneId;
    int achievementId;

public:
    /**
     * @brief Represents the construction level.
     */
    enum class Level
    {
        Manual = 0,
        Table = 1,
        EngTable = 2,
    };

    /**
     * @brief Represents the identity of a particular recipe.
     */
    struct Identity
    {
        /**
         * @brief The recipe ID (assigned by the server).
         */
        uint32_t id;

        /**
         * @brief The construction level required for this recipe.
         */
        Level level;

        /**
         * @brief The output item stack for this recipe.
         */
        ItemStack outputStack;

        /**
         * @brief The list of input stacks.
         */
        std::vector<ItemStack> inputStacks;

        Identity() {}

        template<typename... TArgs>
        Identity(uint32_t id, Level level, ItemStack outputStack, TArgs... inputStacks) :
            id(id),
            level(level),
            outputStack(outputStack),
            inputStacks{inputStacks...}
        {}
    } identity;

    template<typename... TArgs>
    ItemRecipe(uint32_t id, Level level, ItemStack outputStack, TArgs... inputStacks) :
        identity(id, level, outputStack, inputStacks...)
    {}

    /**
     * @brief Determine if this recipe can be executed on the specified inventories.
     * 
     * Counts whether the input inventory contains enough of each item from the input stacks.
     * 
     * @param player The player.
     */
    bool canBeMade(Player &player);

    /**
     * @brief Make this recipe inside the player's inventory.
     * 
     * This is only executed after `canBeMade()` confirmed that it can be made.
     * 
     * @param player The player.
     */
    void make(Player &player);

    /**
     * @brief Determine if this recipe matches the specified query.
     * 
     * @param query The query.
     */
    bool matchesQuery(std::string query);

    /**
     * @brief Sets an achievement which will be given to the player who makes this recipe.
     */
    ItemRecipe* withAchievement(std::string milestoneId, int achievementId);
};

template<>
struct SerialCodec<ItemRecipe::Identity>
{
    static void encode(std::vector<uint8_t> &outBytes, const ItemRecipe::Identity &recipe)
    {
        uint8_t level = static_cast<uint8_t>(recipe.level);
        serializeInto(outBytes, recipe.id, level, recipe.inputStacks, recipe.outputStack);
    }

    static size_t decode(const uint8_t *data, size_t size, ItemRecipe::Identity &recipe)
    {
        uint8_t level = 0;
        recipe.inputStacks.clear();
        size_t result = deserialize(data, size, recipe.id, level, recipe.inputStacks, recipe.outputStack);
        recipe.level = static_cast<ItemRecipe::Level>(level);
        return result;
    }
};

#endif