"""Self-play prostredie nad jednym `Round`-om (viz rl/DESIGN.md).

Jedno kolo = jedna epizoda. Prostredie je multi-agentne a tahove: v kazdom
kroku je na tahu prave jeden hrac (`Decision.player`), akciu zan doda
volajuci (zdielana siet, heuristika, ...). Odmena je sparse a terminalna --
`Round.get_points_summary()` pre vsetkych 4 hracov naraz na konci kola.

Akcie: v tipovacej faze index tipu 0..8, v hracej faze index karty 0..31
(kanonicke cislovanie z rl/encoding.py). Legalne akcie urcuje
`Decision.mask`; nelegalna akcia prebuble ako BridzikException z enginu.
"""

from random import Random
from typing import NamedTuple

from bridzik import Round, ROUNDS_PER_SERIES
from rl.encoding import encode_observation, guess_mask, index_card, play_mask

PHASE_GUESS = 'guess'
PHASE_PLAY = 'play'


class Decision(NamedTuple):
    """Jeden rozhodovaci bod: kto je na tahu, v akej faze, co vidi a co smie."""
    player: int
    phase: str
    obs: list
    mask: list


class RoundEnv:
    def __init__(self, rng: Random = None):
        self.rng = rng if rng is not None else Random()
        self.round = None

    def reset(self, round_number: int = None, first_player: int = None) -> Decision:
        """Zacne novu epizodu; nezadane parametre sa sampluju uniformne."""
        if round_number is None:
            round_number = self.rng.randrange(ROUNDS_PER_SERIES)
        if first_player is None:
            first_player = self.rng.randrange(4)
        self.round = Round(round_number, first_player, shuffler=self.rng.shuffle)
        return self._decision()

    def step(self, action: int):
        """Vykona akciu hraca na tahu.

        Vracia (decision, rewards, done): pocas kola (Decision, None, False),
        na konci kola (None, [body 4 hracov], True).
        """
        if self.round is None or self.round.is_completed():
            raise RuntimeError('Epizoda nebezi -- najprv zavolaj reset().')
        player = self.round.get_active_player()
        if not self.round.is_guessing_completed():
            self.round.add_player_guess(player, action)
        else:
            self.round.play_card(player, index_card(action))
        if self.round.is_completed():
            return None, self.round.get_points_summary(), True
        return self._decision(), None, False

    def _decision(self) -> Decision:
        player = self.round.get_active_player()
        if not self.round.is_guessing_completed():
            return Decision(player, PHASE_GUESS,
                            encode_observation(self.round, player),
                            guess_mask(self.round))
        return Decision(player, PHASE_PLAY,
                        encode_observation(self.round, player),
                        play_mask(self.round, player))
