"""Differential Evolution Variator"""

import numpy
import random
from deap import tools

def varDE(population, toolbox, cxpb=1.0, mutpb=1.0, jitter=0, low=None, up=None):
    """Part of an evolutionary algorithm applying the variation part
    of the differential evolution algorithm. The modified individuals have their
    fitness invalidated. The individuals are cloned so returned population is
    independent of the input population.

    :param population: A list of individuals to vary.
    :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution
                    operators.
    :param cxpb: The crossover probability CR: probability of a 'base' parameter
                 value being replaced with a differentially evolved parameter
                 value
    :param mutpb: The mutation rate F: a scaling of the differential vector that
                  gets added to the base parameter
    :param low: The low bound of each parameter
    :param up: The upper bound of each parameter
    :returns: A list of varied individuals that are independent of their
              parents.

    The variation goes as follows. For each member of the population, we will 
    generate a new offspring. For each offspring, we select 3 random members of 
    the parent population: a base, and 2 parents for the differential. Then we 
    select a random parameter 'index' that we will always change. Then, for each 
    parameter, if a random number [0:1] is < Cx, or if the parameter is 'index', 
    we change the parameter from base by adding the differential of that 
    parameter in each of the second two parents (multiplied by rate factor F),
    (jittered by +/- jitter/2).
    That's it! 
    """

    offspring = [toolbox.clone(ind) for ind in population]

    # Differentially evolve each base offspring:
    for individual in offspring:
        # Keep mutating until all parameters are within all boundaries:
        inside = False
        while inside == False:
            base,a,b = tools.selRandom(population, 3)
            while (base == a or base == b or a == b):
                base,a,b = tools.selRandom(population, 3)
            index = random.randrange(len(individual))
            for j, parameter in enumerate(individual):
                if j == index or random.random() < cxpb:
                    diff = (a[j]-b[j])
                    individual[j] = base[j] + (mutpb*diff) + (((random.random()*jitter)-(jitter/2))*diff)
                    # perform 'bounce' away from boundaries to maintain parameter diversity
                    if individual[j] < low[j]:
                        individual[j] = low[j]+(low[j]-individual[j])
                    elif individual[j] > up[j]:
                        individual[j] = up[j]-(individual[j]-up[j])
            # Check boundary
            inside = True
            for j, parameter in enumerate(individual):
                if individual[j] < low[j]:
                    inside = False
                elif individual[j] > up[j]:
                    inside = False
        del individual.fitness.values

    return offspring

__all__ = ['varDE']