namespace OpenNest.Data;
///
/// Generated nest names: "N" + two-digit year + "-" + the year's sequence number in an
/// unambiguous base-29 alphabet, padded to three digits with its zero digit ('2').
/// No S (it reads as 5). The sequence runs for the whole year; three digits cover 24,389 nests
/// before a fourth appears.
///
public static class NestNames
{
private const string Base29Digits = "2345679ACDEFGHJKLMNPQRTUVWXYZ";
/// Earliest and latest years a sequence may be allocated for.
public const int MinYear = 2000, MaxYear = 2099;
public static string Format(int year, int number)
{
ArgumentOutOfRangeException.ThrowIfNegative(number);
var seq = ToBase29(number).PadLeft(3, Base29Digits[0]);
return $"N{year % 100:D2}-{seq}";
}
///
/// The sequence number of a generated name for , or false for
/// any other name (renamed nests, other years, non-canonical padding).
///
public static bool TryParse(string? name, int year, out int number)
{
number = 0;
var prefix = $"N{year % 100:D2}-";
if (name is null || name.Length < prefix.Length + 3 || !name.StartsWith(prefix, StringComparison.Ordinal))
return false;
long value = 0;
foreach (var c in name.AsSpan(prefix.Length))
{
var digit = Base29Digits.IndexOf(c);
if (digit < 0)
return false;
value = value * Base29Digits.Length + digit;
if (value > int.MaxValue)
return false;
}
number = (int)value;
return Format(year, number) == name;
}
private static string ToBase29(int value)
{
if (value == 0)
return Base29Digits[0].ToString();
var result = "";
while (value > 0)
{
result = Base29Digits[value % Base29Digits.Length] + result;
value /= Base29Digits.Length;
}
return result;
}
}
/// Wire shape of the nest server's next-name allocation.
public sealed class NestNameAllocation
{
public int Year { get; set; }
public int Number { get; set; }
public string Name { get; set; } = "";
}