from axelrod.action import Action

from .memoryone import MemoryOnePlayer

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 LRPlayer(MemoryOnePlayer):
    """
    Abstraction for Linear Relation players. These players enforce a linear
    difference in stationary payoffs :math:`s (S_{xy} - l) = S_{yx} - l.`

    The parameter :math:`s` is called the slope and the parameter :math:`l` the
    baseline payoff. For extortionate strategies, the extortion factor
    :math:`\chi` is the inverse of the slope :math:`s`.

    For the standard prisoner's dilemma where :math:`T > R > P > S` and
    :math:`R > (T + S) / 2 > P`, a pair :math:`(l, s)` is enforceable iff

    .. math::
       :nowrap:

       \\begin{eqnarray}
       &P &<= l <= R \\\\
       &s_{min} &= -\min\\left( \\frac{T - l}{l - S}, \\frac{l - S}{T - l}\\right) <= s <= 1
       \\end{eqnarray}

    And also that there exists :math:`\\phi` such that

    .. math::
       :nowrap:

       \\begin{eqnarray}
          p_1 &= P(C|CC) &= 1 - \\phi (1 - s)(R - l) \\\\
          p_2 &= P(C|CD) &= 1 - \\phi (s(l - S) + (T - l)) \\\\
          p_3 &= P(C|DC) &= \\phi ((l - S) + s(T - l)) \\\\
          p_4 &= P(C|DD) &= \\phi (1 - s)(l - P)
       \\end{eqnarray}


    These conditions also force :math:`\\phi >= 0`. For a given pair :math:`(l, s)`
    there may be multiple such :math:`\\phi`.

    This parameterization is Equation 14 in [Hilbe2013]_.
    See Figure 2 of the article for a more in-depth explanation. Other game
    parameters can alter the relations and bounds above.

    Names:

    - Linear Relation player: [Hilbe2013]_
    """

    name = "LinearRelation"
    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, phi: float = 0.2, s: float = 0.1, l: float = 1) -> None:
        """
        Parameters

        phi, s, l: floats
            Parameters determining the four_vector of the LR player.
        """
        self.phi = phi
        self.s = s
        self.l = l
        super().__init__()

    def set_initial_four_vector(self, four_vector):
        pass

    def receive_match_attributes(self):
        """
        Parameters

        phi, s, l: floats
            Parameter used to compute the four-vector according to the
            parameterization of the strategies below.
        """

        R, P, S, T = self.match_attributes["game"].RPST()
        l = self.l
        phi = self.phi
        s = self.s

        # Check parameters
        s_min = -min((T - l) / (l - S), (l - S) / (T - l))
        if (l < P) or (l > R) or (s > 1) or (s < s_min):
            raise ValueError

        p1 = 1 - phi * (1 - s) * (R - l)
        p2 = 1 - phi * (s * (l - S) + (T - l))
        p3 = phi * ((l - S) + s * (T - l))
        p4 = phi * (1 - s) * (l - P)

        four_vector = [p1, p2, p3, p4]
        self.set_four_vector(four_vector)

class ZDExtort2(LRPlayer):
    """
    An Extortionate Zero Determinant Strategy with l=P.

    Names:

    - Extort-2: [Stewart2012]_
    """

    name = "ZD-Extort-2"

    def __init__(self, phi: float = 1 / 9, s: float = 0.5) -> None:
        # l = P will be set by receive_match_attributes
        super().__init__(phi, s, None)

    def receive_match_attributes(self):
        (R, P, S, T) = self.match_attributes["game"].RPST()
        self.l = P
        super().receive_match_attributes()