"""Crossover implementations for discrete solutions kind
"""
# main imports
import random
import sys

# module imports
from macop.operators.base import Crossover


class SimpleCrossover(Crossover):
    """Crossover implementation which generated new solution by splitting at mean size best solution and current solution

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

    Example:

    >>> # operators import
    >>> from macop.operators.discrete.crossovers import SimpleCrossover
    >>> from macop.operators.discrete.mutators import SimpleMutation
    >>>
    >>> # policy import
    >>> from macop.policies.reinforcement import UCBPolicy
    >>>
    >>> # solution and algorithm imports
    >>> from macop.solutions.discrete import BinarySolution
    >>> from macop.algorithms.mono import IteratedLocalSearch
    >>> from macop.algorithms.mono import HillClimberFirstImprovment
    >>>
    >>> # evaluator import
    >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
    >>>
    >>> # evaluator initialization (worths objects passed into data)
    >>> worths = [ random.randint(0, 20) for i in range(10) ]
    >>> evaluator = KnapsackEvaluator(data={'worths': worths})
    >>>
    >>> # validator specification (based on weights of each objects)
    >>> weights = [ random.randint(20, 30) for i in range(10) ]
    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.data) if value == 1]) < 200 else False
    >>>
    >>> # initialiser function for binary solution using specific solution size
    >>> initialiser = lambda x=10: BinarySolution.random(x, validator)
    >>>
    >>> # operators list with crossover and mutation
    >>> simple_crossover = SimpleCrossover()
    >>> simple_mutation = SimpleMutation()
    >>> operators = [simple_crossover, simple_mutation]
    >>> policy = UCBPolicy(operators)
    >>> local_search = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
    >>> algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
    >>>
    >>> # using best solution, simple crossover is applied
    >>> best_solution = algo.run(100)
    >>> list(best_solution.data)
    [1, 1, 0, 1, 1, 1, 0, 1, 1, 1]
    >>> new_solution_1 = initialiser()
    >>> new_solution_2 = initialiser()
    >>> offspring_solution = simple_crossover.apply(new_solution_1, new_solution_2)
    >>> list(offspring_solution.data)
    [1, 0, 1, 1, 0, 0, 0, 1, 0, 0]
    """
    def apply(self, solution1, solution2=None):
        """Create new solution based on best solution found and 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 generated solution
        """

        size = solution1.size

        # copy data of solution
        firstData = solution1.data.copy()

        # copy of solution2 as output solution
        copy_solution = solution2.clone()

        splitIndex = int(size / 2)

        if random.uniform(0, 1) > 0.5:
            copy_solution.data[splitIndex:] = firstData[splitIndex:]
        else:
            copy_solution.data[:splitIndex] = firstData[:splitIndex]

        return copy_solution


class RandomSplitCrossover(Crossover):
    """Crossover implementation which generated new solution by randomly splitting best solution and current solution

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

    Example:

    >>> # operators import
    >>> from macop.operators.discrete.crossovers import RandomSplitCrossover
    >>> from macop.operators.discrete.mutators import SimpleMutation
    >>>
    >>> # policy import
    >>> from macop.policies.reinforcement import UCBPolicy
    >>>
    >>> # solution and algorithm imports
    >>> from macop.solutions.discrete import BinarySolution
    >>> from macop.algorithms.mono import IteratedLocalSearch
    >>> from macop.algorithms.mono import HillClimberFirstImprovment
    >>>
    >>> # evaluator import
    >>> from macop.evaluators.discrete.mono import KnapsackEvaluator
    >>>
    >>> # evaluator initialization (worths objects passed into data)
    >>> worths = [ random.randint(0, 20) for i in range(10) ]
    >>> evaluator = KnapsackEvaluator(data={'worths': worths})
    >>>
    >>> # validator specification (based on weights of each objects)
    >>> weights = [ random.randint(20, 30) for i in range(10) ]
    >>> validator = lambda solution: True if sum([weights[i] for i, value in enumerate(solution.data) if value == 1]) < 200 else False
    >>>
    >>> # initialiser function for binary solution using specific solution size
    >>> initialiser = lambda x=10: BinarySolution.random(x, validator)
    >>>
    >>> # operators list with crossover and mutation
    >>> random_split_crossover = RandomSplitCrossover()
    >>> simple_mutation = SimpleMutation()
    >>> operators = [random_split_crossover, simple_mutation]
    >>> policy = UCBPolicy(operators)
    >>> local_search = HillClimberFirstImprovment(initialiser, evaluator, operators, policy, validator, maximise=True, verbose=False)
    >>> algo = IteratedLocalSearch(initialiser, evaluator, operators, policy, validator, localSearch=local_search, maximise=True, verbose=False)
    >>>
    >>> # using best solution, simple crossover is applied
    >>> best_solution = algo.run(100)
    >>> list(best_solution.data)
    [1, 1, 1, 1, 1, 0, 1, 1, 0, 0]
    >>> new_solution_1 = initialiser()
    >>> new_solution_2 = initialiser()
    >>> offspring_solution = random_split_crossover.apply(new_solution_1, new_solution_2)
    >>> list(offspring_solution.data)
    [0, 0, 0, 1, 1, 1, 1, 0, 0, 1]
    """
    def apply(self, solution1, solution2=None):
        """Create new solution based on best solution found and 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 generated solution
        """
        size = solution1.size

        # copy data of solution
        firstData = solution1.data.copy()

        # copy of solution2 as output solution
        copy_solution = solution2.clone()

        splitIndex = random.randint(0, size)

        if random.uniform(0, 1) > 0.5:
            copy_solution.data[splitIndex:] = firstData[splitIndex:]
        else:
            copy_solution.data[:splitIndex] = firstData[:splitIndex]

        return copy_solution
