from deck import Deck
from player import Player
from collections import defaultdict
from itertools import combinations

# who will initialize minimum bet? and starting amount?
# changing minimum bet?


class Table:

    theRound = 0
    hand_dict = {10: "royal-flush", 9:"straight-flush", 8:"four-of-a-kind", 7:"full-house", 6:"flush", 5:"straight", 4:"three-of-a-kind", 3:"two-pairs", 2:"one-pair", 1:"highest-card"}


    def __init__(self, player_list):
        self._players = player_list
        self._deck = Deck()
        self._visible_cards = []
        self._minimumBet = 50
        self._pot = 0
        self._dealer_gen_obj = self._dealer_gen()
        self._small_blind = None
        self._big_blind = None
        self._dealer = None
        self._current_player = None
        self._skip_to_show = False
        self._last_action = None

    def new_round(self):

        # Resetting phase
        self._skip_to_show = False
        for player in self._players:
            player.reset_all()
        self._deck.reset()
        self._visible_cards.clear()
        self._pot = 0
        self._minimumBet = 50
        Table.theRound += 1


        #New round initialization phase    
        # Dealer and blinds pointers that change only at the end of the round
        self._dealer = next(self._dealer_gen_obj)
        self._small_blind = next(self._dealer_gen_obj)
        self._big_blind = next(self._dealer_gen_obj)
        self._last_action = self._big_blind
        while True:
            pointer = next(self._dealer_gen_obj)
            if pointer == self._dealer:
                break
        
        # Taking the blind amounts
        self._small_blind.change_balance(-self._minimumBet // 2)
        self._small_blind.add_investment(self._minimumBet // 2)

        self._big_blind.change_balance(-self._minimumBet)
        self._big_blind.add_investment(self._minimumBet)

        self.add_to_pot(self._minimumBet + self._minimumBet // 2)

        # Current player pointer
        self._current_player_gen_obj = self._current_player_gen()
        while True:
            self._current_player = next(self._current_player_gen_obj)
            if self._current_player == self._big_blind:
                break
        
        #Pre - flop point to player left of big blind
        self._current_player = next(self._current_player_gen_obj)
    
    @property
    def current_player(self):
        return self._current_player

    # cards on the table 
    @property
    def visible_cards(self):
        return self._visible_cards

    def add_to_visible_cards(self, _card):
        self._visible_cards.append(_card)

    @property
    def minimum_bet(self):
        return self._minimumBet

    def change_minimum_bet(self, amount):
        self._minimumBet += amount

    @property
    def pot(self):
        return self._pot

    def add_to_pot(self, amount):
        self._pot += amount
    
    @property
    def small_blind(self):
        return self._small_blind

    @property
    def big_blind(self):
        return self._big_blind

    @property
    def dealer(self):
        return self._dealer

    @property
    def skip_to_show(self):
        return self._skip_to_show

    @skip_to_show.setter
    def skip_to_show(self, _value):
        self._skip_to_show = _value
    # Deals 2 cards to each player's hand
    
    @property
    def last_action(self):
        return self._last_action
    
    # Call after flop
    def change_last_action(self):
        # If the dealer has folded pick the active palyer to the right of the dealer.
        if self._dealer.isFolded:
            previous_p = self._dealer
            current_p = next(self._dealer_gen_obj)
            while True:
                if current_p == self._dealer:
                    break
                else:
                    if not current_p.isFolded:
                        previous_p = current_p
                    current_p = next(self._dealer_gen_obj)
            self._last_action = previous_p
        else:
            self._last_action = self._dealer

    def distribute_cards(self):
        for _ in range(2):
            for p in self._players:
                if not p.bankrupt:
                    p.deal(self._deck.pick_card())
    
    def _dealer_gen(self):
        while True:
            for p in self._players:
                if p.bankrupt:
                    pass
                else:
                    yield p
    
    def _current_player_gen(self):
        while True:
            for p in self._players:
                if p.bankrupt or p.isFolded:
                    pass
                else:
                    yield p
    
    def next_player(self):
        self._current_player = next(self._current_player_gen_obj)


    def check_hand(self, hand):
        if self.check_royal_flush(hand):
            return 10
        elif self.check_straight_flush(hand):
            return 9
        elif self.check_four_of_a_kind(hand)[0]:
            return 8
        elif self.check_full_house(hand):
            return 7
        elif self.check_flush(hand):
            return 6
        elif self.check_straight(hand):
            return 5
        elif self.check_three_of_a_kind(hand)[0]:
            return 4
        elif self.check_two_pair(hand)[0]:
            return 3
        elif self.check_one_pair(hand)[0]:
            return 2
        else:
            return 1

    def check_royal_flush(self, hand):
        if self.check_straight_flush(hand):
            values = [card.rank for card in hand]
            if set(values) == set([10, 11, 12, 13, 14]):
                return True
            else:
                return False
        else:
            return False

    def check_straight_flush(self, hand):
        if self.check_flush(hand) and self.check_straight(hand):
            return True
        else:
            return False

    def check_four_of_a_kind(self, hand):
        values = [card.rank for card in hand]
        value_counts = defaultdict(lambda:0)
        for v in values:
            value_counts[v] += 1
        if sorted(value_counts.values()) == [1, 4]:
            return [True, 4 * sorted(value_counts.keys()[1])]
        return [False, None]

    def check_full_house(self, hand):
        values = [card.rank for card in hand]
        value_counts = defaultdict(lambda:0)
        for v in values:
            value_counts[v] += 1
        if sorted(value_counts.values()) == [2, 3]:
            return True
        return False
    
    def check_flush(self, hand):
        suits = [card.suit for card in hand]
        if len(set(suits)) == 1:
            return True
        else:
            return False
    
    def check_straight(self, hand):
        values = [card.rank for card in hand]
        value_counts = defaultdict(lambda:0)
        for v in values:
            value_counts[v] += 1
        rank_values = [i for i in values]
        value_range = max(rank_values) - min(rank_values)
        if len(set(value_counts.values())) == 1 and (value_range == 4):
            return True
        else:
            if set(values) == set([14, 2, 3, 4, 5]):
                return True
        if set(values) == set([11, 12, 13, 14, 2]) or set(values) == set([12, 13, 14, 2, 3]) or set(values) == set([13, 14, 2, 3, 4])  :
            return False
        return False

    def check_three_of_a_kind(self, hand):
        values = [card.rank for card in hand]
        value_counts = defaultdict(lambda:0)
        for v in values:
            value_counts[v] += 1
        if set(value_counts.values()) == set([3, 1]):
            return [True, 3 * sorted(value_counts.keys())[2]]
        else:
            return [False, None]

   
    def check_two_pair(self, hand):
        values = [card.rank for card in hand]
        value_counts = defaultdict(lambda:0)
        for v in values:
            value_counts[v] += 1
        if sorted(value_counts.values()) == [1, 2, 2]:
            return [True, 2 * (sorted(value_counts.keys())[1] + sorted(value_counts.keys())[2])]
        else:
            return [False, None]
    
    def check_one_pair(self, hand):
        values = [card.rank for card in hand]
        value_counts = defaultdict(lambda:0)
        for v in values:
            value_counts[v] += 1
        if 2 in value_counts.values():
            return [True, 2 * sorted(value_counts.keys())[3]]
        else:
            return [False, None]

    def play(self, cards):
        best_hand = 0
        best_sum = 0
        hand_sum = None
        combination = combinations(cards, 5)
        for i in combination:
            hand_value = self.check_hand(list(i))
            if hand_value > best_hand:
                best_hand = hand_value
                best_sum = sum([card.rank for card in list(i)])
                if hand_value == 2:
                    hand_sum = self.check_one_pair(list(i))[1] 
                if hand_value == 3:
                    hand_sum = self.check_two_pair(list(i))[1] 
                if hand_value == 4:
                    hand_sum = self.check_three_of_a_kind(list(i))[1] 
                if hand_value == 8:
                    hand_sum = self.check_four_of_a_kind(list(i))[1] 
            if hand_value == best_hand:
                if sum([card.rank for card in list(i)]) > best_sum:
                    best_sum = sum([card.rank for card in list(i)])
        return [best_hand, best_sum, hand_sum]

    def show(self):
        for player in self._players:
            if player.isFolded:
                continue
            else:
                print(player.name, "'s cards: ",end = "")
                for card in player.hand:
                    print(card, end = ",")
                print()
                play = self.play(player.hand + self._visible_cards)
                player.set_best_hand(play[0])
                player.set_best_sum(play[1])
                print("Best hand:", player.best_hand)
                print("Best sum:", player.best_sum)
        max_combination = max(p.best_hand for p in self._players)
        max_sum = max(p.best_sum for p in self._players if p.best_hand == max_combination)
        print("Max sum: ", max_sum, "Max combination: ", max_combination)
        ties_with_max = [p for p in self._players if p.best_hand == max_combination and p.best_sum == max_sum]

        print(len(ties_with_max))
        if len(ties_with_max) == 1: # if one player wins whole pot, no ties
            ties_with_max[0].change_balance(self.pot)
            print(ties_with_max[0].name, "has won the pot:", self.pot)
        else:
            # TODO BE MODIFIED TO CHECK FOR TIE BREAKERS
            split = self.pot / len(ties_with_max)
            for p in ties_with_max:
                p.change_balance(split)
                print(p, "has won a split of the pot:", split)

# Old big blind will become small blind, i.e., game goes clockwise
# 