import random
from random import random, randint, randrange
from individu import Individu
from RotTable import RotTable
from croisement import croisement_un_point, croisement_deux_points

class Population:
    def __init__(self,n):
        self.indiv=[Individu(RotTable()) for k in range (n)]
        self.n = n

    def modifier_population(self, liste_individus):
        """Fonction qui renvoie une nouvelle instance de population a partir d'une liste d'individus"""
        self.n = len(liste_individus)
        self.indiv = liste_individus
        return self

    def selection_duel_pondere(self,p=None): 
        if p == None :
            p = (self.n)//2
        newself=[] 
        vu=set()
        m=randrange(0,self.n)
        t=randrange(0,self.n)                   #méthode des duels pondérée: si x=10 et y=1, y a une chance sur 11 de passer
        while len(newself)<p:
            while m in vu:
                m=randrange(0,self.n)
            while t in vu:
                t=randrange(0,self.n)
            x=self.indiv[m]
            y=self.indiv[t]
            vu.add(t)
            vu.add(m)
            p=random()
            if p>x.score/(x.score+y.score):
                newself.append(y)
            else:
                newself.append(x)
            
        self = self.modifier_population(newself)
    
    def selection_duel(self,p=None):
        if p == None :
            p = (self.n)//2
        newself=[]
        vu=set()                        
        t=randrange(0,self.n)
        m=randrange(0,self.n)             
        while len(newself)<p:
            while m in vu:
                m=randrange(0,self.n)
            while t in vu:
                t=randrange(0,self.n)
            x=self.indiv[m]
            y=self.indiv[t]
            vu.add(t)
            vu.add(m)
            if x.score>=y.score:
                newself.append(x)
            else:
                newself.append(y)
        self = self.modifier_population(newself)

    def selection_par_rang(self,p = None):
        if p == None :
            p = (self.n)//2
        liste_individus = self.indiv
        n = self.n
        
        def echanger(tableau, i, j):
            tableau[i], tableau[j] = tableau[j], tableau[i]
            
        def partitionner(tableau,debut,fin):
            echanger(tableau,debut,randint(debut,fin-1)) 
            partition=debut
            for i in range(debut+1,fin):
                # if tableau[i] < tableau[debut]:
                if tableau[i].score<tableau[debut].score: 
                    partition+=1 
                    echanger(tableau,i,partition) 
            echanger(tableau,debut,partition) 
            return partition
            
        def tri_rapide_aux(tableau,debut,fin):
            if debut < fin-1:
                positionPivot=partitionner(tableau,debut,fin)
                tri_rapide_aux(tableau,debut,positionPivot)
                tri_rapide_aux(tableau,positionPivot+1,fin)
            
        def tri_rapide(tableau):
            tri_rapide_aux(tableau,0,len(tableau))
            
        tri_rapide(liste_individus)
        individus_selectionnes = []
    
        for _ in range(p):
            curseur = random()*n*(n+1)/2
            # print("curseur", curseur)
            j = 1
            while j*(j+1)/2 < curseur :
                j+=1 
            #on doit prendre l'individu avec le jème score 
            # print("individus selectionés", individus_selectionnes)
            individus_selectionnes.append(liste_individus[j-1])
        
        self = self.modifier_population(individus_selectionnes)
        
    def selection_proportionelle(self,p= None):
        if p == None :
            p = (self.n)//2
        newself=[]
        somme=0
        for indiv in self.indiv:
            somme=somme+indiv.score
        while len(newself)<p:
            m=m=randrange(0, self.n)
            x=self.indiv[m]
            p=random()
            if p<=x.score/somme:
                newself.append(x)
        self = self.modifier_population(newself)

    def reproduction(self,selection=None,enfant=croisement_un_point, p = None):
        if selection == None :
            selection = self.selection_duel
        if p == None :
            p = (self.n)//2
        vieille_taille = self.n
        selection(p)
        newself = list(self.indiv)
        while len(newself)<vieille_taille:
            m=randrange(0,self.n)
            t=randrange(0,self.n)
            x=newself[m]
            y=newself[t]
            couple_enfant = enfant(x,y)
            newself.append(couple_enfant[0])
            newself.append(couple_enfant[1])
        self = self.modifier_population(newself)

def afficher(popu):
    for individu in popu.indiv :
        print("\n individu \n")
        print(individu.table.rot_table)
    
def test():
    popu = Population(4)
    print("\n POPULATION INITIALE \n")
    for individu in popu.indiv :
        individu.evaluate("AAAGGATCTTCTTGAGATCCTTTTTTTCTGCGCGTAATCTGCTGCCAGTAAACGAAAAAACCGCCTGGGGAGGCGGTTTAGTCGAA")
    afficher(popu)
    popu.reproduction(selection = popu.selection_duel)
    print("\n REPRODUCTION \n")
    afficher(popu)

#test()