import random


class Coccinelle:
    def __init__(self, sexe, age, niv_nutrition):
        self.age = age
        self.esperance_de_vie = random.randint(200, 350)
        self.sexe = sexe
        self.niv_nutrition = niv_nutrition

    def chasser(self, nb_proies, nb_coccinelles):
        """Cette méthode simule la chasse d'une coccinelle. Le nombre de proies
        consommées est tiré aléatoirement en fonction de du nombre de proies disponibles par coccinelle.
        Si la coccinelle consomme au moins 10 proies, son niveau de nutrition augmente de 1.
        On renvoie le nombre de proies restantes après la chasse de cette coccinelle.
        """
        # Si aucune coccinelle n'est présente, les proies ne sont pas consommées.
        # Ne devrait pas arriver dans le cadre de l'évolution, mais on gère ce cas
        # pour éviter une division par zéro.
        if nb_coccinelles == 0:
            return nb_proies

        # Partage des proies entre les coccinelles
        proies_par_cocci = nb_proies / nb_coccinelles

        # Tirage aléatoire du nombre de proies consommées en fonction de la répartition
        if proies_par_cocci > 20:
            consomme = random.randint(12, 20)
        elif proies_par_cocci > 10:
            consomme = random.randint(8, 15)
        else:
            consomme = random.randint(3, 8)

        # On ne peut pas consommer plus de proies que ce qui est disponible
        # Cette ligne peut être utilisée selon le tirage du corps du elif ou du else de la condition précédente..
        consomme = min(consomme, nb_proies)

        # Mise à jour du niveau de nutrition de la coccinelle en fonction du nombre de proies consommées
        if consomme >= 10:
            self.niv_nutrition += 1
        else:
            self.niv_nutrition = max(0, self.niv_nutrition - 1)

        # Calcul du nombre de proies restantes après la chasse de cette coccinelle
        return nb_proies - consomme

    def reproduction(self):
        """
        Une femelle avec un niveau de nutrition >= 2 engendre exactement
        deux descendants : un mâle et une femelle.
        """
        descendants = []
        if self.sexe == "femelle" and self.niv_nutrition >= 2 and self.age >= 20:
            descendants.append(Coccinelle("male", 0, 0))
            descendants.append(Coccinelle("femelle", 0, 0))
            self.niv_nutrition = 0

        return descendants

    def a_survecu(self):
        """
        Met à jour l'âge de la coccinelle et indique si elle est encore en vie.
        """
        self.age = self.age + 1
        if self.niv_nutrition == 0:
            m = random.randint(0, 2) # On tire un nombre aléatoire pour simuler la probabilité de mourir de faim (1/3)
            if m == 0: 
                return False
        return self.age < self.esperance_de_vie

    def __repr__(self):
        return f"Coccinelle {self.sexe}, âge: {self.age}/{self.esperance_de_vie}, niv_nutrition: {self.niv_nutrition}"


def evolution(population, nb_proies):
    """
    Simule une journée dans l'écosystème :
    - chasse des coccinelles
    - reproduction
    - vieillissement et mortalité
    - croissance des pucerons

    population est une liste d'instances de la classe Coccinelle
    nb_proies est un entier indiquant le nombre de proies

    Cette fonction renvoie un couple (population_suivante, nouveau_nb_proies) indiquant
    la nouvelle population à la fin de la journée et le nombre de proies.
    """
    population_suivante = []
    nouveau_nes = []
    nb_coccinelles = len(population)

    for coccinelle in population:
        nb_proies = coccinelle.chasser(nb_proies, nb_coccinelles)

        if coccinelle.a_survecu():
            population_suivante.append(coccinelle)

        nouveau_nes += coccinelle.reproduction()

    # Croissance naturelle des pucerons (augmentation de 20% par jour)
    nb_proies = int(nb_proies * 1.2)

    # Ajout des nouveau-nés en fin de journée
    population_suivante += nouveau_nes

    return population_suivante, nb_proies


#############################################################################
# Écrire ci-dessous le code pour les questions de l'énoncé                  #
#############################################################################

# Question 1

# Création de la population initiale
population = [
    Coccinelle("femelle", 10, 2),
    Coccinelle("femelle", 10, 2),
    Coccinelle("male", 10, 2)
]

# Nombre initial de pucerons
nb_proies = 200

# Simulation de l'évolution sur 5 jours
print(f"Jour 0: Nombre de coccinelles = {len(population)}, Nombre de pucerons = {nb_proies}")
for jour in range(1, 6):
    population, nb_proies = evolution(population, nb_proies)
    print(f"Jour {jour}: Nombre de coccinelles = {len(population)}, Nombre de pucerons = {nb_proies}")
    

# Question 2

def simulation_simple(population, nb_proies):
    jours_simules = 0
    while jours_simules < 30 and len(population) > 0 and nb_proies > 0:
        population, nb_proies = evolution(population, nb_proies)
        jours_simules += 1

    return len(population), nb_proies, jours_simules

# Test de la fonction simulation_simple
population_initiale = [
    Coccinelle("femelle", 10, 2),
    Coccinelle("femelle", 10, 2),
    Coccinelle("male", 10, 2)
]
nb_proies_initial = 1000

resultat = simulation_simple(population_initiale, nb_proies_initial)
print(f"Résultat de la simulation : {resultat}")


# Quesiton 3
# Question vague, solution sans doute trop longue !

# Quesiton 4
# On voit que sur 5 jours, la population de 3 coccinelles n'a pas évoluée (positif vu les conditions rajoutées)
# Pour le test avec 1000 pucerons, la simulation s'arrête au bout de 30 jours, et les populations ont augmentées.
# Il faut donc plus de 3 coccinelles pour venir à bout de 1000 pucerons !
