You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
9.8 KiB
328 lines
9.8 KiB
//
|
|
// TeamRegistration.swift
|
|
// Padel Tournament
|
|
//
|
|
// Created by razmig on 10/03/2024.
|
|
//
|
|
|
|
import Foundation
|
|
import LeStorage
|
|
|
|
@Observable
|
|
class TeamRegistration: ModelObject, Storable {
|
|
static func resourceName() -> String { "team-registrations" }
|
|
|
|
var id: String = Store.randomId()
|
|
var tournament: String
|
|
var groupStage: String?
|
|
var registrationDate: Date?
|
|
var callDate: Date?
|
|
var bracketPosition: Int?
|
|
var groupStagePosition: Int? //todo devrait être non nil ?
|
|
var comment: String?
|
|
var source: String?
|
|
var sourceValue: String?
|
|
var logo: String?
|
|
var name: String?
|
|
|
|
var walkOut: Bool = false
|
|
var wildCardBracket: Bool = false
|
|
var wildCardGroupStage: Bool = false
|
|
var category: Int?
|
|
var weight: Int = 0
|
|
var lockWeight: Int?
|
|
var confirmationDate: Date?
|
|
var qualified: Bool = false
|
|
|
|
internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, category: Int? = nil) {
|
|
self.tournament = tournament
|
|
self.groupStage = groupStage
|
|
self.registrationDate = registrationDate
|
|
self.callDate = callDate
|
|
self.bracketPosition = bracketPosition
|
|
self.groupStagePosition = groupStagePosition
|
|
self.comment = comment
|
|
self.source = source
|
|
self.sourceValue = sourceValue
|
|
self.logo = logo
|
|
self.name = name
|
|
self.category = category
|
|
}
|
|
|
|
func isSeedable() -> Bool {
|
|
bracketPosition == nil && groupStage == nil
|
|
}
|
|
|
|
func setSeedPosition(inSpot match: Match, slot: TeamPosition?, opposingSeeding: Bool) {
|
|
let matchIndex = match.index
|
|
let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex)
|
|
let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound)
|
|
let isUpper = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) < (numberOfMatches / 2)
|
|
var teamPosition = slot ?? (isUpper ? .one : .two)
|
|
if opposingSeeding {
|
|
teamPosition = slot ?? (isUpper ? .two : .one)
|
|
}
|
|
match.previousMatch(teamPosition)?.disableMatch()
|
|
bracketPosition = matchIndex * 2 + teamPosition.rawValue
|
|
}
|
|
|
|
var initialWeight: Int {
|
|
lockWeight ?? weight
|
|
}
|
|
|
|
func called() -> Bool {
|
|
callDate != nil
|
|
}
|
|
|
|
func confirmed() -> Bool {
|
|
confirmationDate != nil
|
|
}
|
|
|
|
func isImported() -> Bool {
|
|
unsortedPlayers().allSatisfy({ $0.isImported() })
|
|
}
|
|
|
|
func isWildCard() -> Bool {
|
|
wildCardBracket || wildCardGroupStage
|
|
}
|
|
|
|
func isPlaying() -> Bool {
|
|
currentMatch() != nil
|
|
}
|
|
|
|
func currentMatch() -> Match? {
|
|
teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() })
|
|
}
|
|
|
|
func teamScores() -> [TeamScore] {
|
|
Store.main.filter(isIncluded: { $0.teamRegistration == id })
|
|
}
|
|
|
|
var tournamentCategory: TournamentCategory {
|
|
get {
|
|
TournamentCategory(rawValue: category ?? 0) ?? .men
|
|
}
|
|
set {
|
|
category = newValue.rawValue
|
|
}
|
|
}
|
|
|
|
@objc
|
|
var canonicalName: String {
|
|
players().map { $0.canonicalName }.joined(separator: " ")
|
|
}
|
|
|
|
func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool {
|
|
guard let codeClubOrClubName else { return true }
|
|
return unsortedPlayers().anySatisfy({
|
|
$0.clubName?.contains(codeClubOrClubName) == true || $0.clubName?.contains(codeClubOrClubName) == true
|
|
})
|
|
}
|
|
|
|
func updateWeight() {
|
|
setWeight(from: self.players())
|
|
}
|
|
|
|
func teamLabel(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
switch displayStyle {
|
|
case .wide:
|
|
unsortedPlayers().map { $0.playerLabel(displayStyle) }.joined(separator: " & ")
|
|
case .short:
|
|
unsortedPlayers().map { $0.playerLabel(.wide) }.joined(separator: "\n")
|
|
}
|
|
}
|
|
|
|
func index(in teams: [TeamRegistration]) -> Int? {
|
|
teams.firstIndex(where: { $0.id == id })
|
|
}
|
|
|
|
func formattedSeed(in teams: [TeamRegistration]) -> String {
|
|
if let index = index(in: teams) {
|
|
return "#\(index + 1)"
|
|
} else {
|
|
return "###"
|
|
}
|
|
}
|
|
|
|
func contains(_ searchField: String) -> Bool {
|
|
unsortedPlayers().anySatisfy({ $0.contains(searchField) }) || self.name?.localizedCaseInsensitiveContains(searchField) == true
|
|
}
|
|
|
|
func includes(_ players: [PlayerRegistration]) -> Bool {
|
|
players.allSatisfy { player in
|
|
includes(player)
|
|
}
|
|
}
|
|
|
|
func includes(_ player: PlayerRegistration) -> Bool {
|
|
unsortedPlayers().anySatisfy { _player in
|
|
_player.isSameAs(player)
|
|
}
|
|
}
|
|
|
|
func canPlay() -> Bool {
|
|
teamScores().isEmpty == false || players().allSatisfy({ $0.hasPaid() || $0.hasArrived })
|
|
}
|
|
|
|
func availableForSeedPick() -> Bool {
|
|
groupStage == nil && bracketPosition == nil
|
|
}
|
|
|
|
func inGroupStage() -> Bool {
|
|
groupStagePosition != nil
|
|
}
|
|
|
|
func inRound() -> Bool {
|
|
bracketPosition != nil
|
|
}
|
|
|
|
func resetPositions() {
|
|
groupStage = nil
|
|
groupStagePosition = nil
|
|
bracketPosition = nil
|
|
}
|
|
|
|
func pasteData() -> String {
|
|
[name, playersPasteData(), formattedInscriptionDate()].compactMap({ $0 }).joined(separator: "\n")
|
|
}
|
|
|
|
func formattedInscriptionDate() -> String? {
|
|
if let registrationDate {
|
|
return "Inscrit le " + registrationDate.formatted(.dateTime.weekday().day().month().hour().minute())
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func playersPasteData() -> String {
|
|
players().map { $0.pasteData() }.joined(separator: "\n")
|
|
}
|
|
|
|
|
|
func updatePlayers(_ players: Set<PlayerRegistration>) {
|
|
try? DataStore.shared.playerRegistrations.delete(contentOfs: unsortedPlayers())
|
|
setWeight(from: Array(players))
|
|
|
|
players.forEach { player in
|
|
player.teamRegistration = id
|
|
}
|
|
}
|
|
|
|
func qualifiedFromGroupStage() -> Bool {
|
|
groupStagePosition != nil && bracketPosition != nil
|
|
}
|
|
|
|
typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool
|
|
|
|
func players() -> [PlayerRegistration] {
|
|
Store.main.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in
|
|
let predicates: [AreInIncreasingOrder] = [
|
|
{ $0.sex < $1.sex },
|
|
{ $0.rank ?? 0 < $1.rank ?? 0 },
|
|
{ $0.lastName < $1.lastName},
|
|
{ $0.firstName < $1.firstName }
|
|
]
|
|
|
|
for predicate in predicates {
|
|
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
|
|
continue
|
|
}
|
|
|
|
return predicate(lhs, rhs)
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
func unsortedPlayers() -> [PlayerRegistration] {
|
|
Store.main.filter { $0.teamRegistration == self.id }
|
|
}
|
|
|
|
func setWeight(from players: [PlayerRegistration]) {
|
|
let significantPlayerCount = significantPlayerCount()
|
|
weight = (players.prefix(significantPlayerCount).map { $0.weight } + missingPlayerType().map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
|
|
}
|
|
|
|
func significantPlayerCount() -> Int {
|
|
tournamentObject()?.significantPlayerCount() ?? 2
|
|
}
|
|
|
|
func mandatoryPlayerType() -> [Int] {
|
|
switch tournamentCategory {
|
|
case .mix:
|
|
return [0, 1]
|
|
case .women:
|
|
return [0, 0]
|
|
case .men:
|
|
return [1, 1]
|
|
}
|
|
}
|
|
|
|
func missingPlayerType() -> [Int] {
|
|
let players = unsortedPlayers()
|
|
if players.count >= 2 { return [] }
|
|
let s = players.map { $0.sex }
|
|
var missing = mandatoryPlayerType()
|
|
s.forEach { i in
|
|
if let index = missing.firstIndex(of: i) {
|
|
missing.remove(at: index)
|
|
}
|
|
}
|
|
return missing
|
|
}
|
|
|
|
func unrankValue(for malePlayer: Bool) -> Int {
|
|
tournamentObject()?.unrankValue(for: malePlayer) ?? 100_000
|
|
}
|
|
|
|
func groupStageObject() -> GroupStage? {
|
|
guard let groupStage else { return nil }
|
|
return Store.main.findById(groupStage)
|
|
}
|
|
|
|
func tournamentObject() -> Tournament? {
|
|
Store.main.findById(tournament)
|
|
}
|
|
|
|
override func deleteDependencies() throws {
|
|
try Store.main.deleteDependencies(items: self.unsortedPlayers())
|
|
}
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case _id = "id"
|
|
case _tournament = "tournament"
|
|
case _groupStage = "groupStage"
|
|
case _registrationDate = "registrationDate"
|
|
case _callDate = "callDate"
|
|
case _bracketPosition = "bracketPosition"
|
|
case _groupStagePosition = "groupStagePosition"
|
|
case _comment = "comment"
|
|
case _source = "source"
|
|
case _sourceValue = "sourceValue"
|
|
case _logo = "logo"
|
|
case _name = "name"
|
|
case _wildCardBracket = "wildCardBracket"
|
|
case _wildCardGroupStage = "wildCardGroupStage"
|
|
case _category = "category"
|
|
case _weight = "weight"
|
|
case _walkOut = "walkOut"
|
|
case _lockWeight = "lockWeight"
|
|
case _confirmationDate = "confirmationDate"
|
|
case _qualified = "qualified"
|
|
}
|
|
}
|
|
|
|
extension TeamRegistration: Hashable {
|
|
static func == (lhs: TeamRegistration, rhs: TeamRegistration) -> Bool {
|
|
lhs.id == rhs.id
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
hasher.combine(id)
|
|
}
|
|
}
|
|
|
|
enum TeamDataSource: Int, Codable {
|
|
case beachPadel
|
|
}
|
|
|