#include "FactoryChecksum.h"

#include <map>
#include <vector>

#include "BuildingBuffers.h"
#include "FactoryState.h"
#include "Item.h"
#include "ItemType.h"
#include "StateChecksum.h"

namespace
{
void appendItems(Hasher& hasher, const std::vector<Item>& items)
{
    hasher.append(items.size());
    for (const Item& item : items)
    {
        hasher.append(item.type.id);
    }
}

// std::map<ItemType, int> iterates in sorted-id order (ItemType::operator<), so both
// buffer sides hash the same way in every run.
void appendItemCounts(Hasher& hasher, const std::map<ItemType, int>& counts)
{
    hasher.append(counts.size());
    for (const std::pair<const ItemType, int>& entry : counts)
    {
        hasher.append(entry.first.id);
        hasher.append(entry.second);
    }
}

void appendInputBuffer(Hasher& hasher, const InputBuffer& buffer)
{
    appendItemCounts(hasher, buffer.counts);
    appendItemCounts(hasher, buffer.caps);
}
}  // namespace

void appendChecksum(const FactoryState& state, Hasher& hasher)
{
    // state.buildings keeps a stable, deterministic order (append on build, swap-free
    // erase aside -- both runs perform identical operations, so order matches).
    hasher.append(state.buildings.size());
    for (const Building& b : state.buildings)
    {
        hasher.append(b.id);
        hasher.append(b.anchor);
        hasher.append(b.footprint.width());
        hasher.append(b.footprint.height());
        hasher.append(b.rotation);
        hasher.append(b.type);
        hasher.append(b.recipeId);
        appendInputBuffer(hasher, b.inputBuffer);
        appendItems(hasher, b.outputBuffer.items);
        appendItemCounts(hasher, b.outputBuffer.caps);
        hasher.append(b.emergingItems.size());
        for (const std::vector<BeltItemSlot>& lane : b.emergingItems)
        {
            hasher.append(lane.size());
            for (const BeltItemSlot& slot : lane)
            {
                hasher.append(slot.item.type.id);
                hasher.append(slot.progress);
            }
        }
        hasher.append(b.incomingItems.size());
        for (const std::vector<BeltItemSlot>& lane : b.incomingItems)
        {
            hasher.append(lane.size());
            for (const BeltItemSlot& slot : lane)
            {
                hasher.append(slot.item.type.id);
                hasher.append(slot.progress);
            }
        }
        hasher.append(b.production.has_value());
        if (b.production.has_value())
        {
            hasher.append(b.production->recipeId);
            hasher.append(b.production->completesAt);
            appendItems(hasher, b.production->chosenOutputs);
        }
        hasher.append(b.shipLayout.has_value());
        hasher.append(b.queuedForDeconstruction);
    }

    hasher.append(state.constructionQueue.size());
    for (const ConstructionSite& s : state.constructionQueue)
    {
        hasher.append(s.id);
        hasher.append(s.anchor);
        hasher.append(s.footprint.width());
        hasher.append(s.footprint.height());
        hasher.append(s.rotation);
        hasher.append(s.type);
        hasher.append(s.recipeId);
        hasher.append(s.completesAt);
        hasher.append(s.shipLayout.has_value());
        hasher.append(s.splitterFilterA.size());
        for (const ItemType& type : s.splitterFilterA) { hasher.append(type.id); }
        hasher.append(s.splitterFilterB.size());
        for (const ItemType& type : s.splitterFilterB) { hasher.append(type.id); }
    }

    hasher.append(state.deconstructionQueue.size());
    for (const DeconstructionEntry& e : state.deconstructionQueue)
    {
        hasher.append(e.id);
        hasher.append(e.completesAt);
        hasher.append(e.splitterFilterA.size());
        for (const ItemType& type : e.splitterFilterA) { hasher.append(type.id); }
        hasher.append(e.splitterFilterB.size());
        for (const ItemType& type : e.splitterFilterB) { hasher.append(type.id); }
    }

    state.grid.appendChecksum(hasher);
}
