using System;
using System.Collections.Generic;
using System.Linq;
using static Games.Common.IO.Strings;
namespace Games.Common.IO;
///
/// Reads from input and assembles a given number of values, or tokens, possibly over a number of input lines.
///
internal class TokenReader
{
private readonly TextIO _io;
private readonly Predicate _isTokenValid;
private TokenReader(TextIO io, Predicate isTokenValid)
{
_io = io;
_isTokenValid = isTokenValid ?? (t => true);
}
///
/// Creates a which reads string tokens.
///
/// A instance.
/// The new instance.
public static TokenReader ForStrings(TextIO io) => new(io, t => true);
///
/// Creates a which reads tokens and validates that they can be parsed as numbers.
///
/// A instance.
/// The new instance.
public static TokenReader ForNumbers(TextIO io) => new(io, t => t.IsNumber);
///
/// Reads valid tokens from one or more input lines and builds a list with the required quantity.
///
/// The string used to prompt the user for input.
/// The number of tokens required.
/// The sequence of tokens read.
public IEnumerable ReadTokens(string prompt, uint quantityNeeded)
{
if (quantityNeeded == 0)
{
throw new ArgumentOutOfRangeException(
nameof(quantityNeeded),
$"'{nameof(quantityNeeded)}' must be greater than zero.");
}
var tokens = new List();
while (tokens.Count < quantityNeeded)
{
tokens.AddRange(ReadValidTokens(prompt, quantityNeeded - (uint)tokens.Count));
prompt = "?";
}
return tokens;
}
///
/// Reads a line of tokens, up to , and rejects the line if any are invalid.
///
/// The string used to prompt the user for input.
/// The maximum number of tokens to read.
/// The sequence of tokens read.
private IEnumerable ReadValidTokens(string prompt, uint maxCount)
{
while (true)
{
var tokensValid = true;
var tokens = new List();
foreach (var token in ReadLineOfTokens(prompt, maxCount))
{
if (!_isTokenValid(token))
{
_io.WriteLine(NumberExpected);
tokensValid = false;
prompt = "";
break;
}
tokens.Add(token);
}
if (tokensValid) { return tokens; }
}
}
///
/// Lazily reads up to tokens from an input line.
///
/// The string used to prompt the user for input.
/// The maximum number of tokens to read.
///
private IEnumerable ReadLineOfTokens(string prompt, uint maxCount)
{
var tokenCount = 0;
foreach (var token in Tokenizer.ParseTokens(_io.ReadLine(prompt)))
{
if (++tokenCount > maxCount)
{
_io.WriteLine(ExtraInput);
break;
}
yield return token;
}
}
}