import random

# module: playing_cards
# a module that contains two classes: Card and Deck

class Card:

    #
    # instance variables:
    #    self.rank - 1..13 (1=ace, 11=jack, 12=queen, 13=king)
    #    self.suit - 0..3  (0=diamonds, 1=clubs, 2=hearts, 3=spades)
    #

    def __init__(self, r, s):
        if r >= 1 and r <= 13:
            self.__rank = r
        else:
            self.__rank = 2  # rank is 2 if they give me a bad rank

        if s >= 0 and s <= 3:
            self.__suit = s
        else:
            self.__suit = 0

    # setter methods for instance variables

    def set_rank(self, r):
        if r >= 1 and r <= 13:
            self.__rank = r
        
    def set_suit(self, s):
        if s >= 0 and s <= 3:
            self.__suit = s
        # if s is a bad value, __suit remains what it was

    # getter methods for the instance variables
    def get_suit(self):
        return self.__suit
    
    def get_rank(self):
        return self.__rank

    def get_value(self):
        # here I should return 1..10 if ace through 10,
        # return 10 if J, Q, or K
        if self.__rank >= 11:
            return 10
        else:
            return self.__rank

    # called like: if c1 == c2:
    # and c1 goes into self
    # and c2 coes into rhs
    def __eq__(self, rhs):
        return self.__rank == rhs.__rank
    
    # called like: if c1 > c2:
    def __gt__(self, rhs):
        #return self.__rank > rhs.__rank
        # consider Ace being highest rank (for >)
        if self.__rank == rhs.__rank:
            return False
        elif self.__rank == 1:
            return True
        elif rhs.__rank == 1:
            return False
        else:
            return self.__rank > rhs.__rank

    # called like: if c1 < c2:
    def __lt__(self, rhs):
        #return self.__rank < rhs.__rank
        # consider Ace being highest rank
        if self.__rank == rhs.__rank:
            return False
        elif self.__rank == 1:
            return False
        elif rhs.__rank == 1:
            return True
        else:
            return self.__rank < rhs.__rank
        
    
    # this is called by using str(____)
    def __str__(self):
        
        if self.__rank == 1:
            rank_str = 'Ace'
        elif self.__rank == 11:
            rank_str = 'Jack'
        elif self.__rank == 12:
            rank_str = 'Queen'
        elif self.__rank == 13:
            rank_str = 'King'
        else:
            rank_str = str(self.__rank)

        if self.__suit == 0:
            suit_str = 'Diamonds'
        elif self.__suit == 1:
            suit_str = 'Clubs'
        elif self.__suit == 2:
            suit_str = 'Hearts'
        else:
            suit_str = 'Spades'

        return rank_str + ' of ' + suit_str



    
        


class Deck:

    #
    # instance variable:
    #    self.__deck - a list of Card objects
    #
    
    def __init__(self):

        self.__deck = []

        for s in range(0,4):
            for r in range(1,14):
                self.__deck.append(Card(r,s))

    def cut(self):
        cutpoint = random.randint(0,51)
        self.__deck = self.__deck[cutpoint:] + self.__deck[:cutpoint]

    def shuffle(self):
        # self.__deck contains all the cards that need to be shuffled

        # an idea with random.choice is to
        # a bunch of times, get a Card using random.choice(self.deck)
        #          and remove that random Card and then append it

##        for _ in range(2000):
##            acard = random.choice(self.__deck)
##            self.__deck.remove(acard)
##            self.__deck.append(acard)

        # another idea is to get 2 random indices of self.deck
        # using this random.randint(0,51) twice
        # and swap the Cards at those two indices

        for _ in range(2000):
            idx1 = random.randint(0,51)
            idx2 = random.randint(0,51)
            temp = self.__deck[idx1]
            self.__deck[idx1] = self.__deck[idx2]
            self.__deck[idx2] = temp

    def deal(self):
        # grab the top card
        # remove it from self.deck
        # return that card
        cardtodeal = self.__deck[0]
        self.__deck.remove(cardtodeal)
        return cardtodeal
        
        

    # this is called by using str(____)
    def __str__(self):


        deck_str = ''
        for item in self.__deck:
            deck_str = deck_str + str(item) + '\n'
        return deck_str



# PokerHand class:
class PokerHand:

    # self.the_hand is the instance variable that will hold 5 cards in a list

    def __init__(self):
        self.the_hand = []

    def add_to_hand(self, acard):
        if len(self.the_hand) < 5:  # this ensures that the_hand never has more than 5 cards in it
            self.the_hand.append(acard)

    # assume this will only be called after add_to_hand has been called 5 times
    def sort_by_rank(self):
        # self.the_hand
        numpasses = len(self.the_hand) - 1

        for i in range(numpasses):
            minidx = i
            for j in range(i+1, len(self.the_hand)):
                if self.the_hand[j].get_rank() < self.the_hand[minidx].get_rank():
                    minidx = j
                
            temp = self.the_hand[minidx]
            self.the_hand[minidx] = self.the_hand[i]
            self.the_hand[i] = temp

    # return 0 for "high card",
    # return 1 for "pair"
    # return 2 for "2 pair"
    # return 3 for "3 of a kind"
    # return 4 for "straight"
    # return 5 for "flush"
    # return 6 for "full house"
    # return 7 for "four of a kind"
    # return 8 for "straight flush"
    
    def determine_hand(self):
        self.sort_by_rank()
        # assuming we have code here that checks all types 8 down to 7

        # full house
        if ((self.the_hand[0].get_rank() == self.the_hand[1].get_rank()) and \
            (self.the_hand[1].get_rank() == self.the_hand[2].get_rank()) and \
            (self.the_hand[3].get_rank() == self.the_hand[4].get_rank())) or \
           ((self.the_hand[0].get_rank() == self.the_hand[1].get_rank()) and \
            (self.the_hand[2].get_rank() == self.the_hand[3].get_rank()) and \
            (self.the_hand[3].get_rank() == self.the_hand[4].get_rank())):
            return 6


        # assuming we have code here that checks all types 6 down to 2
        
        # only check this AFTER KNOWING WE DON'T HAVE A BETTER HAND
        # is it a pair:
        if (self.the_hand[0].get_rank() == self.the_hand[1].get_rank()) or \
            (self.the_hand[1].get_rank() == self.the_hand[2].get_rank()) or \
            (self.the_hand[2].get_rank() == self.the_hand[3].get_rank()) or \
            (self.the_hand[3].get_rank() == self.the_hand[4].get_rank()):
            return 1

        return 0 # this means "high card" hand type
        
    def __str__(self):
        self.sort_by_rank()
        hand_str = ''

        for acard in self.the_hand:
            hand_str += str(acard) + '\n'
            
        return hand_str

                

                
        
        
