using Nonemm.Contests.Multipliers; namespace Nonemm.Contests.Rules; /// The four parts of a Sweepstakes exchange, read out of the one box the /// operator types them into: `72 A 74 SCV`, or `72A 74SCV`, or any other order. /// N1MM reads the same box the same way, from the last word back, so a part /// typed again replaces nothing that was already found. public sealed record SweepstakesExchange(int Number, string Precedence, int Check, string Section) { private const int HighestNumber = 10000; private const int HighestCheck = 99; private static readonly IReadOnlySet Precedences = new HashSet { "A", "B", "Q", "U", "M", "S" }; public static SweepstakesExchange Parse(string typed) { string rest = typed.Trim().ToUpperInvariant(); string number = ""; string check = ""; string section = ""; string precedence = ""; bool foundSection = false; bool foundNumber = false; while (rest.Trim().Length > 0) { string word = LastWord(rest); rest = AllButLastWord(rest); string before = LastWord(rest); // "72 A" and "74 SCV" are one part typed with a space in it if ((word.Length == 1 && char.IsAsciiLetter(word[0]) && IsNumber(before)) || (word.Length is >= 2 and < 4 && word.All(char.IsAsciiLetter) && IsNumber(before))) { word = before + word; rest = AllButLastWord(rest); } if (word.Length > 1 && !IsNumber(word[^1..]) && IsNumber(word[..^1])) { if (!foundNumber) { number = word[..^1]; foundNumber = true; } precedence = precedence.Length == 0 ? PrecedenceFrom(word[^1..]) : precedence; } else if (word.Length > 3 && !IsNumber(word) && IsNumber(word[..2]) && word[2..].All(char.IsAsciiLetter)) { check = check.Length == 0 ? word[..2] : check; (section, foundSection) = SectionFrom(word[2..], section, foundSection); } else if (word.Length > 2 && !IsNumber(word) && IsNumber(word[..1]) && word[1..].All(char.IsAsciiLetter)) { check = check.Length == 0 ? word[..1] : check; (section, foundSection) = SectionFrom(word[1..], section, foundSection); } else if (IsNumber(word) && ((word.Length == 1 && word != "0") || word.Length > 2)) { number = foundNumber ? number : word; foundNumber = true; } else if (IsNumber(word) && check.Length > 0 && !foundNumber) { number = number.Length == 0 ? word : number; } else if (IsNumber(word)) { check = check.Length == 0 ? word : check; } else if (word.Length > 1) { (section, foundSection) = SectionFrom(word, section, foundSection); } else if (precedence.Length == 0) { precedence = PrecedenceFrom(word); } } return new SweepstakesExchange( Math.Min(Value(number), HighestNumber), precedence, Math.Min(Value(check), HighestCheck), section); } private static (string Section, bool Found) SectionFrom(string word, string section, bool found) => found || !ArrlSections.IsSection(word) ? (section, found) : (word, true); private static string PrecedenceFrom(string word) => Precedences.Contains(word) ? word : ""; private static string LastWord(string text) { string trimmed = text.Trim(); int space = trimmed.LastIndexOf(' '); return space < 0 ? trimmed : trimmed[(space + 1)..]; } private static string AllButLastWord(string text) { string trimmed = text.Trim(); int space = trimmed.LastIndexOf(' '); return space < 0 ? "" : trimmed[..space]; } private static bool IsNumber(string text) => text.Length > 0 && text.All(char.IsAsciiDigit); private static int Value(string text) => int.TryParse(text, out int number) ? number : 0; }