CareerMatchArchive.cs

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