Files
BCSH1-TournamentApp/TournamentOrganizer/ViewModels/MatchesViewModel.cs
T
2026-05-06 16:21:50 +02:00

527 lines
14 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using TournamentOrganizer;
using TournamentOrganizer.Models;
namespace TournamentOrganizer.ViewModels;
public partial class MatchesViewModel : ViewModelBase
{
private readonly TournamentContext _context;
[ObservableProperty]
private ObservableCollection<EventOption> _availableEvents = [];
[ObservableProperty]
private EventOption? _selectedEvent;
[ObservableProperty]
private ObservableCollection<TournamentOption> _availableTournaments = [];
[ObservableProperty]
private TournamentOption? _selectedTournament;
[ObservableProperty]
private ObservableCollection<MatchDisplay> _matches = [];
[ObservableProperty]
private MatchDisplay? _selectedMatch;
[ObservableProperty]
private ObservableCollection<TeamParticipantDisplay> _matchTeams = [];
[ObservableProperty]
private ObservableCollection<RoundDisplay> _rounds = [];
[ObservableProperty]
private RoundDisplay? _selectedRound;
[ObservableProperty]
private string _eventFilterSearch = string.Empty;
[ObservableProperty]
private string _tournamentFilterSearch = string.Empty;
[ObservableProperty]
private bool _isEventDropdownOpen;
[ObservableProperty]
private bool _isTournamentDropdownOpen;
[ObservableProperty]
private string _statusMessage = string.Empty;
private readonly ObservableCollection<EventOption> _filteredEvents = [];
public ObservableCollection<EventOption> FilteredEvents => _filteredEvents;
private readonly ObservableCollection<TournamentOption> _filteredTournaments = [];
public ObservableCollection<TournamentOption> FilteredTournaments => _filteredTournaments;
public bool IsTournamentFilterEnabled => SelectedEvent != null;
public bool CanCreateRound => Rounds.Count == 0 || Rounds.All(r => r.State == RoundState.Finished);
public bool CanStartRound => SelectedRound != null && SelectedRound.State == RoundState.Waiting;
public bool CanAwardVictory => SelectedRound != null && SelectedRound.State == RoundState.Started;
public MatchesViewModel()
{
_context = new TournamentContext();
}
public async Task LoadEvents()
{
var events = await _context.Events
.Include(e => e.Tournaments)
.ThenInclude(t => t.Game)
.ToListAsync();
AvailableEvents.Clear();
_filteredEvents.Clear();
foreach (var e in events)
{
var option = new EventOption(e);
AvailableEvents.Add(option);
_filteredEvents.Add(option);
}
UpdateAvailableTournaments();
UpdateFilteredTournaments();
}
private void UpdateAvailableTournaments()
{
AvailableTournaments.Clear();
if (SelectedEvent == null)
{
return;
}
foreach (var t in SelectedEvent.Event.Tournaments)
{
AvailableTournaments.Add(new TournamentOption(t));
}
}
private async Task LoadMatches()
{
if (SelectedTournament == null)
{
Matches.Clear();
return;
}
int? selectedMatchId = SelectedMatch?.Id;
var matches = await _context.Matches
.Where(m => m.TournamentId == SelectedTournament.Tournament.Id)
.Include(m => m.Teams)
.ThenInclude(tp => tp.Team)
.Include(m => m.Rounds)
.ToListAsync();
Matches.Clear();
MatchDisplay? newSelectedMatch = null;
foreach (var match in matches)
{
var matchDisplay = new MatchDisplay(match);
Matches.Add(matchDisplay);
if (match.Id == selectedMatchId)
{
newSelectedMatch = matchDisplay;
}
}
if (newSelectedMatch != null)
{
SelectedMatch = newSelectedMatch;
}
}
private async Task LoadMatchDetails()
{
if (SelectedMatch == null)
{
MatchTeams.Clear();
Rounds.Clear();
_lastLoadedMatchId = null;
return;
}
_lastLoadedMatchId = SelectedMatch.Id;
var match = await _context.Matches
.Where(m => m.Id == SelectedMatch.Id)
.Include(m => m.Teams)
.ThenInclude(tp => tp.Team)
.Include(m => m.Rounds)
.FirstOrDefaultAsync();
if (match == null)
{
MatchTeams.Clear();
Rounds.Clear();
return;
}
MatchTeams.Clear();
foreach (var team in match.Teams)
{
MatchTeams.Add(new TeamParticipantDisplay(team));
}
Rounds.Clear();
foreach (var round in match.Rounds)
{
Rounds.Add(new RoundDisplay(round));
}
OnPropertyChanged(nameof(CanCreateRound));
OnPropertyChanged(nameof(CanStartRound));
OnPropertyChanged(nameof(CanAwardVictory));
}
partial void OnSelectedEventChanged(EventOption? value)
{
OnPropertyChanged(nameof(IsTournamentFilterEnabled));
UpdateAvailableTournaments();
UpdateFilteredTournaments();
SelectedTournament = null;
Matches.Clear();
MatchTeams.Clear();
Rounds.Clear();
}
partial void OnSelectedTournamentChanged(TournamentOption? value)
{
_ = LoadMatches();
MatchTeams.Clear();
Rounds.Clear();
}
private int? _lastLoadedMatchId;
partial void OnSelectedMatchChanged(MatchDisplay? value)
{
if (value?.Id == _lastLoadedMatchId)
return;
_ = LoadMatchDetails();
}
partial void OnSelectedRoundChanged(RoundDisplay? value)
{
OnPropertyChanged(nameof(CanStartRound));
OnPropertyChanged(nameof(CanAwardVictory));
}
partial void OnEventFilterSearchChanged(string value) => UpdateFilteredEvents();
partial void OnTournamentFilterSearchChanged(string value) => UpdateFilteredTournaments();
private void UpdateFilteredEvents()
{
_filteredEvents.Clear();
foreach (var e in AvailableEvents.Where(e => string.IsNullOrWhiteSpace(EventFilterSearch) || e.DisplayName.ToLower().Contains(EventFilterSearch.ToLower())))
{
_filteredEvents.Add(e);
}
}
private void UpdateFilteredTournaments()
{
_filteredTournaments.Clear();
if (SelectedEvent == null)
{
return;
}
foreach (var t in AvailableTournaments.Where(t => string.IsNullOrWhiteSpace(TournamentFilterSearch) || t.DisplayName.ToLower().Contains(TournamentFilterSearch.ToLower())))
{
_filteredTournaments.Add(t);
}
}
public void SelectEventFilter(EventOption option)
{
SelectedEvent = option;
IsEventDropdownOpen = false;
EventFilterSearch = string.Empty;
}
public void ClearEventFilter()
{
SelectedEvent = null;
EventFilterSearch = string.Empty;
}
public void SelectTournamentFilter(TournamentOption option)
{
SelectedTournament = option;
IsTournamentDropdownOpen = false;
TournamentFilterSearch = string.Empty;
}
public void ClearTournamentFilter()
{
SelectedTournament = null;
TournamentFilterSearch = string.Empty;
}
[RelayCommand]
private async Task CreateRound()
{
if (SelectedMatch == null)
{
StatusMessage = "Select a match first";
return;
}
var match = await _context.Matches
.Where(m => m.Id == SelectedMatch.Id)
.Include(m => m.Rounds)
.FirstOrDefaultAsync();
if (match == null)
{
StatusMessage = "Match not found";
return;
}
if (match.Rounds.Any(r => r.State != RoundState.Finished))
{
StatusMessage = "All rounds must be finished before creating a new round";
return;
}
var round = new Round
{
MatchId = match.Id,
Match = match,
State = RoundState.Waiting,
Players = []
};
match.Rounds.Add(round);
await _context.SaveChangesAsync();
StatusMessage = "Round created";
await LoadMatches();
await LoadMatchDetails();
}
[RelayCommand]
private async Task StartRound()
{
if (SelectedRound == null)
{
StatusMessage = "Select a round to start";
return;
}
var round = await _context.Rounds.FindAsync(SelectedRound.Id);
if (round == null)
{
StatusMessage = "Round not found";
return;
}
if (round.State != RoundState.Waiting)
{
StatusMessage = "Round must be in Waiting state to start";
return;
}
round.State = RoundState.Started;
await _context.SaveChangesAsync();
StatusMessage = "Round started";
await LoadMatches();
await LoadMatchDetails();
}
[RelayCommand]
private async Task AwardVictory(object? parameter)
{
if (SelectedRound == null)
{
StatusMessage = "Select a round first";
return;
}
if (SelectedMatch == null)
{
StatusMessage = "No match selected";
return;
}
if (SelectedRound.State != RoundState.Started)
{
StatusMessage = "Round must be in Started state to award victory";
return;
}
if (!int.TryParse(parameter?.ToString(), out int teamIndex))
{
StatusMessage = "Invalid team index";
return;
}
var round = await _context.Rounds.FindAsync(SelectedRound.Id);
if (round == null)
{
StatusMessage = "Round not found";
return;
}
round.State = RoundState.Finished;
var match = await _context.Matches
.Where(m => m.Id == SelectedMatch.Id)
.Include(m => m.Teams)
.FirstOrDefaultAsync();
if (match != null && teamIndex >= 0 && teamIndex < match.Teams.Count)
{
match.Teams[teamIndex].Score++;
}
await _context.SaveChangesAsync();
// Promote winner and loser to next matches
await PromoteTeams(match);
await _context.SaveChangesAsync();
var teamName = teamIndex < MatchTeams.Count ? MatchTeams[teamIndex].TeamName : "Unknown";
StatusMessage = $"Victory awarded to {teamName}";
await LoadMatches();
await LoadMatchDetails();
}
private async Task PromoteTeams(Match? match)
{
if (match == null || match.Teams.Count < 2)
return;
// Determine winner (higher score) and loser
var sortedTeams = match.Teams.OrderByDescending(t => t.Score).ToList();
var winner = sortedTeams[0];
var loser = sortedTeams[1];
// Promote winner to WinnerMatch
if (match.WinnerMatchId.HasValue)
{
var winnerMatch = await _context.Matches.FindAsync(match.WinnerMatchId.Value);
if (winnerMatch != null && !winnerMatch.Teams.Any(t => t.TeamId == winner.TeamId))
{
winnerMatch.Teams.Add(new TeamParticipant
{
TeamId = winner.TeamId,
Seed = winner.Seed,
Score = 0,
Team = winner.Team,
MatchId = winnerMatch.Id,
Round = null!
});
// Check if winnerMatch now has a bye (only 1 team) and needs recursive promotion
if (winnerMatch.Teams.Count == 1 && winnerMatch.WinnerMatchId.HasValue)
{
await PromoteRecursive(winnerMatch);
}
}
}
// Promote loser to LoserMatch (for double elimination)
// Only if this match has a LoserMatchId (meaning it's a winner bracket match)
if (match.LoserMatchId.HasValue)
{
var loserMatch = await _context.Matches.FindAsync(match.LoserMatchId.Value);
if (loserMatch != null && !loserMatch.Teams.Any(t => t.TeamId == loser.TeamId))
{
loserMatch.Teams.Add(new TeamParticipant
{
TeamId = loser.TeamId,
Seed = loser.Seed,
Score = 0,
Team = loser.Team,
MatchId = loserMatch.Id,
Round = null!
});
// Check if loserMatch now has a bye and needs recursive promotion
if (loserMatch.Teams.Count == 1 && loserMatch.WinnerMatchId.HasValue)
{
await PromoteRecursive(loserMatch);
}
}
}
}
private async Task PromoteRecursive(Match match)
{
if (match.Teams.Count != 1 || !match.WinnerMatchId.HasValue)
return;
var team = match.Teams.First();
var nextMatch = await _context.Matches.FindAsync(match.WinnerMatchId.Value);
if (nextMatch != null && !nextMatch.Teams.Any(t => t.TeamId == team.TeamId))
{
nextMatch.Teams.Add(new TeamParticipant
{
TeamId = team.TeamId,
Seed = team.Seed,
Score = 0,
Team = team.Team,
MatchId = nextMatch.Id,
Round = null!
});
// Continue recursive promotion if needed
if (nextMatch.Teams.Count == 1 && nextMatch.WinnerMatchId.HasValue)
{
await PromoteRecursive(nextMatch);
}
}
}
}
public class TeamParticipantDisplay
{
public int TeamId { get; set; }
public string TeamName { get; set; } = string.Empty;
public int Score { get; set; }
public int Seed { get; set; }
public TeamParticipantDisplay() { }
public TeamParticipantDisplay(TeamParticipant tp)
{
TeamId = tp.TeamId;
TeamName = tp.Team.Name;
Score = tp.Score;
Seed = tp.Seed;
}
}
public class RoundDisplay
{
public int Id { get; set; }
public RoundState State { get; set; }
public RoundDisplay() { }
public RoundDisplay(Round round)
{
Id = round.Id;
State = round.State;
}
}