"""
Tournament selection with or without replacement in the tournaments.

For each slot in the destination, a tournament with
:attr:`~moptipy.algorithms.modules.selections.tournament.Tournament.s`
randomly chosen participating solutions is conducted. The solution with
the best fitness wins and is copied to the destination. If
:attr:`~moptipy.algorithms.modules.selections.tournament.Tournament\
.replacement` is `True`, then the solutions are drawn with replacement
for each tournament, meaning that one solution may enter a given tournament
several times. If replacements are turned off, each solution may enter each
tournament only at most once. Either way, a solution may be selected multiple
times.

1. Peter J. B. Hancock. An Empirical Comparison of Selection Methods in
   Evolutionary Algorithms. In Terence Claus Fogarty, editor, *Selected Papers
   from the AISB Workshop on Evolutionary Computing (AISB EC'94),* April
   11-13, 1994, Leeds, UK, volume 865 of Lecture Notes in Computer Science,
   pages 80-94, Berlin/Heidelberg, Germany: Springer, ISBN: 978-3-540-58483-4.
   https://dx.doi.org/10.1007/3-540-58483-8_7. Conference organized by the
   Society for the Study of Artificial Intelligence and Simulation of
   Behaviour (AISB).
2. Uday Kumar Chakraborty and Kalyanmoy Deb and Mandira Chakraborty. Analysis
   of Selection Algorithms: A Markov Chain Approach. *Evolutionary
   Computation,* 4(2):133-167. Summer 1996. Cambridge, MA, USA: MIT Press.
   doi:10.1162/evco.1996.4.2.133.
   https://dl.acm.org/doi/pdf/10.1162/evco.1996.4.2.133
3. Tobias Blickle and Lothar Thiele. A Comparison of Selection Schemes used in
   Genetic Algorithms. Second edition, December 1995. TIK-Report 11 from the
   Eidgenössische Technische Hochschule (ETH) Zürich, Department of Electrical
   Engineering, Computer Engineering and Networks Laboratory (TIK), Zürich,
   Switzerland. ftp://ftp.tik.ee.ethz.ch/pub/publications/TIK-Report11.ps
"""

from math import inf
from typing import List, Final, Callable, Iterable, cast, Union, Optional, Any

from numpy.random import Generator

from moptipy.algorithms.modules.selection import Selection, FitnessRecord
from moptipy.utils.logger import KeyValueLogSection
from moptipy.utils.types import type_error


class Tournament(Selection):
    """Tournament selection with or without replacement in the tournament."""

    def __init__(self, s: int = 2, replacement: bool = True) -> None:
        """
        Create the tournament selection method.

        :param s: the size of the tournaments
        :param replacement: will the tournaments be with replacement?
        """
        super().__init__()
        if not isinstance(s, int):
            raise type_error(s, "s", int)
        if s < 1:
            raise ValueError(f"Tournament size must be > 1, but is {s}.")
        if not isinstance(replacement, bool):
            raise type_error(replacement, "replacement", bool)
        #: the tournament size
        self.s: Final[int] = s
        #: should we perform replacements in the tournaments?
        self.replacement: Final[bool] = replacement

    def select(self, source: List[FitnessRecord],
               dest: Callable[[FitnessRecord], Any],
               n: int, random: Generator) -> None:
        """
        Perform deterministic best selection without replacement.

        :param source: the list with the records to select from
        :param dest: the destination collector to invoke for each selected
            record
        :param n: the number of records to select
        :param random: the random number generator
        """
        # set up constants
        s: Final[int] = self.s
        r: Final[bool] = self.replacement
        l: Final[int] = len(source)

        # fast call
        choice: Final[Callable[[int, int, bool], Iterable[int]]] = \
            cast(Callable[[int, int, bool], Iterable[int]], random.choice)

        for _ in range(n):  # conduct n tournaments
            best: Optional[FitnessRecord] = None  # best competitor
            best_fitness: Union[int, float] = inf  # best fitness

            for i in choice(l, s, r):  # perform single tournament
                rec = source[i]  # get record from source
                rec_fitness = rec.fitness  # get its fitness
                if rec_fitness <= best_fitness:  # if it is better or equal...
                    best = rec  # ... rec becomes the new best record
                    best_fitness = rec_fitness  # and we remember its fitness

            dest(best)  # at end of the tournament, store best in dest

    def __str__(self):
        """
        Get the name of the tournament selection algorithm.

        :return: the name of the tournament selection algorithm
        """
        st = f"tour{self.s}"
        if self.replacement:
            return f"{st}r"
        return st

    def log_parameters_to(self, logger: KeyValueLogSection):
        """
        Log the parameters of the algorithm to a logger.

        :param logger: the logger for the parameters
        """
        super().log_parameters_to(logger)
        logger.key_value("size", self.s)
        logger.key_value("withReplacement", self.replacement)
