import random
import logging
from guizero import App, Text, TextBox, PushButton, Picture, Combo, info

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)

class Card:
    """
    The Card class represents a single playing card and is initialised by passing a suit and number.
    """
    def __init__(self, suit,  number):
        self._suit =   suit
        self._number = number
        card_order_dict = {"2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10,"J":11, "Q":12, "K":13, "A":14}
        self._value =  card_order_dict.get(number)
        image_dict = {"hearts":"❤", "clubs":"♣", "diamonds":"♦", "spades":"♠"}
        self._image = image_dict.get(suit)

    def __repr__(self):
        # return self._number + " of " + self._suit + " (" + str(self._value) + ")"
        return "[" + self._number + self._image + "]"

    @property
    def suit(self):
        """ Gets or sets the suit of the Card """
        return self._suit

    @suit.setter
    def suit(self, suit):
        if suit in ["hearts", "clubs", "diamonds", "spades"]:
            self._suit = suit.upper()
        else:
            logger.error("Tried to set an invalid suit (" + str(suit) + ") for " + repr(self) )

    @property
    def number(self):
        """ Gets or sets the number of the Card """
        return self._number

    @number.setter
    def number(self, number):
        if number in ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]:
            self._number = number
        else:
            logger.error("Tried to set an invalid number (" + str(number) + ") for " + repr(self))

    @property
    def value(self):
        """ Gets or sets the value of the Card """
        return self._value

    @value.setter
    def value(self, value):
        if value in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]:
            self._value = value
        else:
            logger.error("Tried to set an invalid value (" + str(value) + ") for " + repr(self))

class Deck:
    """
    The Deck represents a deck of playing cards in order.
    """
    def __init__(self):
        self._cards = []
        self.populate()

    def populate(self):
        suits = ["hearts", "clubs", "diamonds", "spades"]
        #numbers = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
        numbers = [str(n) for n in range(2,11)] + ["J", "Q", "K", "A"]
        self._cards = [ Card(s, n) for s in suits for n in numbers ]
        logger.info("Deck created (" + repr(self) + ")")

    def __repr__(self):
        c = len(self._cards)
        return "Deck of " + str(c) + " cards"

    def shuffler(self):
        """ This method shuffles the deck"""
        logger.info("Deck shuffled")
        random.shuffle(self._cards)

    @property
    def cards(self):
        """ Gets or sets the cards of the Deck """
        return self._cards

    # unfisnished - unsure how to do this. Does it even make sense to make a setter for this?
    @cards.setter
    def cards(self, cards):
        if 1 == 1 :
            self._cards = cards
        else:
            # ~ print("That's not an allowed deck")
            logger.error("Tried to set an invalid deck (" + str(cards) + ") for " + repr(self))

    def draw(self):
        """ Draws a card from the Deck """
        return self._cards.pop()

    def cfinder(self, card):
        """ Looks if a Card exist in the Deck """
        f = False
        for c in self._cards:
            if repr(c) == repr(card):
                logger.info("Card found in deck (" + str(card) + ")")
                f = True
        if f == False:
            logger.info("Card not found in deck (" + str(card) + ")")
        return f

class Player:
    """
    The Player represents a player with a name that can hold cards in their hand.
    """
    def __init__(self, name):
        self._name =   name
        self.p_activity = True
        self._hand =   []
        self._chips =  0
        self._bet =    0

    def __repr__(self):
        return str(self._name)

    def drawCard(self, deck):
        c = deck.draw()
        self._hand.append(c)
        logger.info("A player drew a card (" + str(c) + ")")
        return self

    # unfinished - Need to discard a specfic card instead of just the first one.
    def discard(self):
        c = self._hand.pop()
        logger.info("A player discarded a card (" + str(c) + ")")
        return c

    def show_hand(self):
        print("Your hand: " + str(self._hand))

class Game:
    """
    The Game represent the card game
    """
    def __init__(self, name):
        self.name = name
        self.g_state    = 0
        self.g_pool     = 0
        self.g_priority = 0
        self.g_round    = 0
        self.g_players  = []
        self.g_board    = []
        self.g_discard  = []
        self.g_deck     = []
        # self.p_slot1    = []
        # self.p_slot2    = []
        # self.p_slot3    = []
        # self.p_slot4    = []
        # self.p_slot5    = []
        # self.p_slot6    = []
        # self.app = App(title="Poker Game 2021", width=600, height=400, layout="grid")
        self.g_play_state = "Starting Game"
        self.main()

    def game_window(self):
        """ This makes the game window """

        player = self.g_players[self.g_priority]

        def gui_setup():
            game_state.value = "Gui Loaded"
            self.g_play_state = "Gui Setup Finished"

        def update_player():
            player = self.g_players[self.g_priority]

        def quit_game():
            exit()

        def play_game():
            play_game.disable()
            update_gui()
            gameloop()

        def update_game():
            logger.info("Updating Game") #remove later

        def get_player_names():
            return self.g_players

        def get_player1_info():
            info("Player 1 Info", "Info Stuff...")

        def get_player2_info():
            info("Player 2 Info", "Info Stuff...")

        def update_game_state():
            update_gui()
            show_game_info()
            update_game_info()

        def end_round():
            player = self.g_players[self.g_priority]
            bhp = self.find_winner(self.g_players)
            pc = len(bhp)
            # ~ bhv = self.best_hand(player)

            game_info_18.visible = True

            if pc == 1:
                game_result1.value = "[" + bhp[0]._name + "] won the pot (" + str(self.g_pool) + ")"
                bhp[0]._chips = bhp[0]._chips + self.g_pool
                self.g_pool = 0
                # self.player_cleanup(bhp[0])

            if len(bhp) == 2:
                game_result1.value = "[" + bhp[0]._name + "] splits the pot (" + str(self.g_pool) + ") with [" + bhp[1]._name + "]"
                bhp[0]._chips = bhp[0]._chips + self.g_pool/2
                bhp[1]._chips = bhp[1]._chips + self.g_pool/2
                self.g_pool = 0

            show_winning_hand(self.best_hand(player))

            for player in self.g_players:
                self.player_cleanup(player)

        def show_winning_hand(best_hand_value):
            if best_hand_value == 0:
                logger.error("Error: No winning hand was found.")
            if 1 <= best_hand_value < 2:
                game_result2.value = "Winning hand: High card"
            if 2 <= best_hand_value < 3:
                game_result2.value = "Winning hand: Pair"
            if 3 <= best_hand_value < 4:
                game_result2.value = "Winning hand: Two pair"
            if 4 <= best_hand_value < 5:
                game_result2.value = "Winning hand: Three of a kind"
            if 5 <= best_hand_value < 6:
                game_result2.value = "Winning hand: Straight"
            if 6 <= best_hand_value < 7:
                game_result2.value = "Winning hand: Flush"
            if 7 <= best_hand_value < 8:
                game_result2.value = "Winning hand: Full House"
            if 8 <= best_hand_value < 9:
                game_result2.value = "Winning hand: Four of a kind"
            if 9 <= best_hand_value < 10:
                game_result2.value = "Winning hand: Straight Flush"
            if 10 <= best_hand_value:
                game_result2.value = "Winning hand: Royal Straight Flush"

        def disable_g_buttons():
            call_bet.disable()
            raise_bet.disable()
            fold_bet.disable()

        def enable_g_buttons():
            call_bet.enable()
            raise_bet.enable()
            fold_bet.enable()

        def raise_bet():
            if int(input_bet.value) < 1:
                return
            raise_bet.disable()
            max_bet = player._chips
            if int(input_bet.value) > max_bet:
                self.bet(player, int(max_bet))
            else:
                self.bet(player, int(input_bet.value))
            input_bet.clear()
            game_signal == 1
            # ~ print("Raise")

            # ~ update_game_info()
            if 1 <= self.g_state <= 3:
                self.g_state += 1
                gameloop()
            elif self.g_state == 4:
                self.g_state += 1
                gameloop()
                disable_g_buttons()
                end_round()
                new_game.enable()

            if self.g_priority == 0:
                self.g_priority += 1
                self.computer_turn()

        def call_bet():
            call_bet.disable()
            self.call(player)
            game_signal == 1

            if 1 <= self.g_state <= 3:
                self.g_state += 1
                gameloop()
            elif self.g_state == 4:
                self.g_state += 1
                gameloop()
                disable_g_buttons()
                end_round()
                new_game.enable()

            if self.g_priority == 0:
                self.g_priority += 1
                self.computer_turn()

        def fold_bet():
            fold_bet.disable()
            # ~ print("Fold")
            self.fold(player)
            new_game.enable()

        def new_game():
            new_game.disable()
            purge_game_results()
            self.g_state == 1
            game_info_18.visible = False
            self.new_round()
            update_gui()
            gameloop()

        def purge_game_results():
            game_result1.value = ""
            game_result2.value = ""

        def update_gui():
            raise_bet.visible   = True
            call_bet.visible    = True
            fold_bet.visible    = True
            raise_bet.enable()
            call_bet.enable()
            fold_bet.enable()

        def show_g_buttons():
            raise_bet.visible   = True
            call_bet.visible    = True
            fold_bet.visible    = True

        def hide_g_buttons():
            raise_bet.visible   = False
            call_bet.visible    = False
            fold_bet.visible    = False

        def show_game_result():
            game_result1.visible = True
            game_result2.visible = True

        def hide_game_result():
            game_result1.visible = False
            game_result2.visible = False

        def show_h_cards():
            game_info_18.visible = True

        def hide_h_cards():
            game_info_18.visible = False

        def display_game_area():
            print("Displaying game area")

        def show_game_info():
            game_info_1.visible  = True
            game_info_2.visible  = True
            game_info_3.visible  = True
            game_info_4.visible  = True
            game_info_5.visible  = True
            game_info_6.visible  = True
            game_info_7.visible  = True
            game_info_8.visible  = True
            game_info_9.visible  = True
            game_info_10.visible = True
            game_info_11.visible = True
            game_info_12.visible = True
            game_info_13.visible = True
            game_info_14.visible = True
            game_info_15.visible = True
            game_info_16.visible = True
            game_info_17.visible = True
            # ~ game_info_18.visible = True
            game_info_19.visible = True
            game_info_20.visible = True

        def hide_game_info():
            game_info_1.visible  = False
            game_info_2.visible  = False
            game_info_3.visible  = False
            game_info_4.visible  = False
            game_info_5.visible  = False
            game_info_6.visible  = False
            game_info_7.visible  = False
            game_info_8.visible  = False
            game_info_9.visible  = False
            game_info_10.visible = False
            game_info_11.visible = False
            game_info_12.visible = False
            game_info_13.visible = False
            game_info_14.visible = False
            game_info_15.visible = False
            game_info_16.visible = False
            game_info_17.visible = False
            # ~ game_info_18.visible = False
            game_info_19.visible = False
            game_info_20.visible = False

        def update_game_info():
            game_info_5.value   = str(self.g_players[0]._chips)
            game_info_10.value  = str(self.g_players[1]._chips)
            game_info_12.value  = str(self.high_bet())
            game_info_14.value  = str(self.g_pool)
            game_info_16.value  = str(self.g_players[0]._hand)
            game_info_18.value  = str(self.g_players[1]._hand)
            game_info_20.value  = str(self.g_board)

        def gameloop():
            # ~ print("Game state: "+ str(self.g_state))
            if self.g_state == 1:
                game_state.value = "Starting Round 1"
                self.blinds()
                game_state.value = "Placing Blinds"
                self.deal1()
                game_state.value = "Dealing Hand Cards"
                if player._chips < 25:
                    raise_bet.disable()
                else:
                    raise_bet.enable()
            if self.g_state == 2:
                game_state.value = "The flop (3 Cards)"
                logger.info("Game state :" + str(self.g_state))
                self.deal2()
                if player._chips < 1:
                    raise_bet.disable()
                else:
                    raise_bet.enable()
            if self.g_state == 3:
                game_state.value = "The Turn (1 card)"
                logger.info("Game state :" + str(self.g_state))
                self.deal3()
                if player._chips < 1:
                    raise_bet.disable()
                else:
                    raise_bet.enable()
            if self.g_state == 4:
                game_state.value = "The River (1 card)"
                logger.info("Game state :" + str(self.g_state))
                self.deal4()
                if player._chips < 1:
                    raise_bet.disable()
                else:
                    raise_bet.enable()
            if self.g_state == 5:
                game_state.value = "End of Round"
                logger.info("Game state :" + str(self.g_state))
                logger.info("Round over")

            update_game_state()
            update_game_info()

        # ~ def player_turn():
            # ~ """ player betting round """
            # ~ player = self.g_players[self.g_priority]
            # ~ update_game_state()

            # ~ print("Player Turn Test:" + str(self.g_state))

        app = App(title="Poker Game 2021", width=1200, height=800, layout="grid")

        game_options        = Text(app, text="Game Options", grid=[1,1], size=14, font="Times New Roman", color="black", align="left")
        play_game           = PushButton(app, command=play_game, text="Play Game", grid=[1,2], align="left")
        new_game            = PushButton(app, command=new_game, text="New Game", grid=[1,3], align="left", visible=True, enabled=False)
        exit_game           = PushButton(app, command=quit_game, text="Quit Game", grid=[1,4], align="left")
        # ~ poker_pic           = Picture(app, image="Suit Icons.gif", grid=[2,1], align="left")
        player_description  = Text(app, text="Players:", grid=[1,5], size=14, font="Times New Roman", color="black", align="left")
        player1             = PushButton(app, command=get_player1_info, text="Player 1", grid=[1,6], align="left")
        player2             = PushButton(app, command=get_player2_info, text="Player 2", grid=[1,7], align="left")
        Gui_options         = Text(app, text="Gui Options:", grid=[1,8], size=14, font="Times New Roman", color="black", align="left")
        update              = PushButton(app, command=update_game_state, text="Update", grid=[1,9], align="left")
        # ~ column_seperator    = Text(app, text="|   ", grid=[2,1], size=14, font="Times New Roman", color="black", align="left")
        # ~ column_seperator2   = Text(app, text="|   ", grid=[2,2], size=14, font="Times New Roman", color="black", align="left")
        # ~ column_seperator2   = Text(app, text="|   ", grid=[2,3], size=14, font="Times New Roman", color="black", align="left")
        # ~ column_seperator2   = Text(app, text="|   ", grid=[2,4], size=14, font="Times New Roman", color="black", align="left")
        game_state          = Text(app, text="Active", grid=[7,2], size=14, font="Times New Roman", color="black", align="left", visible=False)
        input_bet           = TextBox(app, grid=[2,1])
        raise_bet           = PushButton(app, command=raise_bet, text="Bet", grid=[3,1], align="left", visible=False, enabled=False)
        call_bet            = PushButton(app, command=call_bet, text="Call", grid=[4,1], align="left", visible=False, enabled=False)
        fold_bet            = PushButton(app, command=fold_bet, text="Fold", grid=[5,1], align="left", visible=False, enabled=False)

        show_g_buttons      = PushButton(app, command=show_g_buttons, text="Show Game Buttons", grid=[1,9], align="left")
        hide_g_buttons      = PushButton(app, command=hide_g_buttons, text="Hide Game Buttons", grid=[1,10], align="left")
        show_gi             = PushButton(app, command=show_game_info, text="Show Game Info", grid=[1,11], align="left")
        hide_gi             = PushButton(app, command=hide_game_info, text="Hide Game Info", grid=[1,12], align="left")
        show_game_result    = PushButton(app, command=show_game_result, text="Show Game Results", grid=[1,13], align="left")
        hide_game_result    = PushButton(app, command=hide_game_result, text="Hide Game Results", grid=[1,14], align="left")
        show_h_cards        = PushButton(app, command=show_h_cards, text="Show Hand Cards", grid=[1,15], align="left")
        hide_h_cards        = PushButton(app, command=hide_h_cards, text="Hide Hand Cards", grid=[1,16], align="left")
        game_state_title    = Text(app, text="Game State:", grid=[1,20], size=14, font="Times New Roman", color="black", align="left")
        game_state          = Text(app, text="", grid=[1,21], size=14, font="Times New Roman", color="black", align="left")

        game_info_1         = Text(app, text="P1:", grid=[8,2], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_2         = Text(app, text=self.g_players[0]._name, grid=[9,2], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_3         = Text(app, text="     ", grid=[10,2], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_4         = Text(app, text="Chips: $", grid=[11,2], size=14, font="Times New Roman", color="black", align="right", visible=False)
        game_info_5         = Text(app, text=str(self.g_players[0]._chips), grid=[12,2], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_6         = Text(app, text="P2:", grid=[8,3], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_7         = Text(app, text=self.g_players[1]._name, grid=[9,3], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_8         = Text(app, text="     ", grid=[10,3], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_9         = Text(app, text="Chips: $", grid=[11,3], size=14, font="Times New Roman", color="black", align="right", visible=False)
        game_info_10        = Text(app, text=str(self.g_players[1]._chips), grid=[12,3], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_11        = Text(app, text="Highest bet: $", grid=[8,4], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_12        = Text(app, text=str(self.high_bet()), grid=[9,4], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_13        = Text(app, text="Pot: $", grid=[11,4], size=14, font="Times New Roman", color="black", align="right", visible=False)
        game_info_14        = Text(app, text=str(self.g_pool), grid=[12,4], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_15        = Text(app, text="Player 1 Hand cards:", grid=[8,5], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_16        = Text(app, text="", grid=[9,5], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_17        = Text(app, text="Player 2 Hand cards:", grid=[8,6], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_18        = Text(app, text="", grid=[9,6], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_19        = Text(app, text="Table cards:", grid=[8,7], size=14, font="Times New Roman", color="black", align="left", visible=False)
        game_info_20        = Text(app, text="", grid=[9,7], size=14, font="Times New Roman", color="black", align="left", visible=False)

        game_signal         = 0
        game_result1        = Text(app, text="", grid=[9,22], size=14, font="Times New Roman", color="black", align="left")
        game_result2        = Text(app, text="", grid=[9,23], size=14, font="Times New Roman", color="black", align="left")

        gui_setup()
        app.display()

    def show_game_state(self):
        """ This show the game state using text """
        print("""
        +------------------------+------------------------+
        | P1: """ + self.g_players[0]._name +
            """               | P2: """ + self.g_players[1]._name +
                """    |
        +------------------------+------------------------+
        | Chips: $""" + str(self.g_players[0]._chips) +
            """           | Chips: $""" +
                str(self.g_players[1]._chips) + """            |
        +------------------------+------------------------+
        | Pot: $""" + str(self.g_pool) +
            """ | Current bet: $""" + str(self.g_players[0]._bet) +
                """ | Highest bet: $""" + str(self.high_bet()) + """
        +------------------------+------------------------+
        | Cards in hand: """ + str(self.g_players[0]._hand) + """
        +------------------------+------------------------+
        | Cards on board: """ + str(self.g_board) + """
        +------------------------+------------------------+
        """)

    def show_hand_cards(self):
        """ This reveal/show the players cards and table cards using text """
        print("""
        +------------------------+------------------------+
        | Hand cards (""" + str(self.g_players[0]._name) + """): """ + str(self.g_players[0]._hand) + """
        +------------------------+------------------------+
        | Hand cards (""" + str(self.g_players[1]._name) + """): """ + str(self.g_players[1]._hand) + """
        +------------------------+------------------------+
        | Table cards: """ + str(self.g_board) + """
        +------------------------+------------------------+
        """)

    def main(self):
        """ sets up the main menu """

        def start_game():
            main_menu.destroy()
            self.setup()

        def quit_game():
            exit()

        main_menu = App(title="Poker Game Menu", width=150, height=250)

        # ~ poker_pic       = Picture(main_menu, image="Suit Icons.gif", align="top")
        window_title    = Text(main_menu, text="Game Menu", size=13, font="Times New Roman", color="black", align="top")
        start_game      = PushButton(main_menu, command=start_game, text="Start Game", align="top")
        exit_game       = PushButton(main_menu, command=quit_game, text="Quit Game", align="top")

        main_menu.display()

        # gp = input("Do you want to play (y/n)? ")

        # if gp == "y":
        #     print("Starting game")
        #     self.setup()
        # if gp == "n":
        #     print("Closing game")
        #     exit()

    def setup(self):
        """ sets up a game vs a computer player """
        self.g_state = 0
        self.g_round = 0
        del self.g_players[:]
        del self.g_board[:]

        # p_name = input("What is your name?")
        self.g_players.append(Player("Player 1"))
        self.g_players.append(Player("Computer player"))

        for player in self.g_players:
            player._chips = 1000
            logger.info(str(player._name) + "'s chips: " + str(player._chips))
            logger.info(player._name + " joined the game")

        self.g_state += 1
        logger.info("Game state :" + str(self.g_state))
        self.g_round += 1
        logger.info("Round 1")

        self.game_window()
        # self.gameloop()

    def new_round(self):
        """ sets up a new round """
        self.g_state = 0
        self.g_pool = 0
        del self.g_board[:]
        for player in self.g_players:
            del player._hand[:]
            player._bet = 0

        self.g_state += 1
        logger.info("Game state :" + str(self.g_state))
        self.g_round += 1
        logger.info("Round " + str(self.g_round))

        # ~ self.gameloop()

    def blinds(self):
        """ adds blinds(forced bets) """
        c1 = self.g_players[0]._chips
        b1 = self.g_players[0]._bet
        c2 = self.g_players[1]._chips
        b2 = self.g_players[1]._bet

        if self.g_priority == 0:
            c1 -= 25
            b1 += 25
            c2 -= 50
            b2 += 50
        if self.g_priority == 1:
            c1 -= 50
            b1 += 50
            c2 -= 25
            b2 += 25

        # this section should be removed (or redone) at some point.
        self.g_players[0]._chips = c1
        self.g_players[0]._bet = b1
        self.g_players[1]._chips = c2
        self.g_players[1]._bet = b2

        self.g_pool = 75

    def deal1(self):
        """ create a deck, shuffle it, and deal 2 cards to each player """
        self.g_deck =  Deck()
        self.g_deck.shuffler()

        for player in self.g_players:
            player.drawCard(self.g_deck)
            player.drawCard(self.g_deck)

    def deal2(self):
        """ Flop - three community cards are flipped face up on the table """
        self.g_board.append(self.g_deck._cards.pop())
        self.g_board.append(self.g_deck._cards.pop())
        self.g_board.append(self.g_deck._cards.pop())
        # self.show_table()

        # ~ self.player_turn()

    def deal3(self):
        """ Turn - fourth community card is flipped face up on the table """
        self.g_board.append(self.g_deck._cards.pop())
        # self.show_table()

        # ~ self.player_turn()

    def deal4(self):
        """ River - fifth community card is flipped face up on the table """
        self.g_board.append(self.g_deck._cards.pop())
        # self.show_table()

        # ~ self.player_turn()

    # ~ def player_turn(self):
        # ~ """ player betting round """
        # ~ player = self.g_players[self.g_priority]
        # ~ self.show_game_state()
        # ~ # self.show_high_bet()
        # ~ # self.show_bet(player)
        # ~ if player._chips < 50:
            # ~ gp = input("You can now fold (f), call/check (c) or quit game (q)")
        # ~ else:
            # ~ gp = input("You can now fold (f), call/check (c), raise (r) or quit game (q)")
        # ~ if gp == "f":
            # ~ self.fold(player)
        # ~ elif gp == "c":
            # ~ self.call(player)
        # ~ elif gp == "r":
            # ~ hb = self.high_bet()
            # ~ cb = player._bet
            # ~ pc = hb - cb
            # ~ print("You call " + str(pc))
            # ~ while True:
                # ~ try:
                    # ~ bet = int(input("how much do you want to bet?"))
                    # ~ break
                # ~ except:
                    # ~ print("Please give a number")
            # ~ if isinstance(bet, int):
                # ~ self.bet(player, int(bet))
        # ~ elif gp == "q":
            # ~ exit()

        # ~ if gp in ["f", "c", "r", "f"]:
            # ~ if self.g_priority == 0:
                # ~ self.g_priority += 1
                # ~ self.computer_turn()
        # ~ else:
            # ~ self.player_turn()

    def computer_turn(self):
        """ first computer betting round """
        # unfinished
        player = self.g_players[self.g_priority]
        self.call(player)

        logger.info("Computer player betted, called or raised")
        self.g_priority -= 1

    # ~ def gameloop(self):
        # ~ self.blinds()
        # ~ self.deal1()
        # ~ self.player_turn()
        # ~ if self.g_state == 1:
            # ~ self.g_state += 1
            # ~ logger.info("Game state :" + str(self.g_state))
            # ~ self.deal2()
        # ~ if self.g_state == 2:
            # ~ self.g_state += 1
            # ~ logger.info("Game state :" + str(self.g_state))
            # ~ self.deal3()
        # ~ if self.g_state == 3:
            # ~ self.g_state += 1
            # ~ logger.info("Game state :" + str(self.g_state))
            # ~ self.deal4()
        # ~ if self.g_state == 4:
            # ~ self.g_state += 1
            # ~ logger.info("Game state :" + str(self.g_state))
            # ~ self.end_round()
            # ~ logger.info("Round over")
            # ~ gp = input("You can now quit (q) or Press enter to continue")
            # ~ if gp == "q":
                # ~ exit()
            # ~ else:
                # ~ self.new_round()

    def end_round(self):
        # print("End of Round Players: ")
        # print(self.g_players)

        self.show_hand_cards()

        bhp = self.find_winner(self.g_players)
        pc = len(bhp)

        if pc == 1:
            print("[" + bhp[0]._name + "] won the pot (" + str(self.g_pool) + ")")
            bhp[0]._chips = bhp[0]._chips + self.g_pool
            self.g_pool = 0
            # self.player_cleanup(bhp[0])

        if len(bhp) == 2:
            print("[" + bhp[0]._name + "] splits the pot (" + str(self.g_pool) + ") with [" + bhp[1]._name + "]")
            bhp[0]._chips = bhp[0]._chips + self.g_pool/2
            bhp[1]._chips = bhp[1]._chips + self.g_pool/2
            self.g_pool = 0

        for player in self.g_players:
            self.player_cleanup(player)

        logger.info("End of Round")

    def find_winner(self, players):
        winners = []
        best_hand_value = 0
        for player in players:
            a = self.best_hand(player)
            if a > best_hand_value:
                if best_hand_value != 0:
                    del winners[0]
                best_hand_value = a
                winners.append(player)
            elif self.best_hand(player) == best_hand_value:
                winners.append(player)
        # ~ self.show_winning_hand(best_hand_value)
        return winners

    def show_winning_hand(self, best_hand_value):
        if best_hand_value == 0:
            logger.error("Error: No winning hand was found.")
        if 1 <= best_hand_value < 2:
            print("Winning hand: High card")
        if 2 <= best_hand_value < 3:
            print("Winning hand: Pair")
        if 3 <= best_hand_value < 4:
            print("Winning hand: Two pair")
        if 4 <= best_hand_value < 5:
            print("Winning hand: Three of a kind")
        if 5 <= best_hand_value < 6:
            print("Winning hand: Straight")
        if 6 <= best_hand_value < 7:
            print("Winning hand: Flush")
        if 7 <= best_hand_value < 8:
            print("Winning hand: Full House")
        if 8 <= best_hand_value < 9:
            print("Winning hand: Four of a kind")
        if 9 <= best_hand_value < 10:
            print("Winning hand: Straight Flush")
        if 10 <= best_hand_value:
            print("Winning hand: Royal Straight Flush")

    def best_hand(self, player):
        egh = self.make_end_hand(player)
        rv = 0

        if self.r_flush(egh) != 0:
            rv = self.r_flush(egh)
        elif self.s_flush(egh) != 0:
            rv = self.s_flush(egh)
        elif self.kind4(egh) != 0:
            rv = self.kind4(egh)
        elif self.full_house(egh) != 0:
            rv = self.full_house(egh)
        elif self.flush(egh) != 0:
            rv = self.flush(egh)
        elif self.straight(egh) != 0:
            rv = self.straight(egh)
        elif self.kind3(egh) != 0:
            rv = self.kind3(egh)
        elif self.two_pair(egh) != 0:
            rv = self.two_pair(egh)
        elif self.pair(egh) != 0:
            rv = self.pair(egh)
        elif self.high_card(egh) != 0:
            rv = self.high_card(egh)

        return rv

    def score_hand(self, cards):
        egh = cards
        rv = 0

        if self.r_flush(egh) != 0:
            print("Royal Straight Flush")
            rv = self.r_flush(egh)
        elif self.s_flush(egh) != 0:
            print("Straight Flush")
            rv = self.s_flush(egh)
        elif self.kind4(egh) != 0:
            print("4 of a kind")
            rv = self.kind4(egh)
        elif self.full_house(egh) != 0:
            print("Full House")
            rv = self.full_house(egh)
        elif self.flush(egh) != 0:
            print("Flush")
            rv = self.flush(egh)
        elif self.straight(egh) != 0:
            print("Straight")
            rv = self.straight(egh)
        elif self.kind3(egh) != 0:
            print("3 of a kind")
            rv = self.kind3(egh)
        elif self.two_pair(egh) != 0:
            print("Two pair")
            rv = self.two_pair(egh)
        elif self.pair(egh) != 0:
            print("Pair")
            rv = self.pair(egh)
        elif self.high_card(egh) != 0:
            print("High card")
            rv = self.high_card(egh)

        return rv

    def make_end_hand(self, player):
        eh = []
        for card in player._hand:
            eh.append(card)
        for card in self.g_board[:]:
            eh.append(card)
        eh.sort(key=lambda x: x._value, reverse=False)
        # print(str(player._name) + "'s end hand: ")
        # print(eh)
        return eh

    def player_cleanup(self, player):
        player._bet       = 0
        player._hand      = []   # not sure if this actually empty the hand. I should test this.
        # player.p_activity = False #  not sure if this should be implemented here or not.

    def show_table(self):
        print("Cards on table: " + str(self.g_board))

    def show_pool(self):
        print("Current pool: " + str(self.g_pool))

    def show_high_bet(self):
        print("Highest bet: " + str(self.high_bet()))

    def show_bet(self, player):
        print("Your current bet: " + str(player._bet))

    def high_bet(self):
        b = 0
        for player in self.g_players:
            if b < player._bet:
                b = player._bet
        return b

    def call(self, player):
        hb = self.high_bet()
        cb = player._bet
        pc = hb - cb

        if cb < hb:
            player._chips -= pc
            player._bet   += pc
            self.g_pool   += pc

        logger.info("Player called/checked")

    def fold(self, player):
        self.player_cleanup(player)
        logger.info("Player folded")

    def bet(self, player, bet):
        hb = self.high_bet()
        cb = player._bet
        pc = hb - cb

        if cb < hb:
            player._chips -= pc
            player._bet   += pc
            self.g_pool   += pc

        player._chips -= bet
        player._bet   += bet
        self.g_pool   += bet

        logger.info("Player raised")

    # def card_value_finder(self, value):
    #     p_hand = self.g_board + self.g_players[0]._hand
    #     for card in p_hand:
    #         if card._value == value:
    #             return True
    #     return False

    def r_flush(self, hand):
        c1 = hand
        f1 = False
        h_suit = 0
        c_suit = 0
        d_suit = 0
        s_suit = 0
        c1.sort(key=lambda x: x._value, reverse=False)
        v1 = 0
        s1 = 0
        rv = 0
        t1 = []
        t2 = []

        for card in c1:
            if card._suit == "hearts":
                h_suit += 1
            if card._suit == "clubs":
                c_suit += 1
            if card._suit == "diamonds":
                d_suit += 1
            if card._suit == "spades":
                s_suit += 1

        if h_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "hearts":
                    t1.append(card)
        elif c_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "clubs":
                    t1.append(card)
        elif d_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "diamonds":
                    t1.append(card)
        elif s_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "spades":
                    t1.append(card)

        if f1:
            for card in t1:
                if card._value > 9:
                    t2.append(card)

            v1 = c1[0]._value

            for card in t2:
                if card._value == v1:
                    s1 += 1
                    if s1 == 5:
                        rv = 10
                        return rv
                    v1 = card._value + 1
                else:
                    v1 = card._value + 1
                    s1 = 1

        return 0

    def s_flush(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=False)
        t1 = []
        f1 = False
        h_suit = 0
        c_suit = 0
        d_suit = 0
        s_suit = 0
        v1 = 0
        s1 = 1
        hv1 = 0
        rv = 0

        for card in c1:
            if card._suit == "hearts":
                h_suit += 1
            if card._suit == "clubs":
                c_suit += 1
            if card._suit == "diamonds":
                d_suit += 1
            if card._suit == "spades":
                s_suit += 1

        if h_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "hearts":
                    t1.append(card)
        elif c_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "clubs":
                    t1.append(card)
        elif d_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "diamonds":
                    t1.append(card)
        elif s_suit > 4:
            f1 = True
            for card in c1:
                if card._suit == "spades":
                    t1.append(card)

        if f1:
            v1 = t1[0]._value + 1
            for card in t1:
                if card._value == v1:
                    s1 += 1
                    if s1 > 4:
                        hv1 = card._value
                    v1 = card._value + 1
                else:
                    v1 = card._value + 1
                    s1 = 1

        if hv1 != 0:
            rv = 9 + hv1/100
            return rv
        else:
            return 0

    def kind4(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        v1 = c1[0]._value
        k1 = 0
        hv1 = 0
        hv2 = 0
        rv = 0

        for card in c1:
            if card._value == v1:
                k1 += 1
                if k1 > 3:
                    hv1 = card._value
            else:
                v1 = card._value
                k1 = 1

        if hv1 != 0:
            for card in c1:
                if card._value != hv1:
                    if card._value > hv2:
                        hv2 = card._value
            rv = 8 + hv1/100 + hv2/10000
            return rv
        else:
            return 0

    def full_house(self, hand):
        c1  = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        v1  = c1[0]._value
        h1  = 0
        hv1 = 0
        hv2 = 0
        rv  = 0

        for card in c1:
            if card._value == v1:
                h1 += 1
                if h1 == 3:
                    if hv1 < card._value:
                        hv1 = card._value
            else:
                v1 = card._value
                h1 = 1

        if hv1 != 0:
            v1 = c1[0]._value
            h1 = 0
            for card in c1:
                if card.value != hv1:
                    if card._value == v1:
                        h1 += 1
                        if h1 == 2:
                            if hv2 < card._value:
                                hv2 = card._value
                    else:
                        v1 = card._value
                        h1 = 1
        if hv2 != 0:
            rv = 7 + hv1/100 + hv2/10000
            return rv
        else:
            return 0

    def flush(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        h_suit = 0
        c_suit = 0
        d_suit = 0
        s_suit = 0
        f1 = 0
        hv1 = 0
        hv2 = 0
        hv3 = 0
        hv4 = 0
        hv5 = 0
        rv = 0

        for card in c1:
            if card._suit == "hearts":
                h_suit += 1
            if card._suit == "clubs":
                c_suit += 1
            if card._suit == "diamonds":
                d_suit += 1
            if card._suit == "spades":
                s_suit += 1

        if h_suit > 4:
            for card in c1:
                if card._suit == "hearts":
                    f1 += 1
                    if f1 == 1:
                        hv1 = card._value
                    if f1 == 2:
                        hv2 = card._value
                    if f1 == 3:
                        hv3 = card._value
                    if f1 == 4:
                        hv4 = card._value
                    if f1 == 5:
                        hv5 = card._value
            rv = 6 + hv1/100 + hv2/10000 + hv3/1000000 + hv4/100000000 + hv5/10000000000
            return rv
        elif c_suit > 4:
            for card in c1:
                if card._suit == "clubs":
                    f1 += 1
                    if f1 == 1:
                        hv1 = card._value
                    if f1 == 2:
                        hv2 = card._value
                    if f1 == 3:
                        hv3 = card._value
                    if f1 == 4:
                        hv4 = card._value
                    if f1 == 5:
                        hv5 = card._value
            rv = 6 + hv1/100 + hv2/10000 + hv3/1000000 + hv4/100000000 + hv5/10000000000
            return rv
        elif d_suit > 4:
            for card in c1:
                if card._suit == "diamonds":
                    f1 += 1
                    if f1 == 1:
                        hv1 = card._value
                    if f1 == 2:
                        hv2 = card._value
                    if f1 == 3:
                        hv3 = card._value
                    if f1 == 4:
                        hv4 = card._value
                    if f1 == 5:
                        hv5 = card._value
            rv = 6 + hv1/100 + hv2/10000 + hv3/1000000 + hv4/100000000 + hv5/10000000000
            return rv
        elif s_suit > 4:
            for card in c1:
                if card._suit == "spades":
                    f1 += 1
                    if f1 == 1:
                        hv1 = card._value
                    if f1 == 2:
                        hv2 = card._value
                    if f1 == 3:
                        hv3 = card._value
                    if f1 == 4:
                        hv4 = card._value
                    if f1 == 5:
                        hv5 = card._value
            rv = 6 + hv1/100 + hv2/10000 + hv3/1000000 + hv4/100000000 + hv5/10000000000
            return rv
        else:
            return 0

    def straight(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=False)
        v1 = c1[0]._value
        s1 = 0
        hv1 = 0
        rv = 0

        for card in c1:
            if card._value == v1:
                s1 += 1
                if s1 > 4:
                    hv1 = card._value
                v1 = card._value + 1
            else:
                v1 = card._value + 1
                s1 = 1

        if c1[-1]._value == 14:
            if s1 < 5:
                s1 = 1
                v1 = 2
                for card in c1:
                    if card._value == v1:
                        s1 += 1
                        if s1 > 4:
                            if hv1 < card._value:
                                hv1 = card._value
                        v1 = card._value +1
        if hv1 != 0:
            rv = 5 + hv1/100
            return rv
        else:
            return 0

    def kind3(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        v1 = c1[0]._value
        k1 = 0
        hv1 = 0
        hv2 = 0
        hv3 = 0
        rv = 0

        for card in c1:
            if card._value == v1:
                k1 += 1
                if k1 == 3:
                    hv1 = card._value
            else:
                v1 = card._value
                k1 = 1

        if hv1 != 0:
            for card in c1:
                if card._value != hv1:
                    if hv2 == 0:
                        hv2 = card._value
                    elif hv3 == 0:
                        hv3 = card._value
            rv = 4 + hv1/100 + hv2/10000 + hv3/1000000
            return rv
        else:
            return rv

    def two_pair(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        v1 = c1[0]._value
        p1 = 0
        hv1 = 0
        hv2 = 0
        hv3 = 0
        rv = 0

        for card in c1:
            if card._value == v1:
                p1 += 1
                if p1 == 2:
                    if hv1 == 0:
                        hv1 = card._value
                    elif hv2 == 0:
                        hv2 = card._value
                    p1 = 0
            else:
                v1 = card._value
                p1 = 1

        if hv2 != 0:
            for card in c1:
                if card._value != hv1:
                    if card._value != hv2:
                        if hv3 < card._value:
                            hv3 = card._value
            rv = 3 + hv1/100 + hv2/10000 + hv3/1000000
            return rv
        else:
            return 0

    def pair(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        v1 = c1[0]._value
        p1 = 0
        hv1 = 0
        hv2 = 0
        hv3 = 0
        hv4 = 0
        rv = 0

        for card in c1:
            if card._value == v1:
                p1 += 1
                if p1 == 2:
                    hv1 = card._value
            else:
                v1 = card._value
                p1 = 1

        if hv1 != 0:
            for card in c1:
                if card._value != hv1:
                    if hv2 == 0:
                        hv2 = card._value
                    elif hv3 == 0:
                        hv3 = card._value
                    elif hv4 == 0:
                        hv4 = card._value
            rv = 2 + hv1/100 + hv2/10000 + hv3/1000000 + hv4/100000000
            return rv
        else:
            return 0

    def high_card(self, hand):
        c1 = hand
        c1.sort(key=lambda x: x._value, reverse=True)
        hv1 = c1[0]._value
        hv2 = c1[1]._value
        hv3 = c1[2]._value
        hv4 = c1[3]._value
        hv5 = c1[4]._value
        rv = 1 + hv1/100 + hv2/10000 + hv3/1000000 + hv4/100000000 + hv5/10000000000
        return rv

g1 = Game("Poker")


'''
created by hkarthik.
'''
from operator import countOf
from mrjob.job import MRJob
import mrjob

class UpvotesAverageCalculator(MRJob):
    OUTPUT_PROTOCOL = mrjob.protocol.TextProtocol

    def mapper(self, _, line):
        try:
            # # +++your code here+++
            date = line.split('\t')[0]# date
            description = line.split('\t')[1:(len(line.split('\t'))-1)]# string containing all category tags
            upvotes = line.split('\t')[-1]# upvotes
            tags = []
            if description is not None:
                # # +++your code here+++
                tags = description[0].split(',')# get a list of all the tags from the description

                for tag in tags:
                    if (tag.rstrip()!=""):
                        yield str(date)+"\t"+tag.strip().replace("'",""),int(upvotes)
                # hint: the mapper maps each category under a date with the upvotes and the product count'''
        except:
            pass

    def combiner(self, key, values):
        # # +++your code here+++
        upvotes=0
        counts=0
        for i in values:
            upvotes += i
            counts += 1
        yield key, (upvotes,counts)
        # hint: the combiner combines to get the total upvotes and the total product counts

    def reducer(self, key, values):
        # # +++your code here+++
        total = 0
        num = 0
        for i in values:
            total += i[0]
            num += i[1]
        yield key, str(round(total/num,2))
        # the reducer combines and reduces the total upvotes and total product counts to the average number of upvotes per product

if __name__ == '__main__':
    UpvotesAverageCalculator.run()


import json
import unittest

from pycards import BaseCard, PlayingCard, PlayingCardWithImages
from pycards import BaseDeck
from pycards.errors import NoCardsRemaining

class PlayingCardTestCase(unittest.TestCase):

    def setUp(self):
        super(PlayingCardTestCase, self).setUp()

    def test_playing_card(self):
        card = PlayingCard(rank='ACE', suit='SPADES')
        self.assertEqual(card.rank, 'ACE')
        self.assertEqual(card.suit, 'SPADES')

        r = card.__repr__()
        self.assertEqual(r, '<PlayingCard: ACE of SPADES>')

        card_dict = card.to_dict()
        self.assertEqual(card_dict['rank'], 'ACE')
        self.assertEqual(card_dict['suit'], 'SPADES')

    def test_playing_card_to_json(self):
        card = PlayingCard(rank='ACE', suit='SPADES')
        card_json = card.to_json()
        card_dict = json.loads(card_json)
        self.assertEqual(card_dict['rank'], 'ACE')
        self.assertEqual(card_dict['suit'], 'SPADES')

    def test_playing_card_from_json(self):
        card_json = json.dumps({'rank': 'ACE', 'suit': 'SPADES'})
        card = PlayingCard.from_json(card_json=card_json)
        self.assertEqual(card.rank, 'ACE')
        self.assertEqual(card.suit, 'SPADES')

    def test_generate_playing_cards(self):
        cards = PlayingCard.generate_cards()
        for card in cards:
            self.assertTrue(isinstance(card, PlayingCard))

    def test_playing_card_with_images(self):
        card = PlayingCardWithImages(
            rank='ACE',
            suit='SPADES',
            front_image='whatever/ACE-SPADES.png',
            back_image='whatever/back.png')
        self.assertEqual(card.rank, 'ACE')
        self.assertEqual(card.suit, 'SPADES')
        self.assertEqual(card.front_image, 'whatever/ACE-SPADES.png')
        self.assertEqual(card.back_image, 'whatever/back.png')

        r = card.__repr__()
        self.assertEqual(r, '<PlayingCardWithImages: ACE of SPADES>')

        card_dict = card.to_dict()
        self.assertEqual(card_dict['rank'], 'ACE')
        self.assertEqual(card_dict['suit'], 'SPADES')
        self.assertEqual(card_dict['front_image'], 'whatever/ACE-SPADES.png')
        self.assertEqual(card_dict['back_image'], 'whatever/back.png')

    def test_generate_playing_cards_with_images(self):
        config = {
            'cards': ('ACE_SPADES', '2_SPADES', '3_SPADES', ),
            'image_path': 'whatever'
        }
        cards = PlayingCardWithImages.generate_cards(config=config)
        for card in cards:
            self.assertTrue(isinstance(card, PlayingCardWithImages))
            self.assertIn('whatever', card.front_image)
            self.assertIn('whatever', card.back_image)

    def test_generate_base_cards(self):
        json_config = json.dumps([
            {'rank': 'ACE', 'suit': 'SPADES'},
            {'rank': 'KING', 'suit': 'SPADES'}])
        cards = BaseCard.generate_cards(config=json_config)
        for card in cards:
            self.assertTrue(isinstance(card, BaseCard))

class BaseDeckTestCase(unittest.TestCase):

    def setUp(self):
        super(BaseDeckTestCase, self).setUp()
        self.card_config = ('ACE_SPADES', )

    def test_generate_base_deck(self):
        deck = BaseDeck.generate_deck(
            card_cls=PlayingCard,
            card_config=self.card_config)
        self.assertEqual(deck.cards_remaining, 1)
        self.assertEqual(deck.cards_removed, 0)

        card = deck.draw_card()
        self.assertTrue(isinstance(card, PlayingCard))
        self.assertEqual(card.rank, 'ACE')
        self.assertEqual(card.suit, 'SPADES')
        self.assertEqual(deck.cards_remaining, 0)
        self.assertEqual(deck.cards_removed, 1)

        deck.shuffle()
        self.assertEqual(deck.cards_remaining, 1)
        self.assertEqual(deck.cards_removed, 0)

    def test_drawing_from_empty_base_deck(self):
        deck = BaseDeck.generate_deck(
            card_cls=PlayingCard,
            card_config=self.card_config)
        self.assertEqual(deck.cards_remaining, 1)
        self.assertEqual(deck.cards_removed, 0)

        deck.draw_card()
        self.assertRaises(NoCardsRemaining, deck.draw_card)

    def test_base_deck_to_dict(self):
        deck = BaseDeck.generate_deck(
            card_cls=PlayingCard,
            card_config=self.card_config)
        deck_dict = deck.to_dict()
        self.assertEqual(deck_dict['cards_remaining'][0]['rank'], 'ACE')
        self.assertEqual(deck_dict['cards_remaining'][0]['suit'], 'SPADES')

    def test_base_deck_from_dict(self):
        deck = BaseDeck.generate_deck(
            card_cls=PlayingCard,
            card_config=self.card_config)
        deck_dict = deck.to_dict()

        new_deck = BaseDeck.from_dict(card_cls=PlayingCard, deck_dict=deck_dict)
        self.assertEqual(new_deck.cards_remaining, 1)

        card = new_deck.draw_card()
        self.assertEqual(card.suit, 'SPADES')
        self.assertEqual(card.rank, 'ACE')

    def test_base_deck_to_json(self):
        deck = BaseDeck.generate_deck(
            card_cls=PlayingCard,
            card_config=self.card_config)
        deck_json = deck.to_json()
        deck_dict = json.loads(deck_json)
        self.assertEqual(deck_dict['cards_remaining'][0]['rank'], 'ACE')
        self.assertEqual(deck_dict['cards_remaining'][0]['suit'], 'SPADES')

    def test_base_deck_from_json(self):
        deck = BaseDeck.generate_deck(
            card_cls=PlayingCard,
            card_config=self.card_config)
        deck_json = deck.to_json()

        new_deck = BaseDeck.from_json(card_cls=PlayingCard, deck_json=deck_json)
        self.assertEqual(new_deck.cards_remaining, 1)

        card = new_deck.draw_card()
        self.assertEqual(card.suit, 'SPADES')
        self.assertEqual(card.rank, 'ACE')


# coding: utf-8

import Queue
import time
import threading
import cv2

QUEUE_SIZE = 10
TIME_INTERVAL_TAKE_PHOTO = 0.03 # camera is 30 FPS

class CameraClient():
    def __init__(self, camNo = 0):
        cap = cv2.VideoCapture(camNo)
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
        self.cap = cap
        self.photoQueue = Queue.Queue(maxsize=QUEUE_SIZE)
        self.th = threading.Thread(target=self._takePhotoWorker)
        self.th.setDaemon(True)
        self.th.start()

    def _takePhotoWorker(self):
        while True:

            ret, frame = self.cap.read()
            if ret:
                if self.photoQueue.full():
                    self.photoQueue.get()
                self.photoQueue.put(frame)

            time.sleep(TIME_INTERVAL_TAKE_PHOTO)

    def getPhoto(self):
        return self.photoQueue.get()


#!/usr/bin/env python
from debe import *

class FpCongestionTagging(object):
    def __init__(self):
        self.main()

    def utc_to_local(self, utc_dt):
        import pytz, time

        local_tz = pytz.timezone('Asia/Jakarta')
        local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
        return local_tz.normalize(local_dt)

    def get_start_time(self):
        import time, timeit

        self.startText = time.strftime("%H:%M:%S")
        self.startTime = timeit.default_timer()

    def get_finish_time(self):
        import time, timeit, math

        self.elapsed = math.ceil(timeit.default_timer() - self.startTime)
        self.finishText = time.strftime("%H:%M:%S")

    # Get unprocessed kinds
    def get_kinds_unprocessed(self, limitQuery):
        data = []
        query = sessionPostgresTraffic.query(ProcessChunking).\
            filter(ProcessChunking.kind_processed == False).\
            order_by(desc(ProcessChunking.t_time)).\
            limit(limitQuery)
        for q in query:
            data.append([q.raw_id, q.kind_id, q.info])
        return data

    def cleaning(self, data):
        import re
        # remove url, pic twitter and mentionp
        datum = re.sub(r"(?:\RT @|@|pic.twitter.com|https?\://)\S+", '', data)
        # remove anything inside ()
        #datum = re.sub(r"\([^)]*\)", "", datum)
        # substitute . to space
        datum = re.sub(r"\.", " ", datum)
        # remove space more than 1
        datum = re.sub(" +", " ", datum)
        datum.replace('(','')
        datum.replace(')', '')
        datum = datum.strip()
        datum = datum.lower()
        return datum

    def tagging(self, data):
        import pickle
        from nltk.tokenize import word_tokenize
        from labels import labels

        tagger = pickle.load(open("/home/aan/congestion/tagged.pickle", "rb"))
        words = tagger.tag(word_tokenize(data))
        datum = []
        for w in words:
            if w[0] == 'arah':
                temp = [w[0], 'DC', 13]
            elif w[1] == '-None-':
                temp = [w[0], 'NN', 2]
            else:
                label = list(labels.values())[list(labels.keys()).index(w[1])]
                temp = [w[0], w[1], label]
            datum.append(temp)
        return datum

    def update_kind_data(self, kind_id):
        query = sessionPostgresTraffic.query(Kind).\
            filter(Kind.id == kind_id).\
            first()
        query.processed = True
        sessionPostgresTraffic.commit()

    def insert_word_data(self, data):
        seq = 0
        for d in data[4]:
            temp = Word(
                raw_id = data[0],
                tag_id = d[2],
                sequence = seq,
                name = d[0]
            )
            seq = seq + 1
            sessionPostgresTraffic.add(temp)

    def insert_syllable_data(self, data):
        seq = 0
        for d in data[4]:
            temp = Syllable(
                user_id = 3,
                raw_id = data[0],
                tag_id = d[2],
                sequence = seq,
                name = d[0]
            )
            seq = seq + 1
            sessionPostgresTraffic.add(temp)

    def main(self):
        limitQuery = 200
        results = []
        data = self.get_kinds_unprocessed(limitQuery)
        if len(data) > 0:
            for d in data:
                dCleaned = self.cleaning(d[2])
                dTagged = self.tagging(dCleaned)
                #self.insert_syllable_data([d[0], d[1], d[2], dCleaned, dTagged])
                self.insert_word_data([d[0], d[1], d[2], dCleaned, dTagged])
                results.append([d[0], d[1], d[2], dCleaned, dTagged])
            sessionPostgresTraffic.commit()
        else:
            results = []

        if len(results) > 0:
            for r in results:
                self.update_kind_data(r[1])
                print(r)

def main():
    FpCongestionTagging()

if __name__ == '__main__':
    main()

{'pytz': 'pytz', 'time': 'time', 'timeit': 'timeit', 'math': 'math', 're': 're', 'pickle': 'pickle', 'word_tokenize': 'nltk.tokenize.word_tokenize', 'labels': 'labels.labels'}


import requests
import json
import threading

with open('config.json') as config:
    config = json.load(config)
subdomain = config['subdomain']
auth = config['authentication']

def get_tickets():
    url = f"https://{subdomain}.zendesk.com/api/v2/requests/open.json"
    headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Basic {auth}',
    }
    response = requests.request("GET", url, headers=headers)
    return json.dumps(response.json()["requests"], indent=4)

def format_ticket(ticket_number, tickets):
    # Return important ticket data
    return {
        "subject": tickets[ticket_number]["subject"],
        "description": tickets[ticket_number]["description"],
        "requester_id": tickets[ticket_number]["requester_id"],
        "status": tickets[ticket_number]["status"],
        "created_at": tickets[ticket_number]["created_at"],
    }

class ThreadWithReturnValue(threading.Thread):
    def __init__(self, *init_args, **init_kwargs):
        threading.Thread.__init__(self, *init_args, **init_kwargs)
        self._return = None
    def run(self):
        self._return = self._target(*self._args, **self._kwargs)
    def join(self):
        threading.Thread.join(self)
        return self._return


import logging.config
import re
from logging import Formatter

from logger.errors import LoggingLevelError

LOGGING_LEVEL_LIST = [
    "CRITICAL",  # 50
    "ERROR",  # 40
    "WARNING",  # 30
    "INFO",  # 20
    "DEBUG",  # 10
    "NOTSET",  # 0
]

class WhitespacesStripperFormatter(Formatter):
    def format(self, record):
        record = super().format(record)
        record = re.sub(r"\n+", " ", record)
        return record.strip()

LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": True,
    "formatters": {
        "file": {
            "()": "logger.WhitespacesStripperFormatter",
            "format": "%(asctime)s - %(levelname)s - %(filename)s - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
        "console": {
            "()": "logger.WhitespacesStripperFormatter",
            "format": "[%(asctime)s] [%(levelname)s] %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout",  # Default is stderr
            "formatter": "console",
            "level": None,  # Defined in ConfigureLogger
        },
    },
    "loggers": {
        "log": {
            "handlers": ["console"],
            "propagate": False,
            "level": "DEBUG",  # Should be lower than handlers levels
        }
    },
}

def ConfigureLogger(log_file: str = "", console_level: str = "INFO") -> None:
    """
    Use LOGGING_CONFIG as configuration for logger.

    :param logger_type: if set, log in a file too. By default log only in console
    :param console_level: choose console log level
    """

    # Check log level
    level = console_level.upper()
    if level not in LOGGING_LEVEL_LIST:
        error = (
            f"'{level}' is not a correct level for logging. "
            f"Log level must be in {LOGGING_LEVEL_LIST}."
        )
        raise LoggingLevelError(error)

    # Manually set console log level
    LOGGING_CONFIG["handlers"]["console"]["level"] = level

    # If logger_name is set
    if log_file:
        LOGGING_CONFIG["loggers"]["log"]["handlers"].append("file")
        LOGGING_CONFIG["handlers"]["file"] = {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": f"{log_file}.log",
            "mode": "a",
            "formatter": "file",
            "level": "DEBUG",
            "maxBytes": 512000,
            "backupCount": 10,
        }

    # Configure logging
    logging.config.dictConfig(LOGGING_CONFIG)


#!/usr/bin/env python
# coding: utf-8
import numpy as np
import matplotlib.pylab as plt

Tolerance=0.
size=200
positive_charges=8
negative_charges=7

x_pos=np.random.randint(.05*size,.95*size,positive_charges)
y_pos=np.random.randint(.05*size,.95*size,positive_charges)

x_neg=np.random.randint(.05*size,.95*size,negative_charges)
y_neg=np.random.randint(.05*size,.95*size,negative_charges)

def geometry(mat):
    mat[:,0]=0
    mat[:,-1]=0
    mat[0,:]=0
    mat[-1,:]=0
    mat[x_pos,y_pos]=1
    mat[x_neg,y_neg]=-1

def time_step(mat):
    copia=np.zeros(mat.shape)
    copia[1:-1,1:-1]=(mat[2:,1:-1]+mat[0:-2,1:-1]+mat[1:-1,2:]+mat[1:-1,:-2])/4.
    geometry(copia)
    diff=(np.sqrt((mat-copia)**2).sum()/(100*100))
    return diff,copia

mat=2*np.random.random((size,size))-1
for t in range(1000000):
    diff,mat=time_step(mat)
    if t%5000==0:
        print(t,diff)
    if diff<=Tolerance:
        print(t,diff)
        break

pl=plt.imshow(mat,cmap='RdBu',vmin=-1,vmax=1)
plt.colorbar(pl)
plt.show()


import http.server
import socketserver
from urllib.parse import urlparse
from urllib.parse import parse_qs
from lxml import etree as ET
import svgling
from svgling.figure import Caption, SideBySide, RowByRow

def treeWidth(tree):
    if len(tree.getchildren()) == 0:
        return 1
    else:
        count = len(tree.getchildren())
        it = 0
        width = 0
        while it < count:
            width = width + treeWidth(tree.getchildren()[it])
            it += 1
        return width

def treeDepth(node):
    depth = -1
    while node is not None:
        depth += 1
        node = node.getparent()
    return depth

def treeForEval(tree):
    if len(tree.getchildren()) == 0:
        out = '"' + tree.attrib['text'] + '"'
        return out
    else:
        count = len(tree.getchildren())
        it = 0
        out = '"' + tree.attrib['text'] + '"'
        while it < count:
            out += ", (" + treeForEval(tree.getchildren()[it]) + ")"
            it += 1
    return out

class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        # Sending an '200 OK' response
        self.send_response(200)

        query_components = parse_qs(urlparse(self.path).query)
        if query_components != None:
            print(query_components)
        tree = ET.parse('tree.xml')
        name = None
        addAs = None
        ref = None
        if 'addAs' in query_components:
            addAs = query_components["addAs"][0]
        if 'name' in query_components:
            name = query_components["name"][0]
        if 'ref' in query_components:
            ref = query_components["ref"][0]
        if 'delete' in query_components:
            for elem in tree.iter():
                if 'text' in elem.attrib:
                    if elem.attrib['text'] == name:
                        parent = elem.getparent()
                        children = elem.getchildren()
                        parent.remove(elem)
                        it = 0
                        while it < len(children):
                            parent.append(children[it])
                            it += 1
                        with open('./tree.xml', 'w') as f:
                            f.write('<?xml version="1.0" encoding="utf-8"?>\n')
                        with open('./tree.xml', 'a') as f:
                            f.write(ET.tostring(
                                tree, pretty_print=True).decode("utf-8"))
                        self.send_header("Content-type", "text/html")
                        self.end_headers()
                        html = f'<meta http-equiv="refresh" content="0.1; url=https://rooted.ddnss.de/tree.xml" />
Deleting...
'
                        self.wfile.write(bytes(html, "utf8"))
                        return

        for elem in tree.iter():
            if 'text' in elem.attrib:
                if name == elem.attrib['text']:
                    self.send_header("Content-type", "text/html")
                    self.end_headers()
                    html = f'<meta http-equiv="refresh" content="10; url=https://rooted.ddnss.de/tree.xml" />Species ' + name + \
                        ' already in tree.
<button>Delete ' + name + '</button>'
                    self.wfile.write(bytes(html, "utf8"))
                    return

        for elem in tree.iter():
            # print(treeDepth(elem))
            # print(elem.attrib)
            # print(treeWidth(elem))
            if 'text' in elem.attrib:
                if addAs == 'child':
                    if elem.attrib['text'] == ref:
                        new = ET.fromstring('<node text="' + name + '"/>')
                        print(new)
                        elem.append(new)
                        self.send_header("Content-type", "text/html")
                        self.end_headers()
                        html = f'<meta http-equiv="refresh" content="0.1; url=https://rooted.ddnss.de/tree.xml" />
Processing... ' + addAs + '
' + \
                            name + ' as ' + addAs + ' from ' + ref + '
'
                        self.wfile.write(bytes(html, "utf8"))
                        # for elem in tree.iter():
                        #   elem.set('width', value=str(treeWidth(elem)).encode("utf-8").decode("utf-8"))
                        #   elem.set('depth', value=str(treeDepth(elem)).encode("utf-8").decode("utf-8"))
                        with open('./tree.xml', 'w') as f:
                            f.write('<?xml version="1.0" encoding="utf-8"?>\n')
                        with open('./tree.xml', 'a') as f:
                            f.write(ET.tostring(
                                tree, pretty_print=True).decode("utf-8"))
                        return
                elif addAs == 'parent':
                    if elem.attrib['text'] == ref:
                        x = 0
                        parent = elem.getparent()
                        while x < len(parent.getchildren()):
                            print(ref)
                            print(parent.getchildren()[x].attrib['text'])
                            if parent.getchildren()[x].attrib['text'] == ref:
                                print('found')
                                tmp = parent.getchildren()[x]
                                parent.remove(parent.getchildren()[x])
                                new = ET.fromstring(
                                    '<node text="' + name + '"/>')
                                new.append(tmp)
                                parent.append(new)
                            x = x + 1
                        self.send_header("Content-type", "text/html")
                        self.end_headers()
                        html = f'<meta http-equiv="refresh" content="0.1; url=https://rooted.ddnss.de/tree.xml" />
Processing... ' + addAs + '
' + \
                            name + ' as ' + addAs + ' from ' + ref + '
'
                        self.wfile.write(bytes(html, "utf8"))
                    # for elem in tree.iter():
                    #   elem.set('width', value=str(treeWidth(elem)).encode("utf-8").decode("utf-8"))
                    #   elem.set('depth', value=str(treeDepth(elem)).encode("utf-8").decode("utf-8"))
                        with open('./tree.xml', 'w') as f:
                            f.write('<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE top SYSTEM "top.dtd">\n')
                        with open('./tree.xml', 'a') as f:
                            f.write(ET.tostring(
                                tree, pretty_print=True).decode("utf-8"))
                        return

        # for elem in tree.iter():
        #    elem.set('width', value=str(treeWidth(elem)).encode("utf-8").decode("utf-8"))
        #    elem.set('depth', value=str(treeDepth(elem)).encode("utf-8").decode("utf-8"))
        with open('./tree.xml', 'w') as f:
            f.write('<?xml version="1.0" encoding="utf-8"?>\n')
        with open('./tree.xml', 'a') as f:
            f.write(ET.tostring(tree, pretty_print=True).decode("utf-8"))

        # Setting the header
        self.send_header("Content-type", "text/xml")
        #self.send_header('Access-Control-Allow-Origin', 'https://rooted.ddnss.de/')
        # Whenever using 'send_header', you also have to call 'end_headers'
        self.end_headers()

        # Extract query param
        #name = 'World'
        for elem in tree.iter():
            if 'text' in elem.attrib:
                if elem.attrib['text'] == 'Life':
                    t = svgling.draw_tree(eval(treeForEval(elem)))
                    t.get_svg().saveas("tree.svg")
        with open('/var/www/rooted/tree.xml', 'rb') as file:
            self.wfile.write(file.read())
            # print(file.read().decode())
        return

# Create an object of the above class
print('Hallo')
handler_object = MyHttpRequestHandler

PORT = 8000
my_server = socketserver.TCPServer(("", PORT), handler_object)
my_server.allow_reuse_address = True

# Star the server
my_server.serve_forever()


import numpy as np
import cv2
import os

class Transformer(object):
    def __init__(self, input_dir=None, output_dir=None, data=None, image_size=None):
        self.input_dir = input_dir
        self.output_dir = output_dir
        self.image_name = data.keys()
        self.data = data
        self.image_size = tuple(image_size)
        self.dist_vertices = np.asarray(
            [[0, 0], [image_size[0], 0], [image_size[0], image_size[1]], [0, image_size[1]]])
        # transformed images
        self.image_set = dict()

    def transform(self):
        for image_name in self.image_name:
            image_path = os.path.join(self.input_dir, image_name)
            image = cv2.imread(image_path)
            src_vertices = np.asarray(self.data[image_name])
            M = cv2.getPerspectiveTransform(
                src_vertices.astype(np.float32), self.dist_vertices.astype(np.float32))
            image = cv2.warpPerspective(
                image, M, self.image_size).astype(np.uint8)
            self.image_set.setdefault(image_name, image)

    def save_images(self):
        for image_name in self.image_name:
            out_name = image_name.split(".")[0] + "_out.jpg"
            out_path = os.path.join(self.output_dir, out_name)
            out_image = self.image_set[image_name]
            cv2.imwrite(out_path, out_image)


import regression as reg
import data as d
import numpy as np
import matplotlib.pyplot as plt
import random

df = d.read_file_columns()

train_df,valid_df,test_df = d.split_groups(df)

x_train_features,y_train_values = d.get_x_y(train_df)
x_valid_features,y_valid_values = d.get_x_y(valid_df)
x_test_features,y_test_values = d.get_x_y(test_df)

font1 = {'family':'serif','size':20}
font2 = {'family':'serif','size':15}

def algorithm1():
    epochs = 100
    num_tests = 6
    alpha_nums = []
    for i in range(num_tests):
        alpha = random.uniform(0.01,0.05)
        alpha_nums.append(alpha)
        error_train_list,error_valid_list = reg.regression(
            x_train_features,y_train_values,x_valid_features,y_valid_values,alpha,epochs)

        plt.figure()
        p1 = plt.scatter(x=list(range(epochs)), y=error_train_list,color="lightskyblue")
        p2 = plt.scatter(x=list(range(epochs)), y=error_valid_list,color="lightgreen")

        plt.legend((p1,p2),["Train error","Validation error"])

        plt.title("Train and Validation Error vs. Epoch",fontdict=font1)
        plt.xlabel("Epoch",fontdict=font2)
        plt.ylabel("Error",fontdict=font2)
        #plt.show()
        plt.savefig(f"plots/plot_{i}.png")

    textfile = open("alpha_nums.txt", "w")
    for element in alpha_nums:
        textfile.write(str(element) + "\n")
    textfile.close()

def algorithm2():
    epochs = 100
    alpha = 0.05
    error_train_list,error_valid_list,error_test_list, = reg.regression2(
        x_train_features,y_train_values,x_valid_features,y_valid_values,x_test_features,y_test_values,alpha,epochs)

    plt.figure()
    p1 = plt.scatter(x=list(range(epochs)), y=error_train_list,color="blue",alpha=0.75)
    p2 = plt.scatter(x=list(range(epochs)), y=error_valid_list,color="green",alpha=0.75)
    p3 = plt.scatter(x=list(range(epochs)), y=error_test_list,color="red",alpha=0.75)

    plt.legend((p1,p2,p3),["Train error","Validation error","Test error"])

    plt.title("Train, Validation and Test Error vs. Epoch",fontdict=font1)
    plt.xlabel("Epoch",fontdict=font2)
    plt.ylabel("Error",fontdict=font2)
    #plt.show()
    plt.savefig("plots/plot_test.png")

def algorithm3():
    epochs = 100
    alphas = [0.005, 0.5]
    for i in range(2):
        alpha = alphas[i]
        error_train_list,error_valid_list = reg.regression(
            x_train_features,y_train_values,x_valid_features,y_valid_values,alpha,epochs)

        plt.figure()
        p1 = plt.scatter(x=list(range(epochs)), y=error_train_list,color="lightskyblue")
        p2 = plt.scatter(x=list(range(epochs)), y=error_valid_list,color="lightgreen")

        plt.legend((p1,p2),["Train error","Validation error"])

        plt.title("Train and Validation Error vs. Epoch",fontdict=font1)
        plt.xlabel("Epoch",fontdict=font2)
        plt.ylabel("Error",fontdict=font2)
        #plt.show()
        plt.savefig(f"plots/plots_{i}.png")

#algorithm1()
#algorithm2()
#algorithm3()


# -*- coding: utf-8 -*-
"""
Created on Fri Jun 30 19:56:13 2023

@author: AMIT
"""
# Smoothening of Images - Apply Blur
import cv2
import numpy as np

img = cv2.imread('E:/OpenCV/child.jpg')
# kernel matrix used for smoothening of images - (3,3),(5,5),(7,7),(9,9)
kernel = np.ones((7,7), np.float32) / 49
print(kernel)

# Filter 2D
smoothed = cv2.filter2D(img, -1, kernel)

# Gaussian Blur
# kernel shape - (3,3),(5,5),(7,7),(9,9)
# sigmaX = std. deviation
g_blur = cv2.GaussianBlur(img, (5,5), 1)

# Median Blur
m_blur = cv2.medianBlur(img, 9)

# Average Blur
avg_blur = cv2.blur(img, (7,7))

cv2.imshow('Image', img)
cv2.imshow('blur', smoothed)
cv2.imshow('g_blur', g_blur)
cv2.imshow('avg_blur', avg_blur)
cv2.imshow('m_blur', m_blur)

# press any to close of frame
cv2.waitKey(0)
# Close all windows
cv2.destroyAllWindows()


#!/usr/bin/env python
'''
WebSpy module

Loads HTML and compares with HTML local dump
'''

import sys
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace

import requests
from bs4 import BeautifulSoup

FREE_SEARCH_FILTER = {"title": "Available in GitLab SaaS Free"}
NONFREE_SEARCH_FILTER = {"title": "Not available in SaaS Free"}

URL = 'https://about.gitlab.com/features/'
BS_PARSER = 'html.parser'

def get_html(url: str) -> str:
    '''Loads html from WEB'''
    response = requests.get(url)
    return response.text

def parse_html(url: str) -> tuple:
    '''Parse HTML and get free and non free elements count'''
    row_html = get_html(url)
    soup = BeautifulSoup(row_html, BS_PARSER)
    free_count = len(soup.find_all("a", FREE_SEARCH_FILTER))
    non_free_count = len(soup.find_all("a", NONFREE_SEARCH_FILTER))

    return free_count, non_free_count

def gitlab_parser_callback(args: Namespace):
    '''Parse GitLab website'''
    free_count, non_free_count = parse_html(args.url)
    print(f"free products: {free_count}", file=sys.stdout)
    print(f"enterprise products: {non_free_count}", file=sys.stdout)

def setup_parser():
    '''Setup parser'''
    parser = ArgumentParser(
        prog='WebSpy',
        description='GitLab web spy',
        formatter_class=ArgumentDefaultsHelpFormatter,
        add_help=True,
        argument_default='-h',
    )
    subparsers = parser.add_subparsers(
        title='commands',
        dest='command',
        required=True,
        description='WebSpy',
    )
    subparsers.required = True
    gitlab_parser = subparsers.add_parser(
        'gitlab',
        help='Get free features list from GitLab website',
        description='WebSpy subparser for GitLab',
    )
    gitlab_parser.add_argument(
        '-u',
        '--url',
        help='Target URL',
        required=False,
        default=URL,
    )
    gitlab_parser.set_defaults(callback=gitlab_parser_callback)

    return parser

def main():
    '''Main function for calling as script'''
    parser = setup_parser()
    args = parser.parse_args()
    args.callback(args)

if __name__ == '__main__':
    main()


import pygame
import random
import sys
import time

Max_X = 1920
Max_Y = 1080
Max_Snow = 100
Snow_Size = 64 # compress image

class Snow():
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.speed = random.randint(1, 3) #random speed 1-3
        self.img_num = random.randint(1, 4) #& files
        self.image_filename = "snow" + str(self.img_num) + ".png" #name files
        self.image = pygame.image.load(self.image_filename).convert_alpha() #not bg, clean image convert.alpha
        self.image = pygame.transform.scale(self.image, (Snow_Size, Snow_Size))

    def move_snow(self): # speed snow
        self.y = self.y + self.speed
        if self.y > Max_Y:
            self.y = (0 - Snow_Size)

        i = random.randint(1, 3)
        if i == 1: #move right
            self.x += 1
            if self.x > Max_X:
                self.x = (0 - Snow_Size)
        elif i == 2: #move left
            self.x -= 1
            if self.x < (0 - Snow_Size):
                self.x = Max_X
    def draw_snow(self):
        screen.blit(self.image, (self.x, self.y))

def initialize_snow(Max_Snow, snowfall):
    for i in range (0, Max_Snow):
        xx = random.randint(0, Max_X)
        yy = random.randint(0, Max_Y)
        snowfall.append(Snow(xx, yy)) #coordinate snow

def check_for_exit(): # exit push button
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            sys.exit()

#--------------------Main------------------

pygame.init()
screen = pygame.display.set_mode((Max_X, Max_Y), pygame.FULLSCREEN) # mode fullscreen

bg_color = (0, 0, 0)
snowfall = [] # storage
initialize_snow(Max_Snow, snowfall) # count snow

while True:
    screen.fill(bg_color) # correct bg
    check_for_exit()
    for i in snowfall:
        i.move_snow()
        i.draw_snow()
    time.sleep(0.0020) #delay
    pygame.display.flip() # draw snowfall


# Kim Lang #33791771
# Eric Chou #95408627
# Jianing Tong #50419612

# CS 121 Project #2
import logging
import re
from urllib.parse import urlparse, urljoin
from corpus import Corpus
import os
from lxml import html
from frontier import Frontier

logger = logging.getLogger(__name__)

class Crawler:
    """
    This class is responsible for scraping urls from the next available link in frontier and adding the scraped links to
    the frontier
    """

    def __init__(self, frontier):
        self.frontier = frontier
        self.corpus = Corpus()
        self.store_dynamicURL = dict()  # Dict of dynamic URLS as key, freq as value
        self.store_URLs = dict()  # Dict of all subdomains visited as key, query as value
        self.outputURL = ("", 0)    #contains the url with the most output links and the count of output links
        self.downloadedURLs = []    #list of downloaded URLs
        self.traps = set()          #set of all traps to prevent showing repeat traps

    def start_crawling(self):
        """
        This method starts the crawling process which is scraping urls from the next available link in frontier and adding
        the scraped links to the frontier
        """
        while self.frontier.has_next_url():
            url = self.frontier.get_next_url()
            self.downloadedURLs.append(url)
            logger.info("Fetching URL %s ... Fetched: %s, Queue size: %s", url, self.frontier.fetched,
                        len(self.frontier))
            url_data = self.fetch_url(url)

            for next_link in self.extract_next_links(url_data):
                if self.corpus.get_file_name(next_link) is not None:
                    if self.is_valid(next_link):                #if the link is valid it stores into the frontier
                        self.frontier.add_url(next_link)
                    elif self.is_valid(next_link) == False:     #if the link is not valid it stores into the trap set()
                        self.traps.add(next_link)               #the way to identify a trap
        for url in self.frontier.urls_queue:                    #loops through the urls in queue
            self.downloadedURLs.append(url)                     #puts the urls that are downloaded into our downloadedURLS list
        self.write_analytics("analytics.txt")                   #writes the analytics to the analytics.txt file

    #writes the analytics required into our analytics.txt file
    def write_analytics(self, filename):
        with open(filename, 'w') as f:                  #opens the file up
            f.write("#1: SUBDOMAINS" + "\n")            #writes all the subdomains from our store_URLs
            for key, value in self.store_URLs.items():  #and writes the number of links from the subdomains
                f.write(key + "\t" + str(len(value)) + '\n')
            f.write("\n" + "#2 URL: " + self.outputURL[0] + " has the most output links of " + str(#writes the url with the most output links and the #
                self.outputURL[1]) + "\n")
            f.write("#3: DOWNLOADED LINKS" + "\n")
            for url in self.downloadedURLs:             #loops through the downloaded links and writes into the file
                f.write(url + "\n")
            f.write("\n" + "TRAPS:" + "\n")
            for trap in self.traps:                     #loops through the traps and writes the traps into the file
                f.write(trap + "\n")
        f.close()

    def fetch_url(self, url):
        """
        This method, using the given url, should find the corresponding file in the corpus and return a dictionary
        containing the url, content of the file in binary format and the content size in bytes
        :param url: the url to be fetched
        :return: a dictionary containing the url, content and the size of the content. If the url does not
        exist in the corpus, a dictionary with content set to None and size set to 0 can be returned.
        """
        #base url data
        url_data = {
            "url": url,
            "content": None,
            "size": 0
        }
        filepath = self.corpus.get_file_name(url)  # gets looks up for a local file and sends the address
        if filepath == None:    # if there is no filepath, return base url_data
            return url_data
        url_data["size"] = os.path.getsize(filepath) # gets the size of file using the filepath
        with open(filepath, "rb") as f:               # opens up the file and reads the file into binary format
            data = f.read()                             # reading
            url_data["content"] = data                 #places all the contents of the file in binary format into the content of the dict
        f.close()                                     #closes the file after done reading
        if "?" in url:                               #checks if the url in the dictionary is a dynamic url
            dynamic_url = url.split("?")[0]          #splits the url and indexing the first part before the ?
            dynamic_freq = self.store_dynamicURL.get(dynamic_url, 0)        #gets the frequency of the current dynamic url
            self.store_dynamicURL[dynamic_url] = dynamic_freq + 1           #adds one to the current frequency of the dynamic_url

        # Keeps track of subdomain visited for Analytics
        url_split = url.split("/")                                          #splits the url
        if url_split[2] in self.store_URLs.keys():                          #url_split[2] == the subdomain part
            self.store_URLs[url_split[2]].add("/".join(url_split[3:]))      # adds the path into the value of store_URLs
        else:
            self.store_URLs[url_split[2]] = set()
            self.store_URLs[url_split[2]].add("/".join(url_split[3:]))

        return url_data                                                   #returns the dict with the url, content in binary form, and size of the file

    def extract_next_links(self, url_data):
        """
        The url_data coming from the fetch_url method will be given as a parameter to this method. url_data contains the
        fetched url, the url content in binary format, and the size of the content in bytes. This method should return a
        list of urls in their absolute form (some links in the content are relative and needs to be converted to the
        absolute form). Validation of links is done later via is_valid method. It is not required to remove duplicates
        that have already been fetched. The frontier takes care of that.

        Suggested library: lxml
        """
        outputLinks = []
        tree = html.fromstring(url_data["content"])             #html.fromstring() expects the content in bytes  tree contains the html file
        url_list = tree.xpath('//a/@href')                      #xpath() scrapes the html file and gives us all the links we want in a list
        base_domain = url_data["url"]                           #the fetched_url
        for sub_url in url_list:                                                #goes through the urllist
            if sub_url.startswith("http://") or sub_url.startswith("https://"): #if the link is an absolute form add into outputlinks
                outputLinks.append(str(sub_url))
            else:                                                               #if not in absolute form or anything else
                joined_url = urljoin(base_domain, sub_url)                      #first join url with the fetched url then add into outputlinks
                outputLinks.append(str(joined_url))                             #joins the query or path

        if self.outputURL[1] < len(url_list):                                   # adds the next link if it has more links than the one before
            self.outputURL = (url_data["url"], len(url_list))

        return outputLinks

    def is_valid(self, url):
        """
        Function returns True or False based on whether the url has to be fetched or not. This is a great place to
        filter out crawler traps. Duplicated urls will be taken care of by frontier. You don't need to check for duplication
        in this method
        """
        parsed = urlparse(url)
        url_breakdown = url.split('/')  # list of url split by '/'

        # Checks for URLs with repetitions in the query and
        # returns false if greater than 2.
        url_dict = dict()
        for word in url_breakdown:
            freq = url_dict.get(word, 0)
            url_dict[word] = freq + 1
        for value in url_dict.values():
            if value > 2:               #if there is more than 2 duplicates in the url, it is considered as a trap
                return False

        # Checks for URLs that are super long
        if len(url_breakdown[-1]) > 150: # checking if the path is extremely long
            return False

        # Checks for dynamic URLs that impede progress of crawler
        dynamic_url = url.split("?")[0]
        if dynamic_url in self.store_dynamicURL.keys() and self.store_dynamicURL[dynamic_url] > 20:  #checks if the dynamic url appears more than 20 times
            return False

        if parsed.scheme not in set(["http", "https"]):
            return False

        try:
            return ".ics.uci.edu" in parsed.hostname \
                   and not re.match(".*\.(css|js|bmp|gif|jpe?g|ico" + "|png|tiff?|mid|mp2|mp3|mp4" \
                                    + "|wav|avi|mov|mpeg|ram|m4v|mkv|ogg|ogv|pdf" \
                                    + "|ps|eps|tex|ppt|pptx|doc|docx|xls|xlsx|names|data|dat|exe|bz2|tar|msi|bin|7z|psd|dmg|iso|epub|dll|cnf|tgz|sha1" \
                                    + "|thmx|mso|arff|rtf|jar|csv" \
                                    + "|rm|smil|wmv|swf|wma|zip|rar|gz|pdf)$", parsed.path.lower())

        except TypeError:
            print("TypeError for ", parsed)
            return False


import gc

import numpy as np
import torch.nn as nn
import torch

class EffectExplainer(nn.Module):
    def __init__(
            self,
            model
    ):
        super(EffectExplainer, self).__init__()
        self.model = model

    def run_explain(self, feat, adj):
        with torch.no_grad():
            self.model.using_attention_explanation()
            _ = self.model(feat, adj)
        edge_weight_matrix = self.model.attention_weight
        return edge_weight_matrix