namespace HSchool.People; internal static class FamilyFactory { public static (Family Family, List Members) Create( DefCatalog catalog, NameSetDef names, int schoolSeed, FamilyPlan plan, DateTime yearStart, DateTime asOf) { var rng = new Random(Seed.Mix(schoolSeed, plan.FamilyIndex, Seed.AppearanceSalt)); var familyId = $"f{plan.FamilyIndex}"; var surname = names.Surnames[rng.Next(names.Surnames.Count)]; var childDrafts = new ChildDraft[plan.Seats.Count]; var usedGiven = new HashSet(StringComparer.Ordinal); for (var i = 0; i < plan.Seats.Count; i++) { var seat = plan.Seats[i]; var female = rng.Next(2) == 0; var given = PickGiven(female ? names.FemaleGiven : names.MaleGiven, rng, usedGiven); var (first, last) = SchoolYears.BirthWindow(yearStart, seat.Year); childDrafts[i] = new ChildDraft(seat, female, given, SchoolYears.RandomInRange(rng, first, last)); } var fatherGiven = PickGiven(names.MaleGiven, rng); var motherGiven = PickGiven(names.FemaleGiven, rng); var fatherPatronymicSource = PickGiven(names.MaleGiven, rng); var motherPatronymicSource = PickGiven(names.MaleGiven, rng); DateTime motherBirth; DateTime fatherBirth; if (childDrafts.Length == 0) { motherBirth = asOf.AddYears(-(28 + rng.Next(28))).AddDays(-rng.Next(365)); fatherBirth = motherBirth.AddDays(rng.Next(-5 * 365, (5 * 365) + 1)); } else { var oldestBirth = childDrafts[0].Birth; foreach (var child in childDrafts) { if (child.Birth < oldestBirth) { oldestBirth = child.Birth; } } var motherYearsOlder = 22 + rng.Next(17); motherBirth = oldestBirth.AddYears(-motherYearsOlder).AddDays(-rng.Next(30)); fatherBirth = motherBirth.AddDays(rng.Next(-5 * 365, (5 * 365) + 1)); } var members = new List(); var fatherId = $"{familyId}.p0"; var motherId = $"{familyId}.p1"; members.Add( RollAdult( catalog, names, rng, fatherId, familyId, female: false, fatherBirth, asOf, surname, fatherGiven, NameGrammar.Patronymic(fatherPatronymicSource.Form, female: false, names.PatronymicRule), isParent: childDrafts.Length > 0)); members.Add( RollAdult( catalog, names, rng, motherId, familyId, female: true, motherBirth, asOf, surname, motherGiven, NameGrammar.Patronymic(motherPatronymicSource.Form, female: true, names.PatronymicRule), isParent: childDrafts.Length > 0)); var childIds = new List(childDrafts.Length); for (var i = 0; i < childDrafts.Length; i++) { var draft = childDrafts[i]; var id = $"{familyId}.c{i}"; childIds.Add(id); members.Add( RollChild( catalog, names, rng, id, familyId, draft, asOf, surname, fatherGiven.Form)); } var family = new Family(familyId, [fatherId, motherId], childIds); return (family, members); } public static (Family Family, List Members) CreateStaffOnly( DefCatalog catalog, NameSetDef names, int schoolSeed, int familyIndex, DateTime asOf) { var empty = new FamilyPlan(familyIndex, []); return Create(catalog, names, schoolSeed, empty, SchoolYears.StartOn(asOf), asOf); } private static Person RollAdult( DefCatalog catalog, NameSetDef names, Random rng, string id, string familyId, bool female, DateTime birth, DateTime asOf, SurnameEntry surname, GivenNameEntry given, string patronymic, bool isParent) { var age = SchoolYears.AgeYears(birth, asOf); var roles = isParent ? new[] { PersonRoles.Parent } : new[] { PersonRoles.Staff }; return FinishPerson( catalog, names, rng, id, familyId, female, birth, asOf, age, surname, given, patronymic, roles, isStudent: false, isParent, classId: null); } private static Person RollChild( DefCatalog catalog, NameSetDef names, Random rng, string id, string familyId, ChildDraft draft, DateTime asOf, SurnameEntry surname, string fatherGiven) { var age = SchoolYears.AgeYears(draft.Birth, asOf); var patronymic = NameGrammar.Patronymic(fatherGiven, draft.Female, names.PatronymicRule); return FinishPerson( catalog, names, rng, id, familyId, draft.Female, draft.Birth, asOf, age, surname, draft.Given, patronymic, [PersonRoles.Student], isStudent: true, isParent: false, draft.Seat.ClassId); } private static Person FinishPerson( DefCatalog catalog, NameSetDef names, Random rng, string id, string familyId, bool female, DateTime birth, DateTime asOf, int age, SurnameEntry surname, GivenNameEntry given, string patronymic, IReadOnlyList roles, bool isStudent, bool isParent, string? classId) { var (numbers, choices) = PersonSampler.Body(catalog, rng, female, age); var traits = PersonSampler.Traits(catalog, rng, roles, age); var skills = PersonSampler.Skills(catalog, rng, age, choices, traits); var needs = PersonSampler.Needs(catalog); var name = new PersonName( given.Form, female ? surname.Female : surname.Male, patronymic, GivenTable(given, names.DefaultGivenDeclension), SurnameTable(surname, female, names.DefaultSurnameDeclension), PatronymicTable(patronymic, female)); return new Person { Id = id, FamilyId = familyId, Female = female, BirthDate = DateTime.SpecifyKind(birth, DateTimeKind.Utc), Name = name, IsStudent = isStudent, IsStaff = false, IsParent = isParent, ClassId = classId, Numbers = numbers, Choices = choices, Skills = skills, Traits = traits, Needs = needs, }; } private static GivenNameEntry PickGiven( IReadOnlyList pool, Random rng, HashSet? used = null) { if (pool.Count == 0) { throw new InvalidOperationException("Name set has no given names for this sex."); } for (var attempt = 0; attempt < 16; attempt++) { var pick = pool[rng.Next(pool.Count)]; if (used is null || used.Add(pick.Form) || used.Count >= pool.Count) { return pick; } } return pool[rng.Next(pool.Count)]; } private static CaseTable GivenTable(GivenNameEntry entry, string defaultModel) => new() { Nom = NameGrammar.InflectGiven(entry, GrammaticalCase.Nominative, defaultModel), Gen = NameGrammar.InflectGiven(entry, GrammaticalCase.Genitive, defaultModel), Dat = NameGrammar.InflectGiven(entry, GrammaticalCase.Dative, defaultModel), Acc = NameGrammar.InflectGiven(entry, GrammaticalCase.Accusative, defaultModel), Ins = NameGrammar.InflectGiven(entry, GrammaticalCase.Instrumental, defaultModel), Pre = NameGrammar.InflectGiven(entry, GrammaticalCase.Prepositional, defaultModel), }; private static CaseTable SurnameTable(SurnameEntry entry, bool female, string defaultModel) => new() { Nom = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Nominative, defaultModel), Gen = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Genitive, defaultModel), Dat = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Dative, defaultModel), Acc = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Accusative, defaultModel), Ins = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Instrumental, defaultModel), Pre = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Prepositional, defaultModel), }; private static CaseTable PatronymicTable(string nominative, bool female) => new() { Nom = nominative, Gen = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Genitive), Dat = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Dative), Acc = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Accusative), Ins = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Instrumental), Pre = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Prepositional), }; private readonly record struct ChildDraft( PupilSeat Seat, bool Female, GivenNameEntry Given, DateTime Birth); }