package com.pmumali.ch3_jumeleplace.service; import com.pmumali.ch3_jumeleplace.model.Course; import com.pmumali.ch3_jumeleplace.model.Gains; import com.pmumali.ch3_jumeleplace.model.PariJumelePlace; import com.pmumali.ch3_jumeleplace.model.ResultatCourse; import com.pmumali.ch3_jumeleplace.repository.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Objects; @Service @Transactional public class GainsService { @Autowired private PariJumelePlaceRepository pariRepository; @Autowired private GainsRepository gainsRepository; @Autowired private CourseRepository courseRepository; @Autowired private ChevalRepository chevalRepository; @Autowired private ResultatCourseRepository resultatRepository; private static final double RAPPORT_MINIMUM = 1.1; private static final double PRELEVEMENTS = 0.15; // 15% de prélèvements public Gains calculerGains(Long courseId, ResultatCourse resultat) { Course course = courseRepository.findById(courseId) .orElseThrow(() -> new RuntimeException("Course non trouvée")); List tousParis = pariRepository.findByCourseId(courseId); // Calcul de la recette nette (Article 5) double totalMises = tousParis.stream().mapToDouble(PariJumelePlace::getMise).sum(); double recetteNette = totalMises; // Calcul des remboursements (Article 4) double montantRemboursements = calculerRemboursements(tousParis); // Masse à partager (Article 5) double masseAPartager = recetteNette - montantRemboursements - (recetteNette * PRELEVEMENTS); // Déterminer les combinaisons payables List combinaisonsPayables = determinerCombinaisonsPayables(resultat); // Calcul des rapports Gains gains = new Gains(); gains.setCourse(course); gains.setMasseAPartager(masseAPartager); gains.setDateCalcul(LocalDateTime.now()); // Calcul détaillé des gains selon les règles if (resultat.isADeadHeat()) { calculerRapportsDeadHeat(gains, combinaisonsPayables, tousParis); } else { calculerRapportsNormaux(gains, combinaisonsPayables, tousParis); } // Gérer la cagnotte si nécessaire (Article 8 et 9) gererCagnotte(gains, combinaisonsPayables); return gainsRepository.save(gains); } private double calculerRemboursements(List paris) { double remboursements = 0.0; for (PariJumelePlace pari : paris) { // Article 4: Remboursement si un ou deux chevaux non partants if (pari.getCheval1().isEstNonPartant() || pari.getCheval2().isEstNonPartant()) { remboursements += pari.getMise(); pari.setEstRembourse(true); pariRepository.save(pari); } } return remboursements; } private List determinerCombinaisonsPayables(ResultatCourse resultat) { List combinaisons = new ArrayList<>(); // Article 1: Le pari est payable si les deux chevaux occupent deux des trois premières places // Article 3: Gestion des dead-heats if (resultat.isADeadHeat()) { // Cas de dead-heat if (resultat.getChevauxPremiers().size() >= 3) { // Article 3a: Dead-heat de trois chevaux ou plus à la première place // Toutes les combinaisons deux à deux des chevaux classés premiers for (int i = 0; i < resultat.getChevauxPremiers().size(); i++) { for (int j = i + 1; j < resultat.getChevauxPremiers().size(); j++) { combinaisons.add(new CombinaisonPayable( resultat.getChevauxPremiers().get(i), resultat.getChevauxPremiers().get(j) )); } } } else if (resultat.getChevauxPremiers().size() == 2 && !resultat.getChevauxTroisiemes().isEmpty()) { // Article 3b: Dead-heat de deux chevaux à la première place et un ou plusieurs à la troisième // Combinaison des deux premiers + combinaisons de chaque premier avec chaque troisième combinaisons.add(new CombinaisonPayable( resultat.getChevauxPremiers().get(0), resultat.getChevauxPremiers().get(1) )); for (Long chevalPremier : resultat.getChevauxPremiers()) { for (Long chevalTroisieme : resultat.getChevauxTroisiemes()) { combinaisons.add(new CombinaisonPayable(chevalPremier, chevalTroisieme)); } } } else if (resultat.getChevauxTroisiemes().size() >= 2) { // Article 3c: Dead-heat de deux chevaux ou plus à la troisième place // Combinaison premier + deuxième + combinaisons premier avec chaque troisième + deuxième avec chaque troisième if (!resultat.getChevauxPremiers().isEmpty() && !resultat.getChevauxDeuxiemes().isEmpty()) { combinaisons.add(new CombinaisonPayable( resultat.getChevauxPremiers().get(0), resultat.getChevauxDeuxiemes().get(0) )); for (Long chevalTroisieme : resultat.getChevauxTroisiemes()) { combinaisons.add(new CombinaisonPayable( resultat.getChevauxPremiers().get(0), chevalTroisieme )); combinaisons.add(new CombinaisonPayable( resultat.getChevauxDeuxiemes().get(0), chevalTroisieme )); } } } } else { // Cas normal - Article 1: Les deux chevaux doivent occuper deux des trois premières places List chevauxPayables = new ArrayList<>(); chevauxPayables.addAll(resultat.getChevauxPremiers()); chevauxPayables.addAll(resultat.getChevauxDeuxiemes()); chevauxPayables.addAll(resultat.getChevauxTroisiemes()); // Générer toutes les combinaisons deux à deux for (int i = 0; i < chevauxPayables.size(); i++) { for (int j = i + 1; j < chevauxPayables.size(); j++) { combinaisons.add(new CombinaisonPayable( chevauxPayables.get(i), chevauxPayables.get(j) )); } } } return combinaisons; } private void calculerRapportsNormaux(Gains gains, List combinaisonsPayables, List tousParis) { // Article 5a: Cas d'arrivée normale if (combinaisonsPayables.isEmpty()) { gains.setMontantCagnotte(gains.getMasseAPartager()); gains.setMasseAPartager(0.0); return; } // Article 5a: Le bénéfice à répartir est divisé en trois parties égales double beneficeARepartir = gains.getMasseAPartager(); double part = beneficeARepartir / 3; List rapports = new ArrayList<>(); for (CombinaisonPayable combinaison : combinaisonsPayables) { double totalMises = calculerMisesSurCombinaison(combinaison, tousParis); if (totalMises > 0) { double rapport = (part / totalMises) + 1; rapports.add(Math.max(rapport, RAPPORT_MINIMUM)); } else { rapports.add(0.0); } } gains.setRapports(rapports); } private void calculerRapportsDeadHeat(Gains gains, List combinaisonsPayables, List tousParis) { // Article 5b: Cas d'arrivée "dead heat" if (combinaisonsPayables.isEmpty()) { gains.setMontantCagnotte(gains.getMasseAPartager()); gains.setMasseAPartager(0.0); return; } double beneficeARepartir = gains.getMasseAPartager(); List rapports = new ArrayList<>(); // Déterminer le type de dead-heat pour appliquer les règles spécifiques ResultatCourse resultat = gains.getCourse().getResultat(); if (resultat.getChevauxPremiers().size() >= 3) { // Article 5b1: Dead-heat de trois chevaux ou plus à la première place double partParCombinaison = beneficeARepartir / combinaisonsPayables.size(); for (CombinaisonPayable combinaison : combinaisonsPayables) { double totalMises = calculerMisesSurCombinaison(combinaison, tousParis); if (totalMises > 0) { double rapport = (partParCombinaison / totalMises) + 1; rapports.add(Math.max(rapport, RAPPORT_MINIMUM)); } else { rapports.add(0.0); } } } else if (resultat.getChevauxPremiers().size() == 2 && !resultat.getChevauxTroisiemes().isEmpty()) { // Article 5b2: Dead-heat de deux chevaux à la première place et un ou plusieurs à la troisième double part = beneficeARepartir / 3; // Trouver la combinaison des deux premiers CombinaisonPayable combinaisonPremiers = null; for (CombinaisonPayable combinaison : combinaisonsPayables) { if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) && resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) { combinaisonPremiers = combinaison; break; } } // Calculer les rapports for (CombinaisonPayable combinaison : combinaisonsPayables) { double totalMises = calculerMisesSurCombinaison(combinaison, tousParis); if (totalMises > 0) { double rapportPart = part; if (combinaison.equals(combinaisonPremiers)) { // Combinaison des deux premiers - 1/3 du bénéfice rapportPart = part; } else { // Combinaisons premier-troisième - 1/3 du bénéfice divisé par le nombre de ces combinaisons int nbCombinaisonsPremierTroisieme = combinaisonsPayables.size() - 1; rapportPart = part / nbCombinaisonsPremierTroisieme; } double rapport = (rapportPart / totalMises) + 1; rapports.add(Math.max(rapport, RAPPORT_MINIMUM)); } else { rapports.add(0.0); } } } else if (resultat.getChevauxDeuxiemes().size() >= 2) { // Article 5b3: Dead-heat de deux chevaux ou plus à la deuxième place double deuxTiers = beneficeARepartir * 2 / 3; double unTiers = beneficeARepartir / 3; // Compter les types de combinaisons int nbCombinaisonsPremierDeuxieme = 0; int nbCombinaisonsDeuxiemeDeuxieme = 0; for (CombinaisonPayable combinaison : combinaisonsPayables) { if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) || resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) { nbCombinaisonsPremierDeuxieme++; } else { nbCombinaisonsDeuxiemeDeuxieme++; } } // Calculer les rapports for (CombinaisonPayable combinaison : combinaisonsPayables) { double totalMises = calculerMisesSurCombinaison(combinaison, tousParis); if (totalMises > 0) { double rapportPart; if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) || resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) { // Combinaisons premier-deuxième - 2/3 du bénéfice divisé par le nombre de ces combinaisons rapportPart = deuxTiers / nbCombinaisonsPremierDeuxieme; } else { // Combinaisons deuxième-deuxième - 1/3 du bénéfice divisé par le nombre de ces combinaisons rapportPart = unTiers / nbCombinaisonsDeuxiemeDeuxieme; } double rapport = (rapportPart / totalMises) + 1; rapports.add(Math.max(rapport, RAPPORT_MINIMUM)); } else { rapports.add(0.0); } } } else if (resultat.getChevauxTroisiemes().size() >= 2) { // Article 5b4: Dead-heat de deux chevaux ou plus à la troisième place double part = beneficeARepartir / 3; // Compter les types de combinaisons int nbCombinaisonsPremierTroisieme = 0; int nbCombinaisonsDeuxiemeTroisieme = 0; for (CombinaisonPayable combinaison : combinaisonsPayables) { if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) || resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) { nbCombinaisonsPremierTroisieme++; } else if (resultat.getChevauxDeuxiemes().contains(combinaison.getCheval1Id()) || resultat.getChevauxDeuxiemes().contains(combinaison.getCheval2Id())) { nbCombinaisonsDeuxiemeTroisieme++; } } // Calculer les rapports for (CombinaisonPayable combinaison : combinaisonsPayables) { double totalMises = calculerMisesSurCombinaison(combinaison, tousParis); if (totalMises > 0) { double rapportPart; if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) && resultat.getChevauxDeuxiemes().contains(combinaison.getCheval2Id()) || resultat.getChevauxPremiers().contains(combinaison.getCheval2Id()) && resultat.getChevauxDeuxiemes().contains(combinaison.getCheval1Id())) { // Combinaison premier-deuxième - 1/3 du bénéfice rapportPart = part; } else if (resultat.getChevauxPremiers().contains(combinaison.getCheval1Id()) || resultat.getChevauxPremiers().contains(combinaison.getCheval2Id())) { // Combinaisons premier-troisième - 1/3 du bénéfice divisé par le nombre de ces combinaisons rapportPart = part / nbCombinaisonsPremierTroisieme; } else { // Combinaisons deuxième-troisième - 1/3 du bénéfice divisé par le nombre de ces combinaisons rapportPart = part / nbCombinaisonsDeuxiemeTroisieme; } double rapport = (rapportPart / totalMises) + 1; rapports.add(Math.max(rapport, RAPPORT_MINIMUM)); } else { rapports.add(0.0); } } } gains.setRapports(rapports); } private double calculerMisesSurCombinaison(CombinaisonPayable combinaison, List tousParis) { double totalMises = 0.0; for (PariJumelePlace pari : tousParis) { if ((pari.getCheval1().getId().equals(combinaison.getCheval1Id()) && pari.getCheval2().getId().equals(combinaison.getCheval2Id())) || (pari.getCheval1().getId().equals(combinaison.getCheval2Id()) && pari.getCheval2().getId().equals(combinaison.getCheval1Id()))) { totalMises += pari.getMise(); } } return totalMises; } private void gererCagnotte(Gains gains, List combinaisonsPayables) { // Article 8 et 9: Gestion de la cagnotte if (combinaisonsPayables.isEmpty()) { gains.setMontantCagnotte(gains.getMasseAPartager()); gains.setMasseAPartager(0.0); } // Article 8d: Course annulée ou reportée if (gains.getCourse().isEstAnnulee()) { gains.setMontantCagnotte(gains.getMasseAPartager()); gains.setMasseAPartager(0.0); } } // Classe interne pour représenter une combinaison payable private static class CombinaisonPayable { private Long cheval1Id; private Long cheval2Id; public CombinaisonPayable(Long cheval1Id, Long cheval2Id) { this.cheval1Id = cheval1Id; this.cheval2Id = cheval2Id; } public Long getCheval1Id() { return cheval1Id; } public Long getCheval2Id() { return cheval2Id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CombinaisonPayable that = (CombinaisonPayable) o; return (Objects.equals(cheval1Id, that.cheval1Id) && Objects.equals(cheval2Id, that.cheval2Id)) || (Objects.equals(cheval1Id, that.cheval2Id) && Objects.equals(cheval2Id, that.cheval1Id)); } @Override public int hashCode() { long id1 = Math.min(cheval1Id, cheval2Id); long id2 = Math.max(cheval1Id, cheval2Id); return Objects.hash(id1, id2); } } public Gains obtenirGainsParCourse(Long courseId) { return gainsRepository.findByCourseId(courseId); } }