import random
import sys
import copy
import time

GREEN = 0
YELLOW = 1
WHITE = 2
BLUE = 3
RED = 4
ALL_COLORS = [GREEN, YELLOW, WHITE, BLUE, RED]
COLORNAMES = ["green", "yellow", "white", "blue", "red"]

class Card:
    def __init__(self, color, rank):
        self.color = color 
        self.rank = rank 
    def isColor(self, color):
        return self.color == color 
    def isRank(self, rank):
        return self.rank == rank
    def __eq__(self, other):
        if other is None: return False 
        if type(other) == tuple:
            return (self.color,self.rank) == other
        return (self.color,self.rank) == (other.color,other.rank)
    def __getitem__(self, idx):
        if idx == 0: return self.color 
        return self.rank
    def __str__(self):
        return COLORNAMES[self.color] + " " + str(self.rank)
    def __repr__(self):
        return str((self.color,self.rank))
        
    def is_useless(self, board):
        return board[self.color].rank + 1 > self.rank
        
    def is_playable(self, board):
        return board[self.color].rank + 1 == self.rank
        
    def __iter__(self):
        return iter([self.color, self.rank])

COUNTS = [3,2,2,2,1]

# semi-intelligently format cards in any format
def f(something):
    if type(something) == list:
        return list(map(f, something))
    elif type(something) == dict:
        return {k: something(v) for (k,v) in something.items()}
    elif type(something) == Card:
        return str(something)
    elif type(something) == tuple and len(something) == 2:
        return (COLORNAMES[something[0]],something[1])
    return something

def make_deck():
    deck = []
    for color in ALL_COLORS:
        for rank, cnt in enumerate(COUNTS):
            for i in range(cnt):
                deck.append(Card(color, rank+1))
    random.shuffle(deck)
    return deck
    
def initial_knowledge():
    knowledge = []
    for color in ALL_COLORS:
        knowledge.append(COUNTS[:])
    return knowledge
    
def hint_color(knowledge, color, truth):
    result = []
    for col in ALL_COLORS:
        if truth == (col == color):
            result.append(knowledge[col][:])
        else:
            result.append([0 for i in knowledge[col]])
    return result
    
def hint_rank(knowledge, rank, truth):
    result = []
    for col in ALL_COLORS:
        colknow = []
        for i,k in enumerate(knowledge[col]):
            if truth == (i + 1 == rank):
                colknow.append(k)
            else:
                colknow.append(0)
        result.append(colknow)
    return result
    
HINT_COLOR = 0
HINT_RANK = 1
PLAY = 2
DISCARD = 3
    
class Action(object):
    def __init__(self, type, player=None, color=None, rank=None, card_index=None):
        self.type = type
        self.player = player
        self.color = color
        self.rank = rank
        self.card_index = card_index
    def __str__(self):
        if self.type == HINT_COLOR:
            return "hints " + str(self.player) + " about all their " + COLORNAMES[self.color] + " cards"
        if self.type == HINT_RANK:
            return "hints " + str(self.player) + " about all their " + str(self.rank)+"s"
        if self.type == PLAY:
            return "plays card at index " + str(self.card_index)
        if self.type == DISCARD:
            return "discards card at index " + str(self.card_index)
    def __eq__(self, other):
        if other is None: return False
        return (self.type, self.player, self.color, self.rank, self.card_index) == (other.type, other.player, other.color, other.rank, other.card_index)

def format_card(card):
    return str(card)

def format_hand(hand):
    return ", ".join(map(format_card, hand))

class Game(object):
    def __init__(self, players, log=sys.stdout, format=0):
        self.players = players
        self.hits = 3
        self.hints = 8
        self.current_player = 0
        self.board = [Card(c,0) for c in ALL_COLORS]
        self.played = []
        self.deck = make_deck()
        self.extra_turns = 0
        self.hands = []
        self.knowledge = []
        self.make_hands()
        self.trash = []
        self.log = log
        self.turn = 1
        self.format = format
        self.dopostsurvey = False
        self.study = False
        if self.format:
            print(self.deck, file=self.log)
    def make_hands(self):
        handsize = 4
        if len(self.players) < 4:
            handsize = 5
        for i, p in enumerate(self.players):
            self.hands.append([])
            self.knowledge.append([])
            for j in range(handsize):
                self.draw_card(i)
    def draw_card(self, pnr=None):
        if pnr is None:
            pnr = self.current_player
        if not self.deck:
            return
        self.hands[pnr].append(self.deck[0])
        self.knowledge[pnr].append(initial_knowledge())
        del self.deck[0]
    def perform(self, action):
        for p in self.players:
            p.inform(action, self.current_player)
        if format:
            print("MOVE:", self.current_player, action.type, action.card_index, action.player, action.color, action.rank, file=self.log)
        if action.type == HINT_COLOR:
            self.hints -= 1
            print(self.players[self.current_player].name, "hints", self.players[action.player].name, "about all their", COLORNAMES[action.color], "cards", "hints remaining:", self.hints, file=self.log)
            print(self.players[action.player].name, "has", format_hand(self.hands[action.player]), file=self.log)
            for card,knowledge in zip(self.hands[action.player],self.knowledge[action.player]):
                if card.color == action.color:
                    for i, k in enumerate(knowledge):
                        if i != card.color:
                            for i in range(len(k)):
                                k[i] = 0
                else:
                    for i in range(len(knowledge[action.color])):
                        knowledge[action.color][i] = 0
        elif action.type == HINT_RANK:
            self.hints -= 1
            print(self.players[self.current_player].name, "hints", self.players[action.player].name, "about all their", action.rank, "hints remaining:", self.hints, file=self.log)
            print(self.players[action.player].name, "has", format_hand(self.hands[action.player]), file=self.log)
            for card,knowledge in zip(self.hands[action.player],self.knowledge[action.player]):
                if card.rank == action.rank:
                    for k in knowledge:
                        for i in range(len(COUNTS)):
                            if i+1 != card.rank:
                                k[i] = 0
                else:
                    for k in knowledge:
                        k[action.rank-1] = 0
        elif action.type == PLAY:
            card = self.hands[self.current_player][action.card_index]
            print(self.players[self.current_player].name, "plays", format_card(card), end=' ', file=self.log)
            if self.board[card.color][1] == card.rank-1:
                self.board[card.color] = card
                self.played.append(card)
                if card.rank == 5:
                    self.hints += 1
                    self.hints = min(self.hints, 8)
                print("successfully! Board is now", format_hand(self.board), file=self.log)
            else:
                self.trash.append(card)
                self.hits -= 1
                print("and fails. Board was", format_hand(self.board), file=self.log)
            del self.hands[self.current_player][action.card_index]
            del self.knowledge[self.current_player][action.card_index]
            self.draw_card()
            print(self.players[self.current_player].name, "now has", format_hand(self.hands[self.current_player]), file=self.log)
        else:
            self.hints += 1 
            self.hints = min(self.hints, 8)
            self.trash.append(self.hands[self.current_player][action.card_index])
            print(self.players[self.current_player].name, "discards", format_card(self.hands[self.current_player][action.card_index]), file=self.log)
            print("trash is now", format_hand(self.trash), file=self.log)
            del self.hands[self.current_player][action.card_index]
            del self.knowledge[self.current_player][action.card_index]
            self.draw_card()
            print(self.players[self.current_player].name, "now has", format_hand(self.hands[self.current_player]), file=self.log)
    def valid_actions(self):
        valid = []
        for i in range(len(self.hands[self.current_player])):
            valid.append(Action(PLAY, card_index=i))
            valid.append(Action(DISCARD, card_index=i))
        if self.hints > 0:
            for i, p in enumerate(self.players):
                if i != self.current_player:
                    for color in set([card[0] for card in self.hands[i]]):
                        valid.append(Action(HINT_COLOR, player=i, color=color))
                    for rank in set([card[1] for card in self.hands[i]]):
                        valid.append(Action(HINT_RANK, player=i, rank=rank))
        return valid
    def run(self, turns=-1):
        self.turn = 1
        while not self.done() and (turns < 0 or self.turn < turns):
            self.turn += 1
            if not self.deck:
                self.extra_turns += 1
            hands = []
            for i, h in enumerate(self.hands):
                if i == self.current_player:
                    hands.append([])
                else:
                    hands.append(h)
            valid = self.valid_actions()
            action = None
            while action not in valid:
                action = self.players[self.current_player].get_action(self.current_player, hands, copy.deepcopy(self.knowledge), self.trash[:], self.played[:], self.board[:], valid, self.hints, self.hits, len(self.deck))
                if action not in valid:
                    print("Tried to perform illegal action, retrying")
            self.perform(action)
            self.current_player += 1
            self.current_player %= len(self.players)
        print("Game done, hits left:", self.hits, file=self.log)
        points = self.score()
        print("Points:", points, file=self.log)
        return points
    def score(self):
        return sum([card.rank for card in self.board])
    def single_turn(self):
        if not self.done():
            if not self.deck:
                self.extra_turns += 1
            hands = []
            for i, h in enumerate(self.hands):
                if i == self.current_player:
                    hands.append([])
                else:
                    hands.append(h)
            action = self.players[self.current_player].get_action(self.current_player, hands, self.knowledge, self.trash, self.played, self.board, self.valid_actions(), self.hints, self.hits, len(self.deck))
            self.perform(action)
            self.current_player += 1
            self.current_player %= len(self.players)
    def external_turn(self, action): 
        if not self.done():
            if not self.deck:
                self.extra_turns += 1
            self.perform(action)
            self.current_player += 1
            self.current_player %= len(self.players)
    def done(self):
        if self.extra_turns == len(self.players) or self.hits == 0:
            return True
        for card in self.board:
            if card.rank != 5:
                return False
        return True
    def finish(self):
        if self.format:
            print("Score", self.score(), file=self.log)
            self.log.close()