using System.Text.Json.Serialization;
namespace MultiversalDiplomacy.Model;
///
/// Tracks the relations between seasons.
///
public class Timelines(int next, Dictionary pasts)
{
private static readonly char[] Letters = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
];
///
/// Convert a string timeline identifier to its serial number.
///
/// Timeline identifier.
/// Integer.
public static int StringToInt(string timeline)
{
int result = Array.IndexOf(Letters, timeline[0]);
for (int i = 1; i < timeline.Length; i++) {
// The result is incremented by one because timeline designations are not a true base26 system.
// The "ones digit" maps a-z 0-25, but the "tens digit" maps a to 1, so "10" (26) is "aa" and not "a0"
result = (result + 1) * 26;
result += Array.IndexOf(Letters, timeline[i]);
}
return result;
}
///
/// Convert a timeline serial number to its string identifier.
///
/// Integer.
/// Timeline identifier.
public static string IntToString(int serial) {
static int downshift(int i ) => (i - (i % 26)) / 26;
IEnumerable result = [Letters[serial % 26]];
for (int remainder = downshift(serial); remainder > 0; remainder = downshift(remainder) - 1) {
// We subtract 1 after downshifting for the same reason we add 1 above after upshifting.
result = result.Prepend(Letters[(remainder % 26 + 25) % 26]);
}
return new string(result.ToArray());
}
///
/// Extract the timeline and turn components of a season designation.
///
/// A timeline-turn season designation.
/// The timeline and turn components.
///
public static (string timeline, int turn) SplitKey(string seasonKey)
{
int i = 1;
for (; !char.IsAsciiDigit(seasonKey[i]) && i < seasonKey.Length; i++);
return int.TryParse(seasonKey.AsSpan(i), out int turn)
? (seasonKey[..i], turn)
: throw new FormatException($"Could not parse turn from {seasonKey}");
}
///
/// The next timeline to be created.
///
public int Next { get; private set; } = next;
///
/// Map of season designations to their parent seasons.
/// The set of keys here is the set of all seasons in the multiverse.
///
public Dictionary Pasts { get; } = pasts;
///
/// Create a new multiverse with an initial season.
///
public static Timelines Create()
=> new(StringToInt(Season.First.Timeline) + 1, new() { {Season.First.Key, null} });
///
/// Create a continuation of a season if it has no futures, otherwise create a fork.
///
public Timelines WithNewSeason(Season past, out Season future)
{
int next;
(next, future) = GetFutureKeys(past).Any()
? (Next + 1, new Season(IntToString(Next), past.Turn + 1))
: (Next, new Season(past.Timeline, past.Turn + 1));
return new Timelines(next, new(Pasts.Append(new KeyValuePair(future.Key, past))));
}
///
/// Create a continuation of a season if it has no futures, otherwise create a fork.
///
public Timelines WithNewSeason(string past, out Season future) => WithNewSeason(new Season(past), out future);
///
/// Get all seasons that are immediate futures of a season.
///
/// A season.
/// The immediate futures of the season.
public IEnumerable GetFutureKeys(Season season)
=> Pasts.Where(kvp => kvp.Value is Season future && future == season).Select(kvp => kvp.Key);
///
/// Get all seasons that are immediate futures of a season.
///
/// A season.
/// The immediate futures of the season.
public IEnumerable GetFutures(Season season) => GetFutureKeys(season).Select(key => new Season(key));
///
/// Returns the first season in this season's timeline. The first season is the
/// root of the first timeline. The earliest season in each alternate timeline is
/// the root of that timeline.
///
public Season GetTimelineRoot(Season season)
{
Console.WriteLine($"GetTimelineRoot({season.Key})");
return Pasts[season.Key] is Season past && season.Timeline == past.Timeline
? GetTimelineRoot(past)
: season;
}
///
/// Returns the first season in this season's timeline. The first season is the
/// root of the first timeline. The earliest season in each alternate timeline is
/// the root of that timeline.
///
public Season GetTimelineRoot(string season) => GetTimelineRoot(new Season(season));
///
/// Returns whether a season is in an adjacent timeline to another season.
/// Seasons are considered to be in adjacent timelines if they are in the same timeline,
/// one is in a timeline that branched from the other's timeline, or both are in timelines
/// that branched from the same point.
///
public bool InAdjacentTimeline(Season one, Season two)
{
// Timelines are adjacent to themselves. Early out in that case.
if (one == two) return true;
// If the timelines aren't identical, one of them isn't the initial trunk.
// They can still be adjacent if one of them branched off of the other, or
// if they both branched off of the same point.
Season rootOne = GetTimelineRoot(one);
Season rootTwo = GetTimelineRoot(two);
bool oneForked = Pasts[rootOne.Key] is Season originOne && originOne.Timeline == two.Timeline;
bool twoForked = Pasts[rootTwo.Key] is Season originTwo && originTwo.Timeline == one.Timeline;
bool bothForked = Pasts[rootOne.Key] == Pasts[rootTwo.Key];
return oneForked || twoForked || bothForked;
}
}