BareGit
#include "data_fake.h"

#include <algorithm>
#include <string_view>
#include <utility>

namespace
{

mw::Error readOnlyError(std::string_view operation)
{
    return mw::runtimeError(
        "DataSourceFake is read-only and cannot " +
        std::string(operation) + ".");
}

bool identitiesMatch(const CardIdentity& left, const CardIdentity& right)
{
    return left.game_short_name == right.game_short_name &&
           left.card_number == right.card_number;
}

} // namespace

DataSourceFake::DataSourceFake(
    std::vector<Card> cards,
    std::vector<Series> series,
    std::unordered_map<std::int64_t, std::vector<std::int64_t>> card_series,
    std::vector<GameDefinitionSnapshot> game_definitions,
    std::unordered_map<std::int64_t, std::vector<GameFieldValue>> field_values)
        : cards_(std::move(cards)),
          series_(std::move(series)),
          card_series_(std::move(card_series)),
          game_definitions_(std::move(game_definitions)),
          field_values_(std::move(field_values))
{}

mw::E<std::int64_t> DataSourceFake::getSchemaVersion() const
{
    return DB_SCHEMA_VERSION;
}

mw::E<void> DataSourceFake::migrateSchema0To1()
{
    return std::unexpected(readOnlyError("migrate the schema"));
}

mw::E<std::unique_ptr<DataSourceTransactionInterface>>
DataSourceFake::beginTransaction()
{
    return std::unexpected(readOnlyError("begin a transaction"));
}

bool DataSourceFake::gameVisible(
    const std::string& short_name, GameContentScope scope) const
{
    if(scope == GameContentScope::INCLUDE_INTERNAL)
    {
        return true;
    }
    const auto definition = std::ranges::find_if(
        game_definitions_,
        [&short_name](const GameDefinitionSnapshot& candidate)
        {
            return candidate.game.short_name == short_name;
        });
    return definition != game_definitions_.end() &&
           definition->game.visibility == GameVisibility::PUBLIC;
}

bool DataSourceFake::cardVisible(
    const Card& card, GameContentScope scope) const
{
    return !card.identity.game_short_name ||
           gameVisible(*card.identity.game_short_name, scope);
}

mw::E<std::vector<Card>> DataSourceFake::getCards(
    GameContentScope scope) const
{
    std::vector<Card> cards;
    std::ranges::copy_if(
        cards_, std::back_inserter(cards),
        [this, scope](const Card& card)
        {
            return cardVisible(card, scope);
        });
    return cards;
}

mw::E<std::vector<Card>> DataSourceFake::getCardsByCreator(
    std::int64_t creator_user_id, GameContentScope scope) const
{
    std::vector<Card> cards;
    std::ranges::copy_if(
        cards_, std::back_inserter(cards),
        [this, creator_user_id, scope](const Card& card)
        {
            return card.creator_user_id == creator_user_id &&
                   cardVisible(card, scope);
        });
    return cards;
}

mw::E<std::optional<Card>> DataSourceFake::getCard(
    const CardIdentity& identity, GameContentScope scope) const
{
    const auto card = std::ranges::find_if(
        cards_,
        [&identity](const Card& candidate)
        {
            return identitiesMatch(candidate.identity, identity);
        });
    if(card == cards_.end() || !cardVisible(*card, scope))
    {
        return std::nullopt;
    }
    return *card;
}

mw::E<std::vector<Game>> DataSourceFake::getGames(
    GameContentScope scope) const
{
    std::vector<Game> games;
    games.reserve(game_definitions_.size());
    for(const GameDefinitionSnapshot& definition : game_definitions_)
    {
        if(gameVisible(definition.game.short_name, scope))
        {
            games.push_back(definition.game);
        }
    }
    std::ranges::sort(
        games,
        [](const Game& left, const Game& right)
        {
            if(left.display_name != right.display_name)
            {
                return left.display_name < right.display_name;
            }
            return left.short_name < right.short_name;
        });
    return games;
}

mw::E<std::optional<GameDefinitionSnapshot>>
DataSourceFake::getGameDefinition(
    const std::string& short_name, GameContentScope scope) const
{
    const auto definition = std::ranges::find_if(
        game_definitions_,
        [&short_name](const GameDefinitionSnapshot& candidate)
        {
            return candidate.game.short_name == short_name;
        });
    if(definition == game_definitions_.end() ||
       !gameVisible(short_name, scope))
    {
        return std::nullopt;
    }
    return *definition;
}

mw::E<std::optional<CardGameFields>> DataSourceFake::getCardFieldValues(
    std::int64_t card_id, GameContentScope scope) const
{
    const auto card = std::ranges::find_if(
        cards_,
        [card_id](const Card& candidate) { return candidate.id == card_id; });
    if(card == cards_.end() || !card->identity.game_short_name ||
       !cardVisible(*card, scope))
    {
        return std::nullopt;
    }
    auto definition = getGameDefinition(
        *card->identity.game_short_name, scope);
    if(!definition || !*definition)
    {
        return std::unexpected(mw::runtimeError(
            "Fake card references a missing game"));
    }
    const auto values = field_values_.find(card_id);
    return CardGameFields{
        std::move(**definition),
        values == field_values_.end()
            ? std::vector<GameFieldValue>{}
            : values->second};
}

mw::E<std::vector<Series>> DataSourceFake::getSeries(
    GameContentScope scope) const
{
    std::vector<Series> series;
    std::ranges::copy_if(
        series_, std::back_inserter(series),
        [this, scope](const Series& item)
        {
            return gameVisible(item.game_short_name, scope);
        });
    return series;
}

mw::E<std::optional<Series>> DataSourceFake::getSeries(
    std::int64_t series_id, GameContentScope scope) const
{
    const auto series = std::ranges::find_if(
        series_,
        [series_id](const Series& candidate)
        {
            return candidate.id == series_id;
        });
    if(series == series_.end() ||
       !gameVisible(series->game_short_name, scope))
    {
        return std::nullopt;
    }
    return *series;
}

mw::E<std::vector<std::int64_t>> DataSourceFake::getCardSeries(
    std::int64_t card_id) const
{
    const auto memberships = card_series_.find(card_id);
    if(memberships == card_series_.end())
    {
        return std::vector<std::int64_t>{};
    }
    return memberships->second;
}

mw::E<void> DataSourceFake::setSchemaVersion(
    [[maybe_unused]] std::int64_t version)
{
    return std::unexpected(readOnlyError("set the schema version"));
}