# genetic_changes.py
# This file handles any genetic crossover and mutation operations


# Internal Imports
from src.genetic_embedding.core.chromosome import Chromosome

# External Imports
import random


'''
----------crossover----------
- This function handles creating a crossover between two chromosomes
-----Inputs-----
- chromosomes - The list of two chromosomes to cross over
- crossover_span - The number of bits to cross over (Defaults to 50)
-----Output-----
- result - The resultant chromosomes
'''
def old_crossover(chromosomes, crossover_span = 50):
    result = []
    # Determine the random start and end indices
    #binary = [bin(chromosomes[0].embedding)[2:].rjust(chromosomes[0].max_embed_length, "0"), bin(chromosomes[1].embedding)[2:].rjust(chromosomes[1].max_embed_length, "0")]
    binary = [chromosomes[0].embedding, chromosomes[1].embedding]
    embedding_len = len(binary[0])
    embedding_dim = chromosomes[0].dimension
    start_index = random.randint(0, embedding_len - 1 - crossover_span)
    end_index = random.randint(start_index, start_index + crossover_span)

    # Create the new chromosomes
    #print(bin(int(binary[0]) ^ int(binary[1]))[2:].count("1"))
    temp_embedding = binary[0][:start_index] + binary[1][start_index:end_index] + binary[0][end_index:]
    #print(bin(int(binary[0]) ^ int(temp_embedding))[2:].count("1"))
    result.append(Chromosome(embed=temp_embedding, dim = embedding_dim))
    temp_embedding = binary[1][:start_index] + binary[0][start_index:end_index] + binary[1][end_index:]
    #print(bin(int(binary[1]) ^ int(temp_embedding))[2:].count("1"))
    result.append(Chromosome(embed=temp_embedding, dim = embedding_dim))

    # Return the resultant chromosomes
    return result


'''
----------mutation----------
- This function handles creating a mutation of a given chromosome
-----Inputs-----
- chromosome - The chromosome to mutate
- mutation_amt - The number of bits to mutate (defaults to 1)
-----Output-----
- result - The resultant chromosome
'''
def mutation(chromosome, mutation_amt = 1):
    #temp_embedding = bin(chromosome.embedding)[2:].rjust(chromosome.max_embed_length, "0")
    temp_embedding = chromosome.embedding
    embedding_range = len(temp_embedding) - 1
    embedding_dim = chromosome.dimension
    # For the mutation amount, pick a random place to mutate
    for i in range(mutation_amt):
        # Get a random index
        index = random.randint(0, embedding_range)

        # Mutate the chromosome, and return the new one
        mutation = 1 if (temp_embedding[index] == "0") else 0
        temp_embedding = temp_embedding[:index] + str(mutation) + temp_embedding[index+1:]

    # Return the resultant chromosome
    return Chromosome(embed=temp_embedding, dim = embedding_dim)



'''
----------crossover----------
- This function handles creating a crossover between two chromosomes
-----Inputs-----
- chromosomes - The list of two chromosomes to cross over
- crossover_method - How to perform the crossover
- mutation_rate - How likely a changed bit will flip spontaneously
-----Output-----
- result - The resultant chromosome
'''
def crossover(chromosomes, crossover_method, mutation_rate):
    embed = [random.choice([chromosomes[0].embedding[i], chromosomes[1].embedding[i]]) for i in range(len(chromosomes[0].embedding))]
    embed = [x if random.uniform(0,1)>mutation_rate else (1 if (x == "0") else 0) for x in embed]
    str_embed = ""
    str_embed.join(embed)
    return Chromosome(dim=chromosomes[0].dimension, embed=str_embed, max_embed_len=chromosomes[0].max_embed_length)