"""Crossover implementations for continuous solutions kind
"""
# main imports
import random
import sys
import numpy as np

# module imports
from macop.operators.base import Crossover

class BasicDifferentialEvolutionCrossover(Crossover):
    """Basic Differential Evolution implementation for continuous solution

    Attributes:
        kind: {:class:`~macop.operators.base.KindOperator`} -- specify the kind of operator

    Example:

    >>> # import of solution and polynomial mutation operator
    >>> from macop.solutions.continuous import ContinuousSolution
    >>> from macop.operators.continuous.crossovers import BasicDifferentialEvolutionCrossover
    >>> solution = ContinuousSolution.random(5, (-2, 2))
    >>> list(solution.data)
    [-1.3760219186551894, -1.7676655513272022, 1.4647045830997407, 0.4044600469728352, 0.832290311184182]
    >>> crossover = BasicDifferentialEvolutionCrossover(interval=(-2, 2))
    >>> crossover_solution = crossover.apply(solution)
    >>> list(crossover_solution.data)
    [-1.7016619497704522, -0.43633033292228895, 2.0, -0.034751768954844, 0.6134819652022994]
    """

    def __init__(self, interval, CR=1.0, F=0.5):
        """"Basic Differential Evolution crossover initialiser in order to specify kind of Operator and interval of continuous solution

        Args:
            interval: {(float, float)} -- minimum and maximum values interval of variables in the solution
            CR: {float} -- probability to use of new generated solutions when modifying a value of current solution
            F: {float} -- degree of impact of the new generated solutions on the current solution when obtaining new solution
        """
        super().__init__()

        self.mini, self.maxi = interval
        self.CR = CR
        self.F = F


    def apply(self, solution1, solution2=None):
        """Create new solution based on solution passed as parameter

        Args:
            solution1: {:class:`~macop.solutions.base.Solution`} -- the first solution to use for generating new solution
            solution2: {:class:`~macop.solutions.base.Solution`} -- the second solution to use for generating new solution

        Returns:
            {:class:`~macop.solutions.base.Solution`}: new continuous generated solution
        """

        size = solution1.size

        solution1 = solution1.clone()

        # create two new random solutions using instance and its static method
        solution2 = solution1.random(size, interval=(self.mini, self.maxi))
        solution3 = solution1.random(size, interval=(self.mini, self.maxi))

        # apply crossover on the new computed solution
        for i in range(len(solution1.data)):

            # use of CR to change or not the current value of the solution using new solutions
            if random.uniform(0, 1) < self.CR:
                solution1.data[i] = solution1.data[i] + self.F * (solution2.data[i] - solution3.data[i])

        # repair solution if necessary
        solution1.data = self._repair(solution1)

        return solution1

    def _repair(self, solution):
        """
        Private repair function for solutions if an element is out of bounds of an expected interval 

        Args:
            solution: {:class:`~macop.solutions.base.Solution`} -- the solution to use for generating new solution

        Returns:
            {ndarray} -- repaired array of float values
        """
        return np.array([self.mini if x < self.mini else self.maxi if x > self.maxi else x for x in solution.data])