138 lines
5.2 KiB
C#
138 lines
5.2 KiB
C#
using PlotLine.Models;
|
|
|
|
namespace PlotLine.Services;
|
|
|
|
public sealed class CharacterAgeDisplayResult
|
|
{
|
|
public bool HasExactAge { get; init; }
|
|
public string? Label { get; init; }
|
|
public string? DisplayText { get; init; }
|
|
}
|
|
|
|
public sealed record CharacterProfileAgeDisplayResult(string DisplayText, string Context, Scene? ReferenceScene);
|
|
|
|
public static class CharacterAgeDisplayService
|
|
{
|
|
public static CharacterProfileAgeDisplayResult GetProfileAge(Character character,
|
|
IReadOnlyList<Scene> orderedScenes, IReadOnlyList<SceneCharacter> appearances)
|
|
{
|
|
var appearanceIds = appearances.Where(x => x.CharacterID == character.CharacterID).Select(x => x.SceneID).ToHashSet();
|
|
var reference = character.AgeReferenceSceneID.HasValue
|
|
? orderedScenes.FirstOrDefault(x => x.SceneID == character.AgeReferenceSceneID)
|
|
: null;
|
|
if (reference is null || !ResolveEffectiveSceneDate(reference.TimeModeName, reference.StartDateTime).HasValue)
|
|
reference = orderedScenes.FirstOrDefault(x => appearanceIds.Contains(x.SceneID)
|
|
&& ResolveEffectiveSceneDate(x.TimeModeName, x.StartDateTime).HasValue
|
|
&& (!character.BirthDate.HasValue || x.StartDateTime!.Value.Date >= character.BirthDate.Value.Date));
|
|
var age = GetAgeForScene(character, reference?.TimeModeName, reference?.StartDateTime);
|
|
return new CharacterProfileAgeDisplayResult(age.DisplayText ?? "Unknown",
|
|
age.HasExactAge ? (reference?.SceneID == character.AgeReferenceSceneID ? "At age reference scene" : "At first dated appearance")
|
|
: character.AgeAtSeriesStart.HasValue ? "Approximate age at series start"
|
|
: character.BirthDate.HasValue ? "A dated story scene is needed to calculate age." : "No birth date or approximate age recorded.",
|
|
age.HasExactAge ? reference : null);
|
|
}
|
|
|
|
public static CharacterAgeDisplayResult GetAgeForScene(SceneCharacter character, string? sceneTimeModeName, DateTime? sceneStartDateTime)
|
|
{
|
|
var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime);
|
|
if (character.BirthDate.HasValue && effectiveDate.HasValue)
|
|
{
|
|
return new CharacterAgeDisplayResult
|
|
{
|
|
HasExactAge = true,
|
|
Label = "Age in scene",
|
|
DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date)
|
|
};
|
|
}
|
|
|
|
if (character.AgeAtSeriesStart.HasValue)
|
|
{
|
|
return new CharacterAgeDisplayResult
|
|
{
|
|
Label = "Approximate age",
|
|
DisplayText = $"About {character.AgeAtSeriesStart.Value}"
|
|
};
|
|
}
|
|
|
|
return new CharacterAgeDisplayResult();
|
|
}
|
|
|
|
public static CharacterAgeDisplayResult GetAgeForScene(Character character, string? sceneTimeModeName, DateTime? sceneStartDateTime)
|
|
{
|
|
var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime);
|
|
if (character.BirthDate.HasValue && effectiveDate.HasValue)
|
|
{
|
|
return new CharacterAgeDisplayResult
|
|
{
|
|
HasExactAge = true,
|
|
Label = "Age in scene",
|
|
DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date)
|
|
};
|
|
}
|
|
|
|
if (character.AgeAtSeriesStart.HasValue)
|
|
{
|
|
return new CharacterAgeDisplayResult
|
|
{
|
|
Label = "Approximate age",
|
|
DisplayText = $"About {character.AgeAtSeriesStart.Value}"
|
|
};
|
|
}
|
|
|
|
return new CharacterAgeDisplayResult();
|
|
}
|
|
|
|
public static DateTime? ResolveEffectiveSceneDate(string? timeModeName, DateTime? startDateTime)
|
|
{
|
|
if (!startDateTime.HasValue)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return string.Equals(timeModeName, "Exact Date", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(timeModeName, "Exact DateTime", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(timeModeName, "Exact Date and Time", StringComparison.OrdinalIgnoreCase)
|
|
? startDateTime.Value.Date
|
|
: null;
|
|
}
|
|
|
|
public static string FormatExactAge(DateTime birthDate, DateTime sceneDate)
|
|
{
|
|
var (years, months) = CalculateExactAge(birthDate, sceneDate);
|
|
|
|
return months == 0
|
|
? $"{years} year{Pluralise(years)}"
|
|
: $"{years} year{Pluralise(years)}, {months} month{Pluralise(months)}";
|
|
}
|
|
|
|
public static (int Years, int Months) CalculateExactAge(DateTime birthDate, DateTime sceneDate)
|
|
{
|
|
var years = sceneDate.Year - birthDate.Year;
|
|
if (sceneDate < birthDate.AddYears(years))
|
|
{
|
|
years--;
|
|
}
|
|
|
|
if (years < 0)
|
|
{
|
|
years = 0;
|
|
}
|
|
|
|
var lastBirthday = birthDate.AddYears(years);
|
|
var months = ((sceneDate.Year - lastBirthday.Year) * 12) + sceneDate.Month - lastBirthday.Month;
|
|
if (sceneDate.Day < lastBirthday.Day)
|
|
{
|
|
months--;
|
|
}
|
|
|
|
if (months < 0)
|
|
{
|
|
months = 0;
|
|
}
|
|
|
|
return (years, months);
|
|
}
|
|
|
|
private static string Pluralise(int value) => value == 1 ? string.Empty : "s";
|
|
}
|