#include "public_id.h"
#include <algorithm>
#include <array>
#include <charconv>
#include <cctype>
#include <cstdint>
#include <limits>
#include <string>
#include <system_error>
namespace
{
mw::E<std::string> formatNumber(std::uint64_t number, int base)
{
std::array<char, std::numeric_limits<std::uint64_t>::digits + 1> buffer;
const auto result = std::to_chars(
buffer.data(),
buffer.data() + buffer.size(),
number,
base);
if(result.ec != std::errc{})
{
return std::unexpected(
mw::runtimeError("Failed to format a public card number"));
}
return std::string(buffer.data(), result.ptr);
}
bool isDigit(char character)
{
return std::isdigit(static_cast<unsigned char>(character)) != 0;
}
bool isLowercaseAlphaNumeric(std::string_view value)
{
return !value.empty() && std::ranges::all_of(
value,
[](char character)
{
return (character >= 'a' && character <= 'z') ||
(character >= '0' && character <= '9');
});
}
template<typename Number>
mw::E<Number> parseNumber(std::string_view value, int base)
{
Number number = 0;
const auto result = std::from_chars(
value.data(), value.data() + value.size(), number, base);
if(value.empty() || result.ec != std::errc{} ||
result.ptr != value.data() + value.size())
{
return std::unexpected(mw::runtimeError("Invalid public card ID"));
}
return number;
}
std::size_t runEnd(std::string_view value, std::size_t begin, bool digit)
{
std::size_t end = begin;
while(end < value.size() && isDigit(value[end]) == digit)
{
++end;
}
return end;
}
int compareNumericRuns(std::string_view left, std::string_view right)
{
const std::size_t left_nonzero = left.find_first_not_of('0');
const std::size_t right_nonzero = right.find_first_not_of('0');
const std::string_view left_value = left_nonzero == std::string_view::npos
? left.substr(left.size() - 1)
: left.substr(left_nonzero);
const std::string_view right_value = right_nonzero == std::string_view::npos
? right.substr(right.size() - 1)
: right.substr(right_nonzero);
if(left_value.size() != right_value.size())
{
return left_value.size() < right_value.size() ? -1 : 1;
}
const int value_comparison = left_value.compare(right_value);
if(value_comparison != 0)
{
return value_comparison;
}
if(left.size() != right.size())
{
return left.size() < right.size() ? -1 : 1;
}
return 0;
}
} // namespace
mw::E<std::string> formatPublicId(const CardIdentity& identity)
{
if(identity.game_short_name)
{
if(identity.game_short_name->empty() || identity.card_number == 0 ||
identity.card_number >
static_cast<std::uint64_t>(
std::numeric_limits<std::int64_t>::max()))
{
return std::unexpected(
mw::runtimeError("Invalid game card identity"));
}
auto number = formatNumber(identity.card_number, 10);
if(!number)
{
return std::unexpected(std::move(number.error()));
}
return *identity.game_short_name + "-" + *number;
}
if(identity.card_number > std::numeric_limits<std::uint32_t>::max())
{
return std::unexpected(mw::runtimeError("Invalid loose card identity"));
}
return formatNumber(identity.card_number, 36);
}
mw::E<CardIdentity> parsePublicId(std::string_view public_id)
{
const std::size_t separator = public_id.find('-');
CardIdentity identity;
if(separator == std::string_view::npos)
{
if(!isLowercaseAlphaNumeric(public_id))
{
return std::unexpected(
mw::runtimeError("Invalid public card ID"));
}
auto number = parseNumber<std::uint32_t>(public_id, 36);
if(!number)
{
return std::unexpected(std::move(number.error()));
}
identity = {std::nullopt, *number};
}
else
{
const std::string_view game = public_id.substr(0, separator);
const std::string_view number_text = public_id.substr(separator + 1);
if(!isLowercaseAlphaNumeric(game) ||
number_text.empty() ||
number_text.front() == '0' ||
!std::ranges::all_of(number_text, isDigit) ||
number_text.find('-') != std::string_view::npos)
{
return std::unexpected(
mw::runtimeError("Invalid public card ID"));
}
auto number = parseNumber<std::int64_t>(number_text, 10);
if(!number || *number <= 0)
{
return std::unexpected(
mw::runtimeError("Invalid public card ID"));
}
identity = {
std::string(game),
static_cast<std::uint64_t>(*number),
};
}
auto canonical_id = formatPublicId(identity);
if(!canonical_id || *canonical_id != public_id)
{
return std::unexpected(mw::runtimeError("Invalid public card ID"));
}
return identity;
}
bool naturalPublicIdLess(std::string_view left, std::string_view right)
{
std::size_t left_index = 0;
std::size_t right_index = 0;
while(left_index < left.size() && right_index < right.size())
{
const bool left_digit = isDigit(left[left_index]);
const bool right_digit = isDigit(right[right_index]);
const std::size_t left_end = runEnd(left, left_index, left_digit);
const std::size_t right_end = runEnd(right, right_index, right_digit);
const std::string_view left_run =
left.substr(left_index, left_end - left_index);
const std::string_view right_run =
right.substr(right_index, right_end - right_index);
int comparison;
if(left_digit && right_digit)
{
comparison = compareNumericRuns(left_run, right_run);
}
else
{
comparison = left_run.compare(right_run);
}
if(comparison != 0)
{
return comparison < 0;
}
left_index = left_end;
right_index = right_end;
}
return left.size() < right.size();
}