from random import choice
# ABCMeta permet de définir des méthodes abstraites qui empèchent l’instanciation
# de la classe. Elle empèche toute instanciation de classes dérivées tant que la
# méthode n’est pas redéfinie.
# http://pymotw.com/2/abc/
# https://docs.python.org/3.4/library/abc.html
from abc import ABCMeta, abstractmethod


class EtreVivant(metaclass=ABCMeta):
    def __init__(self, age=0, resistance=10):
        self.age = age
        self.resistance = resistance

    def vieillir(self):
        self.age += 1

    def estMort(self):
        return self.age >= 20 and self.resistance < 1

    def __repr__(self):
        """Retourne une représentation courte de la forme
        Algue(1, 8) ou bien PoissonHerbivore(3,5).
        Principalement utilisé pour afficher les êtres vivants
        stockés dans des conteneurs.
        """
        return self.__class__.__name__ + "({0}, {1})".format(self.age, self.resistance)

    def __str__(self):
        """Retourne une représentation longue de la forme
        [Algue] Age : 1, Résistance : 8 ou bien
        [Charly] Age : 3, Résistance : 5 (Femelle).
        Principalement utilisé pour l’affichage direct d’un
        objet EtreVivant.
        """
        return "[" + self.getnom() + "] Age : " + str(self.age) + ", Résistance : " + str(self.resistance)

    def seFaireManger(self):
        self.resistance -= 5

    @abstractmethod
    def getnom(self):
        # Seul les êtres vivants avec une espèce connue peuvent avoir des noms
        pass


class Algue(EtreVivant):
    def vieillir(self):
        super().vieillir()
        self.resistance += 1
        if self.resistance > 10:
            new_res = self.resistance // 2
            self.resistance -= new_res
            return Algue(resistance=new_res)

    def getnom(self):
        """Implémentation concrête de la méthode abstraite"""
        return "Algue"


class Poisson(EtreVivant):
    def __init__(self, nom, age=0, resistance=10, faim=0):
        super().__init__(age,resistance)
        self.nom = nom
        self.faim = faim
        self.sexe = choice(["Mâle", "Femelle"])
        self.aCoteDe(None)

    def vieillir(self):
        super().vieillir()
        self.faim += 1
        if self.faim > 5:
            self.seNourrir()
        else:
            return self.seReproduire()

    def getnom(self):
        """Implémentation concrête de la méthode abstraite"""
        return self.nom

    def __str__(self):
        return super().__str__() + " (%s)" % self.sexe

    def aCoteDe(self, voisin):
        self.voisin = voisin

    def seReproduire(self):
        if isinstance(self.voisin, self.__class__) and self.sexe != self.voisin.sexe:
            return self.__class__(self.nom + self.voisin.nom + '/')

    def seNourrir(self):
        nourriture, gain = self.getParametresNourriture()
        if isinstance(self.voisin, nourriture):
            voisin.seFaireManger()
            self.resistance += gain
            self.faim = 0

    @abstractmethod
    def getParametresNourriture(self):
        pass


class PoissonHerbivore(Poisson):
    def getParametresNourriture(self):
        """Implémentation concrête de la méthode abstraite"""
        return (Algue, 3)


class PoissonCarnivore(Poisson):
    def getParametresNourriture(self):
        """Implémentation concrête de la méthode abstraite"""
        return (Poisson, 5)


def aquarium():
    nb_algues, nb_herbivores, nb_carnivores = -1, -1, -1
    # Force l’entrée d’un chiffre positif
    while nb_algues < 0:
        try:
            nb_algues = int(input("Nombre d’algues dans l’aquarium : "))
        except:
            nb_algues = -1
    # Force l’entrée d’un chiffre positif
    while nb_herbivores < 0:
        try:
            nb_herbivores = int(input("Nombre de poissons herbivores : "))
        except:
            nb_herbivores = -1
    # Force l’entrée d’un chiffre positif
    while nb_carnivores < 0:
        try:
            nb_carnivores = int(input("Nombre de poissons carnivores : "))
        except:
            nb_carnivores = -1

    # Construction de l’aquarium en fonction de informations récupérées
    aquarium = [Algue() for _ in range(nb_algues)]
    # extend concatène les listes
    aquarium.extend([PoissonHerbivore(input("Nom poisson herbivore numéro %d " % (i+1))) for i in range(nb_herbivores)])
    aquarium.extend([PoissonCarnivore(input("Nom poisson carnivore numéro %d " % (i+1))) for i in range(nb_carnivores)])

    while input("Avancer d’un jour ? ") == '':
        # On déplace les poissons à côté d’un autre être vivant de l’aquarium
        for etre_vivant in aquarium:
            if isinstance(etre_vivant, Poisson):
                autre = choice(aquarium)
                etre_vivant.aCoteDe(etre_vivant is not autre and autre or None)

        # On fait vieillir tous les êtres vivants de l’aquarium et on rajoute les naissances par la suite
        naissances = [etre_vivant.vieillir() for etre_vivant in aquarium]
        while None in naissances:
            naissances.remove(None)
        print("Naissances du jour :", naissances)
        aquarium.extend(naissances)

        # On supprime les morts
        aquarium = [etre_vivant for etre_vivant in aquarium if not etre_vivant.estMort()]

        # À partir de là, la boucle while recommence, on affiche juste l’état de l’aquarium avant ça
        print(" === État de l’aquarium === ")
        for ev in aquarium:
            print(ev)


# Execute aquarium si le fichier est exécuté mais pas si il est chargé comme un module
if __name__ == '__main__':
    aquarium()
    print("Test classes abstraites :")
    # Une erreur sur cette ligne est normale
    l = [Algue(), Poisson("A"), EtreVivant(), PoissonHerbivore("B"), PoissonCarnivore("C")]
    print(l)
