PlotDirector/PlotLine/Services/CharacterAgeDisplayService.cs

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";
}