#include "BuildingBuffers.h"

#include <algorithm>
#include <cassert>

#include "BuildingType.h"
#include "ItemType.h"
#include "ModulesConfig.h"
#include "ShipsConfig.h"

void initBuffers(Building& b, const RecipeDef& recipe)
{
    b.inputBuffer.counts.clear();
    b.inputBuffer.caps.clear();
    for (const RecipeIngredient& ing : recipe.inputs)
    {
        const ItemType type{ing.item};
        b.inputBuffer.counts[type] = 0;
        b.inputBuffer.caps[type]   = 2 * ing.amount;
    }

    b.outputBuffer.items.clear();
    if (b.type == BuildingType::ReprocessingPlant)
    {
        // 1× max-per-roll (REQ-MAT-OUTPUT-BUFFER-REPROCESSING).
        int maxAmount = 0;
        for (const RecipeOutput& out : recipe.outputs)
        {
            if (out.amount > maxAmount)
            {
                maxAmount = out.amount;
            }
        }
        b.outputBuffer.capacity = maxAmount;
    }
    else
    {
        // 2× per-cycle output.
        int totalAmount = 0;
        for (const RecipeOutput& out : recipe.outputs)
        {
            totalAmount += out.amount;
        }
        b.outputBuffer.capacity = 2 * totalAmount;
    }
}

void initAutoBuffers(const GameConfig& config, Building& b)
{
    b.inputBuffer.counts.clear();
    b.inputBuffer.caps.clear();

    // Union the inputs of every recipe of this building type; the cap for each
    // item is twice the largest per-cycle requirement across those recipes.
    // Output capacity follows the same rules as initBuffers: the Reprocessing
    // Plant holds one cycle's max output (REQ-MAT-OUTPUT-BUFFER-REPROCESSING),
    // other auto buildings hold twice the largest per-cycle output.
    int outputCapacity = 0;
    for (const RecipeDef& recipe : config.recipes.recipes)
    {
        if (recipe.building != b.type)
        {
            continue;
        }

        for (const RecipeIngredient& ing : recipe.inputs)
        {
            const ItemType type{ing.item};
            b.inputBuffer.counts[type] = 0;
            b.inputBuffer.caps[type] =
                std::max(b.inputBuffer.caps[type], 2 * ing.amount);
        }

        if (b.type == BuildingType::ReprocessingPlant)
        {
            int maxAmount = 0;
            for (const RecipeOutput& out : recipe.outputs)
            {
                maxAmount = std::max(maxAmount, out.amount);
            }
            outputCapacity = std::max(outputCapacity, maxAmount);
        }
        else
        {
            int totalAmount = 0;
            for (const RecipeOutput& out : recipe.outputs)
            {
                totalAmount += out.amount;
            }
            outputCapacity = std::max(outputCapacity, 2 * totalAmount);
        }
    }

    b.outputBuffer.items.clear();
    b.outputBuffer.capacity = outputCapacity;
}

void initShipyardBuffers(const GameConfig& config, Building& b)
{
    b.inputBuffer.counts.clear();
    b.inputBuffer.caps.clear();
    b.outputBuffer.items.clear();
    b.outputBuffer.capacity = 0;
    const ShipDef* def = config.ships.findShipDef(b.recipeId);
    if (!def)
    {
        return;
    }
    for (const RecipeIngredient& ing : def->schematic.materials)
    {
        const ItemType type{ing.item};
        b.inputBuffer.counts[type] = 0;
        b.inputBuffer.caps[type]   = 2 * ing.amount;
    }
    if (b.shipLayout.has_value())
    {
        for (const PlacedModule& pm : b.shipLayout->placedModules)
        {
            const ModuleDef* modDef = config.modules.findModuleDef(pm.moduleId);
            if (!modDef)
            {
                continue;
            }
            for (const RecipeIngredient& ing : modDef->materials)
            {
                const ItemType type{ing.item};
                b.inputBuffer.counts.try_emplace(type, 0);
                b.inputBuffer.caps[type] += 2 * ing.amount;
            }
        }
    }
}

void initSalvageBayBuffer(const GameConfig& config, Building& b)
{
    // Salvage Bay has no recipe-driven buffer; its output-buffer holding size for
    // ship drop-off is config-defined (REQ-BLD-SALVAGE-BAY).
    b.outputBuffer.items.clear();
    const BuildingDef* def = config.buildings.findBuildingDef(BuildingType::SalvageBay);
    b.outputBuffer.capacity =
        (def && def->outputBufferCapacity) ? *def->outputBufferCapacity : 0;
}


void reregisterBeltTile(BeltSystem& belts, const GameConfig& config,
                        const Building& building,
                                        const std::vector<ItemType>& splitterFilterA,
                                        const std::vector<ItemType>& splitterFilterB)
{
    switch (building.type)
    {
        case BuildingType::Belt:
            belts.placeBelt(building.anchor, building.rotation);
            break;
        case BuildingType::Splitter:
            assert(building.outputPorts.size() >= 2);
            belts.placeSplitter(building.anchor,
                                  building.outputPorts[0].direction,
                                  building.outputPorts[1].direction);
            belts.setSplitterFilters(building.anchor, splitterFilterA, splitterFilterB);
            break;
        case BuildingType::TunnelEntry:
            belts.placeTunnelEntry(building.anchor, building.rotation,
                                     config.world.tunnelMaxDistance_tiles);
            break;
        case BuildingType::TunnelExit:
            belts.placeTunnelExit(building.anchor, building.rotation);
            break;
        default:
            break;
    }
}

