import numbers
import numpy as np
from envido import Envido
from actions import game_actions, envido_actions, truco_actions, response_actions, playable_cards
from truco import Truco
from card_utils import encode_card_array, encode_envido, encode_truco
from card_game import CardGame
from dealer import Dealer

import logging


class TrucoGame:
    
    
    def __init__(self, players, goes_first=0):
        '''
        Initialize a Game of Truco.
        '''
        self.players = players
        self.dealer = Dealer()
        self.finished = False
        self.round = 0
        self.scoreboard = np.array([(p, 0) for p in players])
        self.first_move_by = self.players[goes_first] 
        self.second_move_by = self.players[1 - goes_first] 
        
        self.dealer.deal_cards_in_order(self.players)
        
        self.envido = Envido(self)
        self.truco = Truco(self)
        self.card_game = CardGame(self, self.first_move_by)

    def get_state(self, player):
        started =  1 if player == self.first_move_by else 0
        mano = 1 if player == self.get_mano() else 0
        game_config = np.array([started, mano], dtype=np.int8)
        
        score = []
        for p, s in self.scoreboard:
            p_id = 1 if player == p else 0
            turn = [p_id, s]
            score.append(np.array(turn, dtype = np.int8))
        
        score = np.vstack(score)
    
        # Order cards from highest to lowest rank to reduce potential card space
        ordered_cards = sorted(player.hand, key=lambda x: x.tier, reverse=True)
        
        player_cards = np.array(encode_card_array(ordered_cards), dtype=np.int8)
            
        state = {
            'game': game_config,
            'player_cards': player_cards,
            'score': score,
            'cards_played': self.card_game.get_state(player),
            'envido_state': self.envido.get_state(player),
            'truco_state': self.truco.get_state(player)
            
        }
            
        return state
    
    def get_score(self, player):
        if self.scoreboard[0][0] == player:
            return self.scoreboard[0][1]
        else:
            return self.scoreboard[1][1]
        
        return 0
    
    def get_winner(self):
        if self.finished:
            if self.scoreboard[0][1] >= self.scoreboard[1][1]:
                return self.scoreboard[0][0]
            else:
                return self.scoreboard[1][0]
            
        return None
        
    def get_legal_actions(self, player):
        if self.get_mano() != player:
            return []
        elif self.envido.is_active():
            return self.envido.get_legal_actions(player)  + ['fold']
        elif self.truco.is_active():
            return self.truco.get_legal_actions(player)  + ['fold']
        
        aggregate = ['fold']
        
        if self.round == 0 and not self.truco.is_started():
            aggregate.extend(self.envido.get_legal_actions(player))
    
        aggregate.extend(self.truco.get_legal_actions(player))
        
        return np.hstack((aggregate, self.card_game.get_legal_actions(player)))
            
    
    def get_cards_played(self):
        return self.card_game.cards_played

    def update_score(self, player, score):
        if player == self.scoreboard[0][0]:
            self.scoreboard[0][1] += score
        else:
            self.scoreboard[1][1] += score
        
    def get_opponent(self, player):
        if player == self.players[0]:
            return self.players[1]
        else:
            return self.players[0]
    
    def get_mano(self):
        if self.envido.envido_next is not None:
            return self.envido.envido_next
        elif self.truco.truco_next is not None:
            return self.truco.truco_next
        return self.card_game.card_next
        
    def finish_hand(self):
        self.finished = True
        logging.info("Hand finished.")
        logging.info(f"{self.scoreboard[0][0]} scored {self.scoreboard[0][1]}")
        logging.info(f"{self.scoreboard[1][0]} scored {self.scoreboard[1][1]}")
    
    def finish_round(self):
        self.round += 1
        
        first_played = self.card_game.cards_played[-2]
        second_played = self.card_game.cards_played[-1]
        comparison = first_played[1].tier - second_played[1].tier
        if comparison >= 0:
            self.card_game.switch_turn() # switch if second person wins round
            logging.debug(f"{second_played[0]} won the round playing {str(second_played[1])}. They will start the next one.")
        
        winner = self.card_game.get_winner()
        if winner is not None: 
            if self.truco.is_started():
                reward = self.truco.get_reward()
                self.update_score(winner, reward)
                logging.debug(f"{winner} was rewarded {reward} for winning truco.")
            else:
                self.update_score(winner, 1)
                logging.debug(f"{winner} was rewarded 1 for winning hand.")
            self.finish_hand()
        else:
            logging.debug("Round finished")
        
    def take_action(self, player, action):
        if self.get_mano() != player:
            logging.warning(f"{player}: Can't play out of turn.")
            return 
        
        action_played = game_actions[action] if isinstance(action, numbers.Number) else action
        
        if action_played in envido_actions:
            if self.round == 0:
                if not self.truco.is_started():
                    self.envido.take_action(player, action_played) 
                else:
                    logging.warn(f"{player}: Envido can only be played before Truco.")
            else:
                logging.warning(f"{player}: Envido can only be played in the first round")
        elif action_played in truco_actions:
            if not self.envido.is_active():
                self.truco.take_action(player, action_played)
            else:
                logging.warning(f"{player} can't call {action_played} unless envido has finished.")
        elif action_played in response_actions:
            if self.envido.is_active() and self.envido.is_valid_state(action_played):
                if self.envido.envido_next == player:
                    self.envido.take_terminal_action(player, action_played) 
                else:
                    logging.warning(f"{player} can't call {action_played} envido for others.")
            elif self.truco.is_active() and self.truco.is_valid_state(action_played):
                self.truco.take_terminal_action(player, action_played)
            else:
                logging.warning(f"{player} can't call {action_played} right now.")
        elif action_played in playable_cards:
            if not self.envido.is_active():
                if not self.truco.is_active():
                    self.card_game.take_action(player, action_played)
                else:
                    logging.warning(f"{player} can't play the card {action_played} before responding to truco.")
            else:
                logging.warning(f"{player} can't play the card {action_played} before responding to envido.")
        elif action_played == "fold":
            logging.info(f"{player} folded.")
            if self.envido.is_started():
                self.envido.fold(player)
                
            if self.truco.is_started():
                self.truco.fold(player)
            else:
                opponent = self.get_opponent(player)
                self.update_score(opponent, 1)
                logging.debug(f"{opponent} was rewarded 1 for winning hand.")
            self.finish_hand()