from RotTable import RotTable
from Traj3D import Traj3D
import numpy as np
from math import sqrt, inf
from random import random, choice

P1 = 0.015

class Individu():
    ''' Un individu est caractérisé par sa table de rotations (individu.table)'''
    def __init__(self, table):
        self.table = table
        lineList = [line.rstrip('\n') for line in open("plasmid_8k.fasta")]
        self.brin = ''.join(lineList[1:])
        #self.brin = "AAAGGATCTTCTTGAGATCCTTTTTTTCTGCGCGTAATCTGCTGCCAGTAAACGAAAAAACCGCCTGGGGAGGCGGTTTAGTCGAA"
        # (sequence used for test)
        self.score = None
        self.distance = None

    def evaluate(self):
        ''' Evalue le score d'un individu sur un nombre numb_ajout de points'''
        # The last numb_ajout dinucleotides of the "ribbon" are joined at its beginning,
        # and the first numb_ajout dinucleotides are joined at the end of it.
        # This "new parts" of the sequence will be compared with the real beginning and end of the the ribbon.
        # If they coincide, then the chromosome is circular
        
        traj = Traj3D()

        # number of dinucleotides which will be compared
        numb_ajout = 10

        # the first and the last numb_ajout dinucleotides respectively
        first_seq = self.brin[0:numb_ajout]
        last_seq = self.brin[-numb_ajout:]

        # creation of the "new ribbon"
        traj.compute(last_seq + self.brin + first_seq, self.table)
        traj_array = traj.getTraj()

        list_distance = []

        begining = traj_array[0:2*numb_ajout]
        end = traj_array[-2*numb_ajout:]

        # score calculation, comparing the new ribbon with the real sequence,
        # according to the distance of the correspondent dinucleotides
        for i in range(numb_ajout):

                nuc_coordonate_beg = begining[i]
                nuc_coordonate_end = end[i]
                distance_nuc = np.linalg.norm(nuc_coordonate_beg - nuc_coordonate_end, ord=2)
                list_distance += [distance_nuc]
        

        self.score = max(list_distance)
        self.distance = np.linalg.norm(traj_array[numb_ajout] - traj_array[-(numb_ajout+1)], ord=2)


    def mutation(self, proba = P1):
        # each dinucleotide has a probability "proba" to mutate
        table_rotations = self.table.rot_table
        for doublet in sorted(table_rotations.keys()) :
            for coord in range(3):
                tir = random()
                if tir < proba :
                    table_rotations[doublet][coord] =np.random.uniform(low = self.table.orta()[doublet][coord] - self.table.orta()[doublet][coord + 3], high = self.table.orta()[doublet][coord] + self.table.orta()[doublet][coord + 3])
                    doublet2 = self.table.corr()[doublet]
                    if coord == 0 or coord == 1 :
                        table_rotations[doublet2][coord] = table_rotations[doublet][coord]
                    else :
                        #sur l'axe z il y a un moins
                        table_rotations[doublet2][coord] = - table_rotations[doublet][coord]

    def mutation_with_numbers(self, proba = P1, number_of_mutations = 1):
        # each individual has a probability "proba" to be mutated
        # if it mutates, then a number "number_of_mutations" of chromosomes will be randomly mutated
        table_rotations = self.table.rot_table
        table_rotation_not_seen = [i for i in sorted(table_rotations.keys())]
        table_rotation_not_seen = table_rotation_not_seen[:8]

        tir = random()
        if tir < proba :
            for i in range(0,number_of_mutations):
                
                doublet = choice(table_rotation_not_seen)
                table_rotation_not_seen.remove(doublet)

                for coord in range(3):
                    table_rotations[doublet][coord] =np.random.uniform(low = self.table.orta()[doublet][coord] - self.table.orta()[doublet][coord + 3], high = self.table.orta()[doublet][coord] + self.table.orta()[doublet][coord + 3])
                    doublet2 = self.table.corr()[doublet]
                    if coord == 0 or coord == 1 :
                        table_rotations[doublet2][coord] = table_rotations[doublet][coord]
                    else :
                        #sur l'axe z il y a un moins
                        table_rotations[doublet2][coord] = - table_rotations[doublet][coord]


    def mutation_close_values(self, proba = P1, number_of_mutations = 1):
        # each individual has a probability "proba" to be mutated
        # if it mutates, then a number "number_of_mutations" of chromosomes will be randomly mutated
        # according to the normal distribution around the current value of the dinucleotide
        table_rotations = self.table.rot_table
        table_rotation_not_seen = [i for i in sorted(table_rotations.keys())]
        table_rotation_not_seen = table_rotation_not_seen[:8]

        tir = random()
        if tir < proba :
            for i in range(0,number_of_mutations):

                doublet = choice(table_rotation_not_seen)
                table_rotation_not_seen.remove(doublet)

                for coord in range(3):
                    value = table_rotations[doublet][coord] + np.random.normal(0, self.table.orta()[doublet][coord + 3]/10)
                    if value > self.table.orta()[doublet][coord] + self.table.orta()[doublet][coord + 3]:
                        value = self.table.orta()[doublet][coord] + self.table.orta()[doublet][coord + 3]
                    elif value < self.table.orta()[doublet][coord] - self.table.orta()[doublet][coord + 3]:
                        value = self.table.orta()[doublet][coord] - self.table.orta()[doublet][coord + 3]
                    table_rotations[doublet][coord] = value
                    
                    doublet2 = self.table.corr()[doublet]
                    if coord == 0 or coord == 1 :
                        table_rotations[doublet2][coord] = table_rotations[doublet][coord]
                    else :
                        #sur l'axe z il y a un moins
                        table_rotations[doublet2][coord] = - table_rotations[doublet][coord]