import copy

import itertools

from typing import List, Optional, Tuple

from axelrod.action import Action, actions_to_str, str_to_actions

from axelrod.evolvable_player import (
    EvolvablePlayer,
    InsufficientParametersError,
    crossover_lists,
)

from axelrod.player import Player

C, D = Action.C, Action.D

actions = (C, D)

class Cycler(Player):
    """
    A player that repeats a given sequence indefinitely.

    Names:

    - Cycler: Original name by Marc Harper
    """

    name = "Cycler"
    classifier = {
        "memory_depth": 2,
        "stochastic": False,
        "long_run_time": False,
        "inspects_source": False,
        "manipulates_source": False,
        "manipulates_state": False,
    }

    def __init__(self, cycle: str = "CCD") -> None:
        """This strategy will repeat the parameter `cycle` endlessly,
        e.g. C C D C C D C C D ...

        Special Cases
        -------------
        Cooperator is equivalent to Cycler("C")
        Defector   is equivalent to Cycler("D")
        Alternator is equivalent to Cycler("CD")

        """
        Player.__init__(self)
        self.cycle = cycle
        self.set_cycle(cycle=cycle)

    def strategy(self, opponent: Player) -> Action:
        """Actual strategy definition that determines player's action."""
        return next(self.cycle_iter)

    def set_cycle(self, cycle: str):
        """Set or change the cycle."""
        self.cycle = cycle
        self.cycle_iter = itertools.cycle(str_to_actions(self.cycle))
        self.classifier["memory_depth"] = len(cycle) - 1

class EvolvableCycler(Cycler, EvolvablePlayer):
    """Evolvable version of Cycler."""

    name = "EvolvableCycler"

    def __init__(
        self,
        cycle: Optional[str] = None,
        cycle_length: Optional[int] = None,
        mutation_probability: float = 0.2,
        mutation_potency: int = 1,
        seed: Optional[int] = None,
    ) -> None:
        EvolvablePlayer.__init__(self, seed=seed)
        cycle, cycle_length = self._normalize_parameters(cycle, cycle_length)
        Cycler.__init__(self, cycle=cycle)
        # Overwrite init_kwargs in the case that we generated a new cycle from cycle_length
        self.overwrite_init_kwargs(cycle=cycle, cycle_length=cycle_length)
        self.mutation_probability = mutation_probability
        self.mutation_potency = mutation_potency

    def _normalize_parameters(
        self, cycle=None, cycle_length=None
    ) -> Tuple[str, int]:
        """Compute other parameters from those that may be missing, to ensure proper cloning."""
        if not cycle:
            if not cycle_length:
                raise InsufficientParametersError(
                    "Insufficient Parameters to instantiate EvolvableCycler"
                )
            cycle = self._generate_random_cycle(cycle_length)
        cycle_length = len(cycle)
        return cycle, cycle_length

    def _generate_random_cycle(self, cycle_length: int) -> str:
        """
        Generate a sequence of random moves
        """
        return actions_to_str(
            self._random.choice(actions) for _ in range(cycle_length)
        )

    def mutate(self) -> EvolvablePlayer:
        """
        Basic mutation which may change any random actions in the sequence.
        """
        if self._random.random() <= self.mutation_probability:
            mutated_sequence = list(str_to_actions(self.cycle))
            for _ in range(self.mutation_potency):
                index_to_change = self._random.randint(
                    0, len(mutated_sequence) - 1
                )
                mutated_sequence[index_to_change] = mutated_sequence[
                    index_to_change
                ].flip()
            cycle = actions_to_str(mutated_sequence)
        else:
            cycle = self.cycle
        cycle, _ = self._normalize_parameters(cycle)
        return self.create_new(cycle=cycle)

    def crossover(self, other) -> EvolvablePlayer:
        """
        Creates and returns a new Player instance with a single crossover point.
        """
        if other.__class__ != self.__class__:
            raise TypeError(
                "Crossover must be between the same player classes."
            )
        cycle_list = crossover_lists(self.cycle, other.cycle, self._random)
        cycle = "".join(cycle_list)
        cycle, _ = self._normalize_parameters(cycle)
        return self.create_new(cycle=cycle, seed=self._random.random_seed_int())