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.
 
 
PadelClub/PadelClub/Data/TeamRegistration.swift

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
}