@ -12,6 +12,16 @@ import SwiftUI
@ Observable
final class Tournament : BaseTournament {
// l o c a l v a r i a b l e
var refreshInProgress : Bool = false
var lastTeamRefresh : Date ?
var refreshRanking : Bool = false
func shouldRefreshTeams ( ) -> Bool {
guard let lastTeamRefresh else { return true }
return lastTeamRefresh . timeIntervalSinceNow < - 60
}
@ ObservationIgnored
var navigationPath : [ Screen ] = [ ]
@ -620,7 +630,7 @@ defer {
let wcBracket = _teams . filter { $0 . wildCardBracket } . sorted ( using : _currentSelectionSorting , order : . ascending )
let groupStageSpots : Int = self . groupStageSpots ( )
var bracketSeeds : Int = min ( teamCount , _teams . count ) - groupStageSpots - wcBracket . count
var bracketSeeds : Int = teamCount - groupStageSpots - wcBracket . count
var groupStageTeamCount : Int = groupStageSpots - wcGroupStage . count
if groupStageTeamCount < 0 { groupStageTeamCount = 0 }
if bracketSeeds < 0 { bracketSeeds = 0 }
@ -663,9 +673,9 @@ defer {
return waitings
}
}
func bracketCut ( teamCount : Int ) -> Int {
return max ( 0 , teamCount - groupStageCut ( ) )
func bracketCut ( teamCount : Int , groupStageCut : Int ) -> Int {
return self . teamCount - groupStageCut
}
func groupStageCut ( ) -> Int {
@ -674,10 +684,12 @@ defer {
func cutLabel ( index : Int , teamCount : Int ? ) -> String {
let _teamCount = teamCount ? ? selectedSortedTeams ( ) . count
let bracketCut = bracketCut ( teamCount : _teamCount )
let groupStageCut = groupStageCut ( )
let bracketCut = bracketCut ( teamCount : _teamCount , groupStageCut : groupStageCut )
if index < bracketCut {
return " Tableau "
} else if index - bracketCut < groupStageCut ( ) && _teamCount > 0 {
} else if index - bracketCut < groupStageCut && _teamCount > 0 {
return " Poule "
} else {
return " Attente "
@ -687,11 +699,12 @@ defer {
func cutLabelColor ( index : Int ? , teamCount : Int ? ) -> Color {
guard let index else { return Color . gray }
let _teamCount = teamCount ? ? selectedSortedTeams ( ) . count
let bracketCut = bracketCut ( teamCount : _teamCount )
let groupStageCut = groupStageCut ( )
let bracketCut = bracketCut ( teamCount : _teamCount , groupStageCut : groupStageCut )
if index < bracketCut {
return Color . mint
} else if index - bracketCut < groupStageCut ( ) && _teamCount > 0 {
return Color . cyan
} else if index - bracketCut < groupStageCut && _teamCount > 0 {
return Color . indigo
} else {
return Color . gray
}
@ -881,8 +894,8 @@ defer {
return max ( 1 , courtCount )
}
}
func registrationIssues ( selectedTeams : [ TeamRegistration ] ) -> Int {
func registrationIssues ( selectedTeams : [ TeamRegistration ] ) async -> Int {
let players : [ PlayerRegistration ] = unsortedPlayers ( )
let callDateIssue : [ TeamRegistration ] = selectedTeams . filter { $0 . callDate != nil && isStartDateIsDifferentThanCallDate ( $0 ) }
let duplicates : [ PlayerRegistration ] = duplicates ( in : players )
@ -1187,9 +1200,9 @@ defer {
}
self . tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : teams )
}
func updateRank ( to newDate : Date ? ) async throws {
func updateRank ( to newDate : Date ? , forceRefreshLockWeight : Bool , providedSources : [ CSVParser ] ? ) async throws {
refreshRanking = true
#if DEBUG_TIME
let start = Date ( )
defer {
@ -1225,16 +1238,42 @@ defer {
let lastRankMan = monthData ? . maleUnrankedValue ? ? 0
let lastRankWoman = monthData ? . femaleUnrankedValue ? ? 0
// F e t c h o n l y t h e r e q u i r e d f i l e s
let dataURLs = SourceFileManager . shared . allFiles . filter { $0 . dateFromPath = = newDate }
guard ! dataURLs . isEmpty else { return } // E a r l y r e t u r n i f n o f i l e s f o u n d
var chunkedParsers : [ CSVParser ] = [ ]
if let providedSources {
chunkedParsers = providedSources
} else {
// F e t c h o n l y t h e r e q u i r e d f i l e s
let dataURLs = SourceFileManager . shared . allFiles . filter { $0 . dateFromPath = = newDate }
guard ! dataURLs . isEmpty else { return } // E a r l y r e t u r n i f n o f i l e s f o u n d
let sources = dataURLs . map { CSVParser ( url : $0 ) }
let sources = dataURLs . map { CSVParser ( url : $0 ) }
chunkedParsers = try await chunkAllSources ( sources : sources , size : 10000 )
}
let players = unsortedPlayers ( )
try await players . concurrentForEach { player in
let lastRank = ( player . sex = = . female ) ? lastRankWoman : lastRankMan
try await player . updateRank ( from : sources , lastRank : lastRank )
try await player . updateRank ( from : chunkedParsers , lastRank : lastRank )
player . setComputedRank ( in : self )
}
if providedSources = = nil {
try chunkedParsers . forEach { chunk in
try FileManager . default . removeItem ( at : chunk . url )
}
}
try tournamentStore ? . playerRegistrations . addOrUpdate ( contentOfs : players )
let unsortedTeams = unsortedTeams ( )
unsortedTeams . forEach { team in
team . setWeight ( from : team . players ( ) , inTournamentCategory : tournamentCategory )
if forceRefreshLockWeight {
team . lockedWeight = team . weight
}
}
try tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : unsortedTeams )
refreshRanking = false
}
@ -1278,7 +1317,7 @@ defer {
}
func hideWeight ( ) -> Bool {
return hideTeamsWeight || tournamentLevel . hideWeight ( )
return hideTeamsWeight
}
func isAnimation ( ) -> Bool {
@ -1537,6 +1576,7 @@ defer {
}
team . setWeight ( from : [ ] , inTournamentCategory : self . tournamentCategory )
team . weight += 200_000
return team
}
@ -1584,9 +1624,10 @@ defer {
return bracketTeamCount
}
func buildBracket ( ) {
func buildBracket ( minimalBracketTeamCount : Int ? = nil ) {
guard rounds ( ) . isEmpty else { return }
let roundCount = RoundRule . numberOfRounds ( forTeams : bracketTeamCount ( ) )
let roundCount = RoundRule . numberOfRounds ( forTeams : minimalBracketTeamCount ? ? bracketTeamCount ( ) )
let matchCount = RoundRule . numberOfMatches ( forTeams : minimalBracketTeamCount ? ? bracketTeamCount ( ) )
let rounds = ( 0. . < roundCount ) . map { // i n d e x 0 i s t h e f i n a l
return Round ( tournament : id , index : $0 , matchFormat : roundSmartMatchFormat ( $0 ) , loserBracketMode : loserBracketMode )
@ -1601,7 +1642,6 @@ defer {
} catch {
Logger . error ( error )
}
let matchCount = RoundRule . numberOfMatches ( forTeams : bracketTeamCount ( ) )
let matches = ( 0. . < matchCount ) . map { // 0 i s f i n a l m a t c h
let roundIndex = RoundRule . roundIndex ( fromMatchIndex : $0 )
@ -2079,12 +2119,15 @@ defer {
func updateSeedsBracketPosition ( ) async {
await removeAllSeeds ( )
await removeAllSeeds ( saveTeamsAtTheEnd : false )
let drawLogs = drawLogs ( ) . reversed ( )
let seeds = seeds ( )
for ( index , seed ) in seeds . enumerated ( ) {
if let drawLog = drawLogs . first ( where : { $0 . drawSeed = = index } ) {
drawLog . updateTeamBracketPosition ( seed )
await MainActor . run {
for ( index , seed ) in seeds . enumerated ( ) {
if let drawLog = drawLogs . first ( where : { $0 . drawSeed = = index } ) {
drawLog . updateTeamBracketPosition ( seed )
}
}
}
@ -2094,12 +2137,14 @@ defer {
Logger . error ( error )
}
}
func removeAllSeeds ( ) async {
func removeAllSeeds ( saveTeamsAtTheEnd : Bool ) async {
let teams = unsortedTeams ( )
teams . forEach ( { team in
team . bracketPosition = nil
team . _cachedRestingTime = nil
team . finalRanking = nil
team . pointsEarned = nil
} )
let allMatches = allRoundMatches ( )
let ts = allMatches . flatMap { match in
@ -2120,18 +2165,13 @@ defer {
Logger . error ( error )
}
do {
try tournamentStore ? . matches . addOrUpdate ( contentOfs : allMatches )
} catch {
Logger . error ( error )
}
do {
try tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : teams )
} catch {
Logger . error ( error )
if saveTeamsAtTheEnd {
do {
try tournamentStore ? . teamRegistrations . addOrUpdate ( contentOfs : teams )
} catch {
Logger . error ( error )
}
}
updateTournamentState ( )
}
func addNewRound ( _ roundIndex : Int ) async {
@ -2238,8 +2278,7 @@ defer {
}
// MARK: - S t a t u s
func shouldTournamentBeOver ( ) -> Bool {
func shouldTournamentBeOver ( ) async -> Bool {
#if _DEBUGING_TIME // D E B U G I N G T I M E
let start = Date ( )
defer {
@ -2263,6 +2302,45 @@ defer {
return false
}
func rankSourceShouldBeRefreshed ( ) -> Date ? {
if let mostRecentDate = SourceFileManager . shared . lastDataSourceDate ( ) , let currentRankSourceDate = rankSourceDate , currentRankSourceDate < mostRecentDate , hasEnded ( ) = = false {
return mostRecentDate
} else {
return nil
}
}
func onlineTeams ( ) -> [ TeamRegistration ] {
unsortedTeams ( ) . filter ( { $0 . hasRegisteredOnline ( ) } )
}
func shouldWarnOnlineRegistrationUpdates ( ) -> Bool {
enableOnlineRegistration && onlineTeams ( ) . isEmpty = = false && hasEnded ( ) = = false && hasStarted ( ) = = false
}
func refreshTeamList ( ) async {
guard StoreCenter . main . hasToken ( ) else { return }
guard shouldRefreshTeams ( ) , refreshInProgress = = false , enableOnlineRegistration , hasEnded ( ) = = false else { return }
await MainActor . run {
refreshInProgress = true
}
do {
try await self . tournamentStore ? . playerRegistrations . loadDataFromServerIfAllowed ( clear : true )
try await self . tournamentStore ? . teamScores . loadDataFromServerIfAllowed ( clear : true )
try await self . tournamentStore ? . teamRegistrations . loadDataFromServerIfAllowed ( clear : true )
await MainActor . run {
refreshInProgress = false
lastTeamRefresh = Date ( )
}
} catch {
Logger . error ( error )
await MainActor . run {
refreshInProgress = false
lastTeamRefresh = Date ( )
}
}
}
// MARK: -
func insertOnServer ( ) throws {