The code archives football match data for a club. It defines structures for players and individual match reports. The ClubCareer class manages the match history, generating unique keys and normalizing old data formats.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Socer;
public sealed class ArchivedFootballer
{
public string Name {get;set;}="";
public string CatalogId {get;set;}="";
public int Number {get;set;}
public int Overall {get;set;}
}
public sealed class ArchivedClubMatch
{
public string Key {get;set;}="";
public int Season {get;set;}
public int Division {get;set;}
public int Round {get;set;}
public int Sequence {get;set;}
public string Competition {get;set;}="";
public bool Cup {get;set;}
public string HomeName {get;set;}="";
public string AwayName {get;set;}="";
public string HomeCrest {get;set;}="";
public string AwayCrest {get;set;}="";
public int HomeGoals {get;set;}
public int AwayGoals {get;set;}
public bool Penalties {get;set;}
public int HomePenalties {get;set;}
public int AwayPenalties {get;set;}
public int OwnSide {get;set;}
public ClubMatchStatistics Statistics {get;set;}
public List<ArchivedFootballer> HomePlayers {get;set;}=new();
public List<ArchivedFootballer> AwayPlayers {get;set;}=new();
}
public sealed partial class ClubCareer
{
public List<ArchivedClubMatch> MatchHistory {get;set;}=new();
bool archiveKeyMigrationDone;
[JsonIgnore] public IEnumerable<ArchivedClubMatch> MatchReports=>
(MatchHistory??new()).OrderByDescending(r=>r.Season).ThenByDescending(r=>r.Sequence);
// The second top-tier cup itself is named "league". Its fixture IDs must
// therefore live in a separate namespace from ordinary league fixtures.
string ReportKey(ClubFixture f,CupTournament cup)=>cup==null?$"{Season}:league:{f.Id}":$"{Season}:cup:{cup.Id}:{f.Id}";
void NormalizeMatchHistoryKeys()
{
MatchHistory??=new();
if(archiveKeyMigrationDone)return;
foreach(var report in MatchHistory.Where(r=>r.Cup))
{
string[] old=(report.Key??"").Split(':');
// Cup is persisted independently of the old ambiguous key. Re-key
// its snapshot without reconstructing or changing any match data.
if(old.Length==3)report.Key=$"{old[0]}:cup:{old[1]}:{old[2]}";
}
archiveKeyMigrationDone=true;
}
public ArchivedClubMatch MatchReport(ClubFixture f,CupTournament cup=null)
{
if(f==null || !f.Played || (f.Home!=0 && f.Away!=0))return null;
NormalizeMatchHistoryKeys();
cup??=TournamentFor(f);
return MatchHistory?.FirstOrDefault(r=>r.Key==ReportKey(f,cup))??CreateMatchReport(f,cup,null,0);
}
ArchivedClubMatch CreateMatchReport(ClubFixture f,CupTournament cup,MatchSimulation match,int homeSide)
{
List<ArchivedFootballer> Lineup(int side)=>match==null?new():match.Players.Where(p=>p.Team==side)
.Select(p=>new ArchivedFootballer{Name=p.Profile.Name,CatalogId=p.Profile.CatalogId,Number=p.Number,Overall=p.Profile.Overall}).ToList();
return new(){Key=ReportKey(f,cup),Season=Season,Division=Division,Round=f.Round,Sequence=f.PlayedSequence,
Competition=cup?.Name??DivisionName,Cup=cup!=null,
HomeName=TeamName(f.Home,cup),AwayName=TeamName(f.Away,cup),
HomeCrest=FixtureTeamCrest(f,f.Home),AwayCrest=FixtureTeamCrest(f,f.Away),
HomeGoals=f.HomeGoals,AwayGoals=f.AwayGoals,OwnSide=f.Home==0?0:1,
Penalties=f.Penalties,HomePenalties=f.HomePenalties,AwayPenalties=f.AwayPenalties,
Statistics=f.Statistics==null?null:JsonSerializer.Deserialize<ClubMatchStatistics>(JsonSerializer.Serialize(f.Statistics)),
HomePlayers=Lineup(homeSide),AwayPlayers=Lineup(1-homeSide)};
}
void RecordPlayedMatch(ClubFixture f,CupTournament cup,MatchSimulation match,int homeSide)
{
if(!f.Played || (f.Home!=0 && f.Away!=0))return;
NormalizeMatchHistoryKeys();
if(MatchHistory.Any(r=>r.Key==ReportKey(f,cup)))return;
MatchHistory.Add(CreateMatchReport(f,cup,match,homeSide));
}
public void InitializeMatchHistory()
{
NormalizeMatchHistoryKeys();
// Existing results are retained. Missing old statistics/lineups stay unknown.
foreach(var f in League.Where(f=>f.Played && (f.Home==0 || f.Away==0)))RecordPlayedMatch(f,null,null,0);
foreach(var cup in Tournaments)
foreach(var f in cup.Games.Where(f=>f.Played && (f.Home==0 || f.Away==0)))RecordPlayedMatch(f,cup,null,0);
}
}