#encoding: utf8
__author__ = 'Diogo Gomes'
__email__ = 'dgomes@ua.pt'
__license__ = "GPL"
__version__ = "0.1"

import copy
import card
from shoe import Shoe
from dealer import Dealer
from player import Player

BET_MULTIPLIER = 2

class Game(object):
    class Rules():
        def __init__(self, shoe_size=4, min_bet=1, max_bet=10):
            self.shoe_size = shoe_size
            self.min_bet = min_bet
            self.max_bet = max_bet
            self.bet_multiplier = BET_MULTIPLIER
        def __str__(self):
            return "RULES\tMin bet: {}, Max bet: {}, Shoe size: {}, Bet multiplier: {}".format(self.min_bet, self.max_bet, self.shoe_size, self.bet_multiplier)
    class PlayerState():
        def __init__(self, p):
            self.player = p
            self.bet = 0
            self.hand = []
            self.bust = False
            self.done = False
            self.watch = False
        def copy(self):
            return copy.deepcopy(self)
        def __str__(self):
            if isinstance(self.player, Dealer):
                return "{}".format(self.hand)
            return "{} ({}€)".format(self.hand, self.bet)
        def __repr__(self):
            return "{}".format(self.player.name)
        def hide_card(self):
            h = self.copy()
            h.hand = h.hand[1:]
            return h
        def want_to_play(self, rules):
            return self.player.want_to_play(rules)
        def take_bet(self, state, rules):
            bet = 0
            while (bet!=self.bet and self.bet!=0) or not (rules.min_bet <= bet <= rules.max_bet) :      #bets can't be 0 and double down means double down
                bet = self.player.bet(state[0].hide_card(), state[1:])
            self.bet += bet

    def __init__(self, players, shoe_size=4, debug=False, verbose=True, min_bet=1, max_bet=10, shoe=None):
        if verbose:
    #       print(chr(27) + "[2J")
            print("-"*80)
        self.verbose = verbose
        self.debug = debug
        self.rules = self.Rules(shoe_size=shoe_size, min_bet=min_bet, max_bet=max_bet)
        self.shoe = Shoe(shoe_size)
        if shoe != None:
            self.shoe = shoe
        self.shoe.shuffle()
        self.state = [self.PlayerState(Dealer())] + [self.PlayerState(p) for p in players]

        self.done = False

    def str_players_hands(self):
        o = ""
        for p in self.state[1:]:
            o+="{!s:^45}".format(p)
        return o

    def str_players_names(self):
        o = ""
        for p in self.state[1:]:
            o+="{!s:^35}".format(p.player)
        return o

    def __str__(self):
        return (\
        "{:^30}\n"\
        "╔"+"═══════════════════════════════"*(len(self.state)-1)+"╗\n"\
        "{!s:^45}\n"\
        "                         \n"\
        "                         \n"\
        "                         \n"\
        "                         \n"\
        "                         \n"\
        "{!s}\n"\
        "╚"+"═══════════════════════════════"*(len(self.state)-1)+"╝\n"\
        "{}\n"\
        ).format(self.state[0].player.name, self.state[0].hand if self.done else (["**"]+self.state[0].hide_card().hand if len(self.state[0].hand) else []), self.str_players_hands(), self.str_players_names())

    def deal(self, num):
        return self.shoe.deal_cards(1)

    def take_bets(self):
        if self.debug:
            print(self)
        for p in self.state[1:]:
            if p.want_to_play(self.rules):
                p.take_bet(self.state, self.rules)
            else:
                p.watch = True

    def loop(self):

        #deal initial cards
        self.state[0].hand += self.shoe.deal_cards(2)
        for p in self.state[1:]:
            if not p.watch:
                p.hand += self.shoe.deal_cards(2)

        turn = 0
        if card.blackjack(self.state[0].hand):  #if the dealer has blackjack there is no point in playing...
            self.done = True
            return [p for p in self.state[1:] if card.blackjack(p.hand)]

        #lets play
        while not self.done:
            turn += 1
            hits = 0
            for p in self.state[::-1]:
                if p.watch or p.bust or p.done or card.value(p.hand) == 21:  #skip players watching, bust players, players who have double down and players who already have blackjack!
                    continue

                if self.debug:
                    print("TURN {}: {}".format(turn, p.player.name))
                    print(self)
                action = ""
                while action not in ["h", "s", "d", "u"]:
                    if isinstance(p.player, Dealer):
                        action = p.player.play(self.state[0], self.state[1:])
                    else:
                        action = p.player.play(self.state[0].hide_card(), self.state[1:])
                    if action == "d" and turn != 1:
                        print("YOU CAN'T DOUBLE DOWN!!! double down is only available on the 1st turn")
                        action = ""

                if action == "u":
                    p.watch = True
                    continue

                if action == "d":
                    p.take_bet(self.state,self.rules)
                    p.done = True

                if action in ["h", "d"]:
                    p.hand+=self.deal(1)
                    hits +=1

                if card.value(p.hand) >= 21:
                    if card.value(p.hand) > 21:
                        p.bust = True
                    else:
                        p.done = True   #already has blackjack
                    if isinstance(p.player, Dealer):
                        self.done = True #game is over we already have a blackjack
            if hits == 0:
                self.done = True

        self.done = True

        return [p for p in self.state if
            not isinstance(p.player, Dealer) and    #Dealer is not really a winner
            not card.blackjack(self.state[0].hand) and  #If dealer gets blackjack no one wins
            not p.watch and  #players watching can't win :)
            not p.bust and  #bust players can't win :)
            (card.value(p.hand) >= card.value(self.state[0].hand) or self.state[0].bust)    #winners have more points then the dealer or the dealer has gone bust
            ]

    def show_table(self):
        for p in self.state[1:]:
            p.player.show(self.state)

    def payback(self, winners):
        for p in self.state[1:]:
            if p.watch:
                #check if player surrendered
                if p.bet > 0:
                    p.player.payback(-p.bet//2) #this means the player lost half his bet
                #skip watchers
                continue
            if p in winners and card.value(self.state[0].hand) == card.value(p.hand):
                p.player.payback(0)  #bet is returned
            elif p in winners:
                p.player.payback(-p.bet + p.bet*BET_MULTIPLIER)
            else:
                p.player.payback(-p.bet) #this means the player lost

    def run(self):
        self.take_bets()
        winners = self.loop()
        self.show_table()
        self.payback(winners)
        if self.verbose:
            print(self)
            print("🏆    Winners: "+str(winners))


[((59, 20, 59, 35), 'shoe.Shoe', 'Shoe', ({(59, 25, 59, 34): 'shoe_size'}, {}), '(shoe_size)', False, 'from shoe import Shoe\n'), ((115, 11, 115, 45), 'card.blackjack', 'card.blackjack', ({(115, 26, 115, 44): 'self.state[0].hand'}, {}), '(self.state[0].hand)', False, 'import card\n'), ((33, 19, 33, 38), 'copy.deepcopy', 'copy.deepcopy', ({(33, 33, 33, 37): 'self'}, {}), '(self)', False, 'import copy\n'), ((63, 39, 63, 47), 'dealer.Dealer', 'Dealer', ({}, {}), '()', False, 'from dealer import Dealer\n'), ((117, 49, 117, 71), 'card.blackjack', 'card.blackjack', ({(117, 64, 117, 70): 'p.hand'}, {}), '(p.hand)', False, 'import card\n'), ((152, 19, 152, 37), 'card.value', 'card.value', ({(152, 30, 152, 36): 'p.hand'}, {}), '(p.hand)', False, 'import card\n'), ((184, 32, 184, 62), 'card.value', 'card.value', ({(184, 43, 184, 61): 'self.state[0].hand'}, {}), '(self.state[0].hand)', False, 'import card\n'), ((184, 66, 184, 84), 'card.value', 'card.value', ({(184, 77, 184, 83): 'p.hand'}, {}), '(p.hand)', False, 'import card\n'), ((124, 50, 124, 68), 'card.value', 'card.value', ({(124, 61, 124, 67): 'p.hand'}, {}), '(p.hand)', False, 'import card\n'), ((153, 23, 153, 41), 'card.value', 'card.value', ({(153, 34, 153, 40): 'p.hand'}, {}), '(p.hand)', False, 'import card\n'), ((166, 16, 166, 50), 'card.blackjack', 'card.blackjack', ({(166, 31, 166, 49): 'self.state[0].hand'}, {}), '(self.state[0].hand)', False, 'import card\n'), ((169, 13, 169, 31), 'card.value', 'card.value', ({(169, 24, 169, 30): 'p.hand'}, {}), '(p.hand)', False, 'import card\n'), ((169, 35, 169, 65), 'card.value', 'card.value', ({(169, 46, 169, 64): 'self.state[0].hand'}, {}), '(self.state[0].hand)', False, 'import card\n')]