"""Memory One strategies. Note that there are Memory One strategies in other
files, including titfortat.py and zero_determinant.py"""

import warnings

from typing import Optional, Tuple

from axelrod.action import Action

from axelrod.player import Player

C, D = Action.C, Action.D

class MemoryOnePlayer(Player):
    """
    Uses a four-vector for strategies based on the last round of play,
    (P(C|CC), P(C|CD), P(C|DC), P(C|DD)). Win-Stay Lose-Shift is set as
    the default player if four_vector is not given.
    Intended to be used as an abstract base class or to at least be supplied
    with a initializing four_vector.

    Names

    - Memory One: [Nowak1990]_
    """

    name = "Generic Memory One Player"
    classifier = {
        "memory_depth": 1,  # Memory-one Four-Vector
        "stochastic": True,
        "long_run_time": False,
        "inspects_source": False,
        "manipulates_source": False,
        "manipulates_state": False,
    }

    def __init__(
        self,
        four_vector: Optional[Tuple[float, float, float, float]] = None,
        initial: Action = C,
    ) -> None:
        """
        Parameters
        ----------
        four_vector: list or tuple of floats of length 4
            The response probabilities to the preceding round of play
            ( P(C|CC), P(C|CD), P(C|DC), P(C|DD) )
        initial: C or D
            The initial move

        Special Cases
        -------------

        Alternator is equivalent to MemoryOnePlayer((0, 0, 1, 1), C)
        Cooperator is equivalent to MemoryOnePlayer((1, 1, 1, 1), C)
        Defector   is equivalent to MemoryOnePlayer((0, 0, 0, 0), D)
        Random     is equivalent to MemoryOnePlayer((0.5, 0.5, 0.5, 0.5))
        (with a random choice for the initial state)
        TitForTat  is equivalent to MemoryOnePlayer((1, 0, 1, 0), C)
        WinStayLoseShift is equivalent to MemoryOnePlayer((1, 0, 0, 1), C)

        See also: The remaining strategies in this file
                  Multiple strategies in titfortat.py
                  Grofman, Joss in axelrod_tournaments.py
        """
        super().__init__()
        self._initial = initial
        self.set_initial_four_vector(four_vector)

    def set_initial_four_vector(self, four_vector):
        if four_vector is None:
            four_vector = (1, 0, 0, 1)
            warnings.warn("Memory one player is set to default (1, 0, 0, 1).")

        self.set_four_vector(four_vector)

    def set_four_vector(self, four_vector: Tuple[float, float, float, float]):
        if not all(0 <= p <= 1 for p in four_vector):
            raise ValueError(
                "An element in the probability vector, {}, is not "
                "between 0 and 1.".format(str(four_vector))
            )
        self._four_vector = dict(
            zip([(C, C), (C, D), (D, C), (D, D)], four_vector)
        )

    def _post_init(self):
        # Adjust classifiers
        values = set(self._four_vector.values())
        self.classifier["stochastic"] = any(0 < x < 1 for x in values)
        if all(x == 0 for x in values) or all(x == 1 for x in values):
            self.classifier["memory_depth"] = 0

    def strategy(self, opponent: Player) -> Action:
        if len(opponent.history) == 0:
            return self._initial
        # Determine which probability to use
        p = self._four_vector[(self.history[-1], opponent.history[-1])]
        # Draw a random number in [0, 1] to decide
        try:
            return self._random.random_choice(p)
        except AttributeError:
            return D if p == 0 else C

class ReactivePlayer(MemoryOnePlayer):
    """
    A generic reactive player. Defined by 2 probabilities conditional on the
    opponent's last move: P(C|C), P(C|D).

    Names:

    - Reactive: [Nowak1989]_
    """

    name = "Reactive Player"

    def __init__(self, probabilities: Tuple[float, float]) -> None:
        four_vector = (*probabilities, *probabilities)
        super().__init__(four_vector)