# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.
#

"""Provide Tournament style selection.

This implements selection based on a tournament style. In this model of
selection, two individuals are randomly chosen from the population, and
the organism with the higher fitness is considered the 'winner' and moves
to the next generation.
"""

# standard modules
import random

# local modules
from .Abstract import AbstractSelection


class TournamentSelection(AbstractSelection):
    """Implement tournament style selection."""

    def __init__(self, mutator, crossover, repairer, num_competitors=2):
        """Initialize the tournament selector.

        Arguments:
         - num_competitors - The number of individiuals that should be
           involved in a selection round. By default we just have two
           individuals (head to head!).

        See AbstractSelection for a description of the arguments to
        the initializer.
        """
        AbstractSelection.__init__(self, mutator, crossover, repairer)

        if num_competitors < 2:
            raise ValueError("Must have at least 2 competitors!")

        self._num_competitors = num_competitors

    def select(self, population):
        """Perform selection on the population using the Tournament model.

        Arguments:
         - population - A population of organisms on which we will perform
           selection. The individuals are assumed to have fitness values which
           are due to their current genome (ie. the fitness is up to date).

        """
        # we want to create a new population of the same size as the original
        new_population = []

        while len(new_population) < len(population):
            # select two individuals using tournament selection
            new_orgs = []
            # select for two individuals
            for round_num in range(2):
                competitors = []
                while len(competitors) < self._num_competitors:
                    new_org = random.choice(population)
                    if new_org not in competitors:
                        competitors.append(new_org)

                # sort the competitors by fitness, this will put them
                # from lowest to highest
                competitors.sort(key=lambda org: org.fitness)

                # get the best organism
                new_orgs.append(competitors[-1])

            assert len(new_orgs) == 2, "Expected two organisms to be selected"

            # do mutation and crossover to get the new organisms
            new_org_1, new_org_2 = self.mutate_and_crossover(new_orgs[0],
                                                             new_orgs[1])

            new_population.extend([new_org_1, new_org_2])

        return new_population
