BareGit
#include "card_pool.h"

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <iterator>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

std::vector<CardPoolEntry> CardPoolService::calculate(
    const std::vector<Card>& cards) const
{
    std::vector<Card> eligible;
    std::ranges::copy_if(cards, std::back_inserter(eligible),
                        [](const Card& card)
    {
        return card.rarity > 0;
    });
    std::ranges::sort(eligible, {}, &Card::id);
    if(eligible.empty())
    {
        return {};
    }
    const std::int64_t minimum = std::ranges::min(
        eligible, {}, &Card::rarity).rarity;
    double total = 0;
    std::vector<CardPoolEntry> result;
    result.reserve(eligible.size());
    for(const Card& card : eligible)
    {
        const double weight = std::exp2(
            static_cast<double>(minimum) -
            static_cast<double>(card.rarity));
        total += weight;
        result.push_back({card, weight, 0});
    }
    for(CardPoolEntry& entry : result)
    {
        entry.probability = entry.scaled_weight / total;
    }
    return result;
}

mw::E<CardPoolEntry> CardPoolService::select(
    const std::vector<CardPoolEntry>& entries,
    mw::CryptoInterface& crypto) const
{
    if(entries.empty())
    {
        return std::unexpected(mw::runtimeError("Card pool is empty"));
    }
    auto bytes = crypto.randomBytes(8);
    if(!bytes)
    {
        return std::unexpected(std::move(bytes.error()));
    }
    std::uint64_t value = 0;
    for(std::byte byte : *bytes)
    {
        value = (value << 8) | std::to_integer<std::uint64_t>(byte);
    }
    const std::uint64_t high_53 = value >> 11;
    const double fraction = std::ldexp(static_cast<double>(high_53), -53);
    double total = 0;
    for(const CardPoolEntry& entry : entries)
    {
        total += entry.scaled_weight;
    }
    const double target = fraction * total;
    double cumulative = 0;
    const CardPoolEntry* last_positive = nullptr;
    for(const CardPoolEntry& entry : entries)
    {
        if(entry.scaled_weight == 0)
        {
            continue;
        }
        last_positive = &entry;
        cumulative += entry.scaled_weight;
        if(target < cumulative)
        {
            return entry;
        }
    }
    if(last_positive != nullptr)
    {
        return *last_positive;
    }
    return std::unexpected(mw::runtimeError(
        "Card pool has no represented weight"));
}

std::string formatProbability(double probability)
{
    if(probability <= 0)
    {
        return "0%";
    }
    const double percentage = probability * 100;
    if(percentage < 0.000001)
    {
        return "<0.000001%";
    }
    std::ostringstream output;
    output << std::fixed << std::setprecision(6) << percentage;
    std::string result = output.str();
    result.erase(result.find_last_not_of('0') + 1);
    if(result.ends_with('.'))
    {
        result.pop_back();
    }
    return result + '%';
}