import itertools

from typing import Any, Dict, List, Optional, Sequence, Text, Tuple

from axelrod.action import Action

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

from axelrod.player import Player

C, D = Action.C, Action.D

actions = (C, D)

Transition = Tuple[int, Action, int, Action]

class SimpleFSM(object):
    """Simple implementation of a finite state machine that transitions
    between states based on the last round of play.

    https://en.wikipedia.org/wiki/Finite-state_machine
    """

    def __init__(self, transitions: tuple, initial_state: int) -> None:
        """
        transitions is a list of the form
        ((state, last_opponent_action, next_state, next_action), ...)

        TitForTat would be represented with the following table:
        ((1, C, 1, C), (1, D, 1, D))
        with initial play C and initial state 1.

        """
        self._state = initial_state
        self._state_transitions = {
            (current_state, input_action): (next_state, output_action)
            for current_state, input_action, next_state, output_action in transitions
        }  # type: dict

        self._raise_error_for_bad_input()

    def _raise_error_for_bad_input(self):
        callable_states = set(
            pair[0] for pair in self._state_transitions.values()
        )
        callable_states.add(self._state)
        for state in callable_states:
            self._raise_error_for_bad_state(state)

    def _raise_error_for_bad_state(self, state: int):
        if (state, C) not in self._state_transitions or (
            state,
            D,
        ) not in self._state_transitions:
            raise ValueError(
                "state: {} does not have values for both C and D".format(state)
            )

    @property
    def state(self) -> int:
        return self._state

    @state.setter
    def state(self, new_state: int):
        self._raise_error_for_bad_state(new_state)
        self._state = new_state

    @property
    def state_transitions(self) -> dict:
        return self._state_transitions.copy()

    def transitions(self) -> list:
        return [
            [x[0], x[1], y[0], y[1]] for x, y in self._state_transitions.items()
        ]

    def move(self, opponent_action: Action) -> Action:
        """Computes the response move and changes state."""
        next_state, next_action = self._state_transitions[
            (self._state, opponent_action)
        ]
        self._state = next_state
        return next_action

    def __eq__(self, other) -> bool:
        """Equality of two FSMs"""
        if not isinstance(other, SimpleFSM):
            return False
        return (self._state, self._state_transitions) == (
            other.state,
            other.state_transitions,
        )

    def num_states(self):
        """Return the number of states of the machine."""
        return len(set(state for state, action in self._state_transitions))