import pygame
import sys
import random

# Setup
screen_height = 600
screen_width = 800
pygame.init()
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
pygame.display.set_caption("Game")
pygame.display.set_icon(pygame.image.load("Player_Icon.png"))

# Start screen
start_img = [pygame.image.load("Start_Button_0.png"), pygame.image.load("Start_Button_1.png")]
restart_img = [pygame.image.load("Restart_0.png"), pygame.image.load("Restart_1.png")]
over_button = False

# start and end screen
def draw_rect():
    global over_button
    (x, y) = pygame.mouse.get_pos()
    if start is True:
        wdt = 180
        hgt = 72
        x_pos = screen_width / 2 - wdt / 2
        y_pos = screen_height / 2 - hgt / 2
        if x_pos < x < x_pos + wdt and y_pos < y < y_pos + hgt:
            screen.blit(pygame.transform.scale(start_img[0], (wdt, hgt)), (x_pos, y_pos))
            # 30 zu 12
            over_button = True
        else:
            # if not over button
            screen.blit(pygame.transform.scale(start_img[1], (wdt, hgt)), (x_pos, y_pos))
        screen.blit(pygame.transform.scale(pygame.image.load("Controls.png"), (300, 300)),
                    (screen_width / 2 - 100, screen_height / 2))
        screen.blit(pygame.transform.scale(pygame.image.load("Stand_Front.png"), (300, 300)), (250, -25))
    if game_over is True or winner is True:
        wdt = 320
        hgt = 72
        x_pos = screen_width / 2 - wdt / 2
        y_pos = 480
        if game_over:
            screen.blit(pygame.image.load("Game_Over.png"), (screen_width/2 - 188, 50))
            screen.blit(pygame.image.load("Player_Dead.png"), (screen_width / 2 - 128, 120))
        if winner:
            screen.blit(pygame.image.load("Victory.png"), (screen_width / 2 - 144, 50))
            screen.blit(pygame.image.load("Player_Front.png"), (screen_width / 2 - 128, 120))
        if x_pos < x < x_pos + wdt and y_pos < y < y_pos + hgt:
            screen.blit(pygame.transform.scale(restart_img[0], (wdt, hgt)), (x_pos, y_pos))
            # 30 zu 12
            over_button = True
        else:
            # if not over button
            screen.blit(pygame.transform.scale(restart_img[1], (wdt, hgt)), (x_pos, y_pos))

# variables for starting/ending game
winner = False
game_over = False
start = True
game = False

# Variables for Game
display_scroll = [0, 0]
coin_count = [0]
map_size = 500
wall_size = 100
SPEED = 6
x_grid = -1200
y_grid = -1200
# Classes and Functions

# background
def draw_bg():
    global x_grid
    global y_grid
    if not(display_scroll[1] < -map_size or display_scroll[1] > map_size):
        y_grid = display_scroll[1]
    if not(display_scroll[0] < -map_size or display_scroll[0] > map_size):
        x_grid = display_scroll[0]
    screen.blit(pygame.transform.scale(pygame.image.load("Bodenlowqua.png"), (1800, 1600)), (-map_size - x_grid, -map_size - y_grid))

# win condition
def win():
    global winner
    global game
    if player.y <= wall_size - 20 and -map_size - x_grid + 850 <= player.x <= -map_size - x_grid + 925 and pressed[pygame.K_w]:
        if o.coin_count == 9:
            winner = True
            game = False

# collision detection
def collide(x, y, e_x, e_y, wdt, hgt, e_wdt, e_hgt):
    if not (x > e_x + e_wdt or x + wdt < e_x or
            y > e_y + e_hgt or y + hgt < e_y):
        return True
    return False

# hearts, coins and sword uses
class Overlay:
    def __init__(self):
        self.coin_count = 0
        self.herz_count = 0
        self.herz = [pygame.image.load("Herz_0.png"), pygame.image.load("Herz_1.png"), pygame.image.load("Herz_2.png"),
                     pygame.image.load("Herz_3.png"), pygame.image.load("Herz_4.png"), pygame.image.load("Herz_5.png")]
        self.coins = [pygame.image.load("Coin_0.png"), pygame.image.load("Coin_1.png"), pygame.image.load("Coin_2.png"),
                      pygame.image.load("Coin_3.png"), pygame.image.load("Coin_4.png"), pygame.image.load("Coin_5.png"),
                      pygame.image.load("Coin_6.png"), pygame.image.load("Coin_7.png"), pygame.image.load("Coin_8.png"),
                      pygame.image.load("Coin_9.png")]
        self.sword_rez = [pygame.image.load("Swd_Rez_0.png"), pygame.image.load("Swd_Rez_1.png"), pygame.image.load("Swd_Rez_2.png"),
                          pygame.image.load("Swd_Rez_3.png"), pygame.image.load("Swd_Rez_4.png")]

    def update(self):
        if self.herz_count <= 5:
            screen.blit(self.herz[self.herz_count], (screen_width - 120, 0))
            screen.blit(self.coins[self.coin_count], (screen_width - 220, 0))
            if swd.sword_use <= 0:
                screen.blit(self.sword_rez[4], (screen_width - 300, 0))
            elif swd.sword_use <= 15:
                screen.blit(self.sword_rez[3], (screen_width - 300, 0))
            elif swd.sword_use <= 30:
                screen.blit(self.sword_rez[2], (screen_width - 300, 0))
            elif swd.sword_use <= 45:
                screen.blit(self.sword_rez[1], (screen_width - 300, 0))
            elif swd.sword_use <= 60:
                screen.blit(self.sword_rez[0], (screen_width - 300, 0))

class Player:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.animate_down = [pygame.image.load("Player.png"), pygame.image.load("F_Step_1.png"), pygame.image.load("F_Step_2.png"),
                             pygame.image.load("Player.png"), pygame.image.load("F_Step_3.png"), pygame.image.load("F_Step_4.png")]
        self.animate_up = [pygame.image.load("Player_H.png"), pygame.image.load("H_Step_1.png"), pygame.image.load("H_Step_2.png"),
                           pygame.image.load("Player_H.png"), pygame.image.load("H_Step_3.png"), pygame.image.load("H_Step_4.png")]
        self.animate_l = [pygame.image.load("Player_L.png"), pygame.image.load("L_Step_1.png"), pygame.image.load("L_Step_2.png"),
                          pygame.image.load("Player_L.png"), pygame.image.load("L_Step_3.png"), pygame.image.load("L_Step_4.png")]
        self.animation_count = 0
        self.last_input = 0
        self.moving_dir = 0

    def movement(self):
        if pressed[pygame.K_a]:
            self.last_input = 2
            self.moving_dir = 2
            if -map_size <= display_scroll[0] <= map_size:
                display_scroll[0] -= SPEED
            elif display_scroll[0] < -map_size or display_scroll[0] > map_size:
                self.x -= SPEED
                display_scroll[0] -= SPEED
                if self.x < 25:
                    self.x += SPEED
                    display_scroll[0] += SPEED
        if pressed[pygame.K_d]:
            self.last_input = 4
            self.moving_dir = 4
            if -map_size <= display_scroll[0] <= map_size:
                display_scroll[0] += SPEED
            elif display_scroll[0] < -map_size or display_scroll[0] > map_size:
                self.x += SPEED
                display_scroll[0] += SPEED
            if self.x + self.width > screen_width - 25:
                self.x -= SPEED
                display_scroll[0] -= SPEED
        if pressed[pygame.K_w]:
            self.last_input = 1
            self.moving_dir = 1
            if -map_size <= display_scroll[1] <= map_size:
                display_scroll[1] -= SPEED
            elif display_scroll[1] < -map_size or display_scroll[1] > map_size:
                display_scroll[1] -= SPEED
                self.y -= SPEED
            if self.y < wall_size - 20:
                self.y += SPEED
                display_scroll[1] += SPEED
        if pressed[pygame.K_s]:
            self.last_input = 3
            self.moving_dir = 3
            if -map_size <= display_scroll[1] <= map_size:
                display_scroll[1] += SPEED
            elif display_scroll[1] < -map_size or display_scroll[1] > map_size:
                display_scroll[1] += SPEED
                self.y += SPEED
            if self.y + self.height > screen_height - 25:
                display_scroll[1] -= SPEED
                self.y -= SPEED

    def animate(self):
        self.animation_count += 1
        if self.animation_count + 1 > 24:
            self.animation_count = 0
        if self.last_input == 1 and self.moving_dir != 1:
            screen.blit(self.animate_up[0], (self.x, self.y))
        if self.last_input == 1 and self.moving_dir == 1:
            screen.blit(self.animate_up[self.animation_count//4], (self.x, self.y))
        if self.last_input == 2 and self.moving_dir != 2:
            screen.blit(self.animate_l[0], (self.x, self.y))
        if self.last_input == 2 and self.moving_dir == 2:
            screen.blit(self.animate_l[self.animation_count//4], (self.x, self.y))
        if self.last_input == 3 and self.moving_dir != 3:
            screen.blit(self.animate_down[0], (self.x, self.y))
        if self.last_input == 3 and self.moving_dir == 3:
            screen.blit(self.animate_down[self.animation_count//4], (self.x, self.y))
        if self.last_input == 4 and self.moving_dir != 4:
            screen.blit(pygame.transform.flip(self.animate_l[0], True, False), (self.x, self.y))
        if self.last_input == 4 and self.moving_dir == 4:
            screen.blit(pygame.transform.flip(self.animate_l[self.animation_count//4], True, False), (self.x, self.y))
        self.moving_dir = 0
        if self.last_input == 0 and self.moving_dir == 0:
            screen.blit(self.animate_down[0], (self.x, self.y))

    def update(self):
        self.movement()
        self.animate()

class Sword:
    def __init__(self):
        self.sword_rez = 0
        self.sword_use = 60
        self.x = -5000
        self.y = -5000

    def update(self):
        if pressed[pygame.K_SPACE] and self.sword_use > 0:
            if player.last_input == 1:
                screen.blit(pygame.transform.scale(pygame.image.load("Sword_Up.png"), (21, 42)), (player.x, player.y - 42))
                self.x = player.x
                self.y = player.y - 42
            if player.last_input == 2:
                screen.blit(pygame.transform.scale(pygame.image.load("Sword_L.png"), (42, 21)), (player.x - 42, player.y + 22))
                self.x = player.x - 42
                self.y = player.y + 22
            if player.last_input == 3:
                screen.blit(pygame.transform.flip(pygame.transform.scale(pygame.image.load("Sword_Up.png"), (21, 42)), False, True), (player.x + 15, player.y + player.height))
                self.x = player.x + 15
                self.y = player.y + player.height
            if player.last_input == 4:
                screen.blit(pygame.transform.flip(pygame.transform.scale(pygame.image.load("Sword_L.png"), (42, 21)), True, False), (player.x + player.width, player.y + 22))
                self.x = player.x + player.width
                self.y = player.y + 22
            self.sword_use -= 1
        else:
            self.sword_rez += 1
            if self.sword_rez == 120:
                self.sword_use = 60
                self.sword_rez = 0

class Enemy:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.width = 32
        self.height = 42
        self.enemy_input = 0
        self.lives = 7
        self.counter = 0
        self.move = False
        self.left = False

    def update(self):
        if self.lives > 0:
            self.attack()
            self.collision()
            self.animate()

    def attack(self):
        if -map_size <= display_scroll[0] <= map_size:
            if pressed[pygame.K_a]:
                self.x += SPEED
            if pressed[pygame.K_d]:
                self.x -= SPEED
        if -map_size <= display_scroll[1] <= map_size:
            if pressed[pygame.K_w]:
                self.y += SPEED
            if pressed[pygame.K_s]:
                self.y -= SPEED

        offset = [(self.x - player.x), (self.y - player.y)]
        if abs(offset[0]) <= 300 and abs(offset[1]) <= 300:  # inactive enemy at certain offset
            if self.x + self.width < player.x:
                self.x += random.randrange(2, 5)
                self.enemy_input = 4
                self.move = True
                self.left = False
            if self.x > player.x + player.width:
                self.x -= random.randrange(2, 5)
                self.enemy_input = 2
                self.move = True
                self.left = True
            if self.y + self.height < player.y:
                self.y += random.randrange(2, 5)
                self.enemy_input = 3
                self.move = True
            if self.y > player.y + player.height:
                self.y -= random.randrange(2, 5)
                self.enemy_input = 1
                self.move = True

    def collision(self):
        global game_over
        global game
        knockback = 80
        if o.herz_count <= 5:
            # collision player
            if collide(self.x, self.y, player.x, player.y, self.width, self.height, player.width, player.height):
                if self.enemy_input == 1:
                    self.y += knockback
                    o.herz_count += 1
                if self.enemy_input == 2:
                    self.x += knockback
                    o.herz_count += 1
                if self.enemy_input == 3:
                    self.y -= knockback
                    o.herz_count += 1
                if self.enemy_input == 4:
                    self.x -= knockback
                    o.herz_count += 1
                if self.y < wall_size - 20:
                    self.y = wall_size - 20
                if self.y + self.height > screen_height - 25:
                    self.y = screen_height - 25 - self.height
                if self.x < 25:
                    self.x = 25
                if self.x + self.width > screen_width - 25:
                    self.x = screen_width - 25 - self.width
        else:
            game_over = True
            game = False
        if player.last_input == 1 or player.last_input == 3:
            # collision sword
            if collide(self.x, self.y, swd.x, swd.y, self.width, self.height, 21, 42):
                if self.enemy_input == 1:
                    self.y += knockback
                    self.lives -= 1
                if self.enemy_input == 3:
                    self.y -= knockback
                    self.lives -= 1
                if self.y < wall_size - 20:
                    self.y = wall_size - 20
                if self.y + self.height > screen_height - 25:
                    self.y = screen_height - 25 - self.height
            swd.x = -5000
            swd.y = -5000
        if player.last_input == 2 or player.last_input == 4:
            if collide(self.x, self.y, swd.x, swd.y, self.width, self.height, 42, 21):
                if self.enemy_input == 2:
                    self.x += knockback
                    self.lives -= 1
                if self.enemy_input == 4:
                    self.x -= knockback
                    self.lives -= 1
                if self.x < 25:
                    self.x = 25
                if self.x + self.width > screen_width - 25:
                    self.x = screen_width - 25 - self.width
            swd.x = -5000
            swd.y = -5000
        # chest enemy collision does not work yet
        '''if not(self.x > chest.new_x + 88 or self.x + self.width < chest.new_x or self.y > chest.new_y + 64 or self.y + self.height < chest.new_y):
            if self.x + self.width > chest.new_x:
                self.x = chest.new_x - self.width
            if self.y < chest.new_y + 64:
                self.y = chest.new_y + 64
            if self.x > chest.new_x + 88:
                self.x = chest.new_x + 88
            if self.y + self.height > chest.new_y:
                self.y = chest.new_y - self.height'''

    def animate(self):
        self.counter += 1
        if self.move is True:
            if self.left is False:
                if self.counter >= 16:
                    self.counter = 0
                if self.counter <= 3:
                    screen.blit(pygame.image.load("enemy_0.png"), (self.x - 8, self.y + 3))
                elif self.counter <= 7:
                    screen.blit(pygame.image.load("enemy_1.png"), (self.x - 5, self.y))
                elif self.counter <= 11:
                    screen.blit(pygame.image.load("enemy_2 .png"), (self.x - 5, self.y - 6))
                elif self.counter <= 15:
                    screen.blit(pygame.image.load("enemy_1.png"), (self.x - 5, self.y))
                self.move = False
            if self.left is True:
                if self.counter >= 16:
                    self.counter = 0
                if self.counter <= 3:
                    screen.blit(pygame.transform.flip(pygame.image.load("enemy_0.png"), True, False), (self.x - 8, self.y + 3))
                elif self.counter <= 7:
                    screen.blit(pygame.transform.flip(pygame.image.load("enemy_1.png"), True, False), (self.x - 5, self.y))
                elif self.counter <= 11:
                    screen.blit(pygame.transform.flip(pygame.image.load("enemy_2 .png"), True, False), (self.x - 5, self.y - 6))
                elif self.counter <= 16:
                    screen.blit(pygame.transform.flip(pygame.image.load("enemy_1.png"), True, False), (self.x - 5, self.y))
                self.move = False
        else:
            if self.counter >= 36:
                self.counter = 0
            if self.counter <= 8:
                screen.blit(pygame.image.load("enemy_sleep_0.png"), (self.x - 8, self.y + 3))
            elif self.counter <= 16:
                screen.blit(pygame.image.load("enemy_sleep_1.png"), (self.x - 5, self.y))
            elif self.counter <= 24:
                screen.blit(pygame.image.load("enemy_sleep_2.png"), (self.x - 5, self.y - 6))
            elif self.counter <= 35:
                screen.blit(pygame.image.load("enemy_sleep_1.png"), (self.x - 5, self.y))

class Block:  # still buggy
    def __init__(self, x, y):
        self.width = 50
        self.height = self.width
        self.x = x
        self.y = y
        self.new_x = self.x
        self.new_y = self.y
    def main(self):
        if -map_size <= display_scroll[0] <= map_size:
            self.new_x = self.x - display_scroll[0]
        if -map_size <= display_scroll[1] <= map_size:
            self.new_y = self.y - display_scroll[1]
        if collide(self.new_x, self.new_y, player.x, player.y, self.width, self.height, player.width, player.height):
            if pressed[pygame.K_w]:
                player.y += SPEED + 5
                display_scroll[1] += SPEED + 5
            if pressed[pygame.K_a]:
                player.x += SPEED + 5
                display_scroll[0] += SPEED + 5
            if pressed[pygame.K_s]:
                player.y -= SPEED - 5
                display_scroll[1] -= SPEED - 5
            if pressed[pygame.K_d]:
                player.x -= SPEED - 5
                display_scroll[0] -= SPEED - 5

        screen.blit(pygame.transform.scale(pygame.image.load("Stein.png"), (self.width, self.height)), (self.new_x, self.new_y))

class Coins:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.new_x = self.x
        self.new_y = self.y
        self.got_coin = False
        self.collected = False

    def update(self):
        if self.collected is False:
            if -map_size <= display_scroll[0] <= map_size:
                self.new_x = self.x - display_scroll[0]
            if -map_size <= display_scroll[1] <= map_size:
                self.new_y = self.y - display_scroll[1]
            if (self.new_x > player.x + player.width or self.new_x + 28 < player.x or
                self.new_y > player.y + player.height or self.new_y + 28 < player.y) and self.got_coin is False:
                screen.blit(pygame.image.load("Coin.png"), (self.new_x, self.new_y))
            else:
                self.got_coin = True
                if self.collected is False:
                    o.coin_count += 1
                    self.collected = True

class Key:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.got_key = False
        self.new_x = self.x
        self.new_y = self.y

    def update(self):
        if self.got_key is False:
            if -map_size <= display_scroll[0] <= map_size:
                self.new_x = self.x - display_scroll[0]
            if -map_size <= display_scroll[1] <= map_size:
                self.new_y = self.y - display_scroll[1]
            if (self.new_x > player.x + player.width or self.new_x + 24 < player.x or
                self.new_y > player.y + player.height or self.new_y + 28 < player.y) and self.got_key is False:
                screen.blit(pygame.image.load("Key.png"), (self.new_x, self.new_y))
            else:
                self.got_key = True

class Chest:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.new_x = self.x
        self.new_y = self.y
        self.open = [pygame.image.load("Chest_0.png"), pygame.image.load("Chest_1.png")]
        self.open_count = 0
        self.counter = 0
        self.active_count = False
        self.counted = False

    def update(self):
        if -map_size <= display_scroll[0] <= map_size:
            self.new_x = self.x - display_scroll[0]
        if -map_size <= display_scroll[1] <= map_size:
            self.new_y = self.y - display_scroll[1]
        if not(self.new_x > player.x + player.width or self.new_x + 88 < player.x or
                self.new_y > player.y + player.height or self.new_y + 64 < player.y):
            if pressed[pygame.K_w]:
                player.y += SPEED
                display_scroll[1] += SPEED
            if pressed[pygame.K_d]:
                player.x -= SPEED
                display_scroll[0] -= SPEED
            if key.got_key is True and self.counter < 30:
                self.open_count = 1
                self.active_count = True
                if self.counted is False:
                    o.coin_count += 3
                    self.counted = True

        screen.blit(self.open[self.open_count], (self.new_x, self.new_y))
        if self.open_count == 1 and self.active_count is True and self.counter < 30:
            self.counter += 1
            screen.blit(pygame.image.load("Coinbag.png"), (self.new_x + 31, self.new_y - 7))

# Main Loop
while True:
    if game:
        pressed = pygame.key.get_pressed()
        for event in pygame.event.get():
            if event.type == pygame.QUIT or pressed[pygame.K_ESCAPE]:
                sys.exit()
        screen.fill((0, 0, 0))
        draw_bg()
        win()
        # actual program
        # update as a main function of each object/class
        # b.main()
        player.update()
        c1.update()
        c2.update()
        c3.update()
        c4.update()
        c5.update()
        c6.update()
        key.update()
        swd.update()
        e.update()
        swd.update()
        e2.update()
        swd.update()
        e3.update()
        chest.update()
        o.update()

        pygame.display.flip()  # update the image
    # menu screen
    if start is True or game_over is True or winner is True:
        pressed = pygame.key.get_pressed()
        for event in pygame.event.get():
            if event.type == pygame.QUIT or pressed[pygame.K_ESCAPE]:
                sys.exit()
        screen.fill((0, 0, 0))
        pygame.mouse.set_visible(True)
        draw_rect()
        if event.type == pygame.MOUSEBUTTONDOWN and over_button is True:
            # Class objects
            player = Player(400, 320, 32, 48)
            c1 = Coins(62.5, -387.5)
            c2 = Coins(-337.5, -237.5)
            c3 = Coins(412.5, 112.5)
            c4 = Coins(812.5, 812.5)
            c5 = Coins(612.5, -137.5)
            c6 = Coins(212.5, 712.5)
            key = Key(-337.5, 912.5)
            swd = Sword()
            e = Enemy(-37.5, -387.5)
            e2 = Enemy(-287.5, 887.5)
            e3 = Enemy(1175, - 350)
            chest = Chest(1156, -420)
            o = Overlay()
            o.herz_count = 0
            o.coin_count = 0
            display_scroll = [0, 0]
            # b = Block(250, 100)
            start = False
            game_over = False
            winner = False
            game = True
            pygame.mouse.set_visible(False)
    # end
    pygame.display.update()
    clock.tick(30)


import random
import cv2
import cvzone
import time
from cvzone.HandTrackingModule import HandDetector

cap = cv2.VideoCapture(0)

cap.set(3, 1080)
cap.set(4, 720)

detector=HandDetector(maxHands=1)

timer=0
stateResult=False
startGame=False
scores =[0,0]

while True:
    bg_main = cv2.imread('F:/rps/New folder/BG.png')

    success, img = cap.read()

    imgScaled = cv2.resize(img,(0,0),None,0.7222, 0.7222)
    imgScaled = imgScaled[:, 3:515]
    imgScaled= cv2.resize(imgScaled,(418,388))

    #finds Hands
    hands, img = detector.findHands(imgScaled)
    if startGame:
        if stateResult is False:
            timer = time.time()- initialTime
            cv2.putText(bg_main, str(int(timer)),(605,435),cv2.FONT_HERSHEY_PLAIN ,6,(255,0,255),4)

            if timer>3:
                stateResult = True
                timer= 0

                if hands:
                    playermove = None
                    hand = hands[0]
                    fingers = detector.fingersUp(hand)
                    if fingers == [0,0,0,0,0]:
                        playermove =1
                    if fingers == [1,1,1,1,1]:
                        playermove = 2
                    if fingers ==[0,1,1,0,0]:
                        playermove = 3

                    randomNumber = random.randint(1,3)
                    imgai= cv2.imread(f"F:/rps/New folder/data/{randomNumber}.png",cv2.IMREAD_UNCHANGED)
                    bg_main = cvzone.overlayPNG(bg_main, imgai ,(235,280))

                    # player Win
                    if (playermove == 1 and randomNumber == 3) or\
                            (playermove == 2 and randomNumber == 1) or\
                            (playermove == 3 and randomNumber == 2):
                        scores[1] +=1

                    # ai Win
                    if (playermove == 3 and randomNumber == 1) or\
                            (playermove == 1 and randomNumber == 2) or\
                            (playermove == 2 and randomNumber == 3):
                        scores[0] +=1

    bg_main[114:502,756:1174]= imgScaled
    print(imgScaled.shape)
    print(bg_main[114:502, 756:1174].shape)
    if stateResult:

        bg_main = cvzone.overlayPNG(bg_main, imgai ,(235,280))

        cv2.putText(bg_main, str(scores[0]),(410,215),cv2.FONT_HERSHEY_PLAIN ,4,(255,255,255),6)
        cv2.putText(bg_main, str(scores[1]),(1112,215),cv2.FONT_HERSHEY_PLAIN ,4,(255,255,255),6)

    #cv2.imshow("image" , img)
    cv2.imshow("BG" ,bg_main)

    #cv2.imshow("scaled", imgScaled)
    key=cv2.waitKey(1)
    if key == ord('s'):
        startGame = True
        initialTime = time.time()
        stateResult = False


'''
Given a string s, reverse the string according to the following rules:

All the characters that are not English letters remain in the same position.
All the English letters (lowercase or uppercase) should be reversed.
Return s after reversing it.
'''

#Solution:
class Solution(object):
    def reverseOnlyLetters(self, s):
        """
        :type s: str
        :rtype: str
        """
        s=str(s)
        new_s=''
        li = s.split('-')
        for x in li:
            new_s += x
        new_s = new_s[::-1]
        return new_s


import os
import sys
import time
import argparse

import math
import shutil

import random
import statistics
import math
from math import sqrt

#import pandas as pd
import numpy as np
import array
import csv
import array as arr
#import matplotlib.pyplot as plt
#import seaborn as seabornInstance

'''
this code is for finding the potential for saving energy- let's say we define 3 distances 2,4,6 meter and for each distance we find the lowest quality for each obj having maximum deg constraint
then, we find the sum of all tris and use the gpu utilization model by a= 4.1786369783353056e-05
b=44.25345492837267

gives two files, one quality potential and 2, gpu, tris and distance inf in gpu_result.txt

blender -b -P test.py
'''

def QualitySelection_reverse(ind,d11): # this is to find candidate qualities based on factor * max_deg
# factors are 0.2 . 0.4 and 0.6

    i=ind
    gamma=float(gamm[i])
    a= float(alpha[i])
    b=float(beta[i])
    c1=float(c[i])

    c1=c1-((d11** gamma)* max_deg[i] * 0.2) # ax2+bx+c= (d^gamma) * max_deg

    inp1=[1.0,1.0,1.0]
    inp2=[1.0,1.0,1.0]

    #print("first equation")
    r1,r2=delta(a,b,c1,c[i],d11,gamma,max_deg[i] * 0.2 )
    r1= float(round(r1,2))
    r2=float(round(r2,2))

    #inp1.append(r1)
   # inp2.append(r2)
    inp1[0]=r1
    inp2[0]=r2
    #print("second equation")

    #need fixing !!!
    sel_inp=0 # selects amon inp1 or inp2
    final_inp = array.array('f')
    final_inp=[1.0,1.0,1.0]

    for i in range (0,1):

        if(inp1[i]==0 and inp2[i]==0):
            final_inp[i]=1.0

        elif( inp2[i]==0 or inp2[i]==1)  :
            final_inp[i]=inp1[i]
        else:
            if( inp1[i]==0 or inp1[i]==1):
              final_inp[i]=inp2[i]
            else:
                 final_inp[i]=min (inp2[i], inp1[i])

    return final_inp[0]

def delta(a,b,c,c_real,d,gm, max_d):
  #("Quadratic function : (a * x^2) + b*x + c=0")
  r=0.0
  r = (b**2 - 4*a*c)

  if r > 0:
    num_roots = 2
    x1 = (((-b) + sqrt(r))/(2*a))
    x2 = (((-b) - sqrt(r))/(2*a))
    #print("There are 2 roots: %f and %f" % (x1, x2))
    if(0.1<x1<1.0 and 0.1<x2<1.0):
      return x1,x2
    if (0.1<x1<1) :
      x=checkerror(a, b, c_real, d, gm, max_d)
      #x=1
      return x1,x
    if (0.1<x2<1) :
      x=checkerror(a, b, c_real, d, gm, max_d)
      #x=1
      return x,x2
    else:
      x=checkerror(a, b, c_real, d, gm, max_d)
      #x=1
      return x,x

  elif r == 0:
    num_roots = 1
    x = (-b) / 2*a
    #print("There is one root: ", x)
    return x,0
  else:
    num_roots = 0
    #print("No roots, discriminant < 0.")
    x=checkerror(a, b, c_real, d, gm, max_d)
    #x=1
    return x,x
    #0 is delta1 here

def checkerror(a,b,creal,d,gamma, max_d):

    r1=0.1
    error=max_d
    for i in range (1,18):

     error = ((a* (r1**2)) + b*r1 + creal) / (d**gamma)
     if(error<max_d):
       return r1
     r1+=0.05
     #r2=0.8
     #error2 = ((a* (r2**2)) + b*r2 + creal) / (d**gamma)

    return 0

alpha=[]
beta=[]
#min_dis=[] # minimum distance between user and each obj -> let it be similar for all objects
c=[]
gamm=[]
max_deg=[]
tris=[]
o_name=[]
mindis=[]
filesize=[]

#dataset = pd.read_csv("degmodel_file.csv")

with open('degmodel_file.csv') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')

    line_count = 0

    for row in csv_reader:
      temp1=str(row)
      temp= (temp1.split(","))

      if(temp[0][2:-1]!= "alpha"):
       alpha.append(float(temp[0][2:-1])) # remove extra unused signs as [ and '
       beta.append(float(temp[1][2:-1]))
       c.append(float(temp[2][2:-1]))
       gamm.append(float(temp[3][2:-1]))
       max_deg.append(float(temp[4][2:-1]))
       tris.append(float(temp[5][2:-1]))
       o_name.append(temp[6][2:-1])
       mindis.append(float(temp[7][2:-1]))
       filesize.append(temp[8][2:-1])

i=0

obj_count=len(alpha)

distance = [2,4,6,8]

objects_quality=[]
total_tris1=[0,0,0,0] # in three distances (2,4,6,8) before optimization

total_tris2=[0,0,0,0] # in three distances (2,4,6,8) after optimization

with open('qualities_reversal_potential.txt', mode='w',newline='') as qual:

 for i in range(0, obj_count):

  name=str(o_name[i])

  qual.write(str(name)+ "\n")

  temp=[]
  j=0
  factor=[]
  for elm in distance:
   temp.append(QualitySelection_reverse(i, elm))
   temp3=(temp[j])
   factor.append(elm)

#The scenario has 3count of these objects plus a bike and plane---> total-onj *3 + tris of the plane and bike of the objects

   if(name=="plant" or name=="bike" or name=="table" or name=="hammer"  or name== "plane" or name=="BigCabin" ):

     total_tris2[j]+= 3*(tris[i] * temp3)
     total_tris1[j]+= 3* (tris[i] * 1)

     if(name=='bike'):
        total_tris2[j]+= (tris[i] * temp3)
        total_tris1[j]+= (tris[i] * 1)
     elif(name=='plane'):
        total_tris2[j]+= (tris[i] * temp3)
        total_tris1[j]+= (tris[i] * 1)

   qual.write(str(elm)+" " + str(temp3)+"\n")
   j+=1

print(total_tris2)
print(total_tris1)

a_gpu= 4.1786369783353056e-05
b_gpu=44.25345492837267
gpu1=[0,0,0,0]
gpu2=[0,0,0,0]

print(gpu1)
print(gpu2)

with open('gpu_result.txt', mode='w',newline='') as qual:
  for i in range(0,4):

    gpu1[i] = (a_gpu* total_tris1[i] ) + b_gpu
    gpu2[i] = (a_gpu* total_tris2[i] ) + b_gpu

  for i in range(0,4):
    qual.write(str(distance[i])+" " + str(total_tris1[i])+" " + str(gpu1[i])+ "\n")

  qual.write("\n")
  qual.write("\n")

  for i in range(0,4):
    qual.write(str(distance[i])+" " + str(total_tris2[i])+" "+ str(gpu2[i])+"\n")


# https://justkode.kr/algorithm/python-dijkstra

'''
1. 알고리즘 진행 순서
    1) 출발 노드와, 도착 노드를 설정
    2) 알고 있는 모든 거리 값을 부여
    3) 출발 노드부터 시작하여, 방문하지 않은 인접 노드를 방문, 거리를 계산한 다음, 현재 알고있는 거리보다 짧으면 해당 값으로 갱신한다.
    4) 현재 노드에 인접한 모든 미방문 노드까지의 거리를 계산했다면, 현재 노드는 방문한 것이므로, 미방문 집합에서 제거한다.
    5) 도착 노드가 미방문 노드 집합에서 벗어나면, 알고리즘을 종료한다.
'''

graph = {
    'A': {'B': 8, 'C': 1, 'D': 2},
    'B': {},
    'C': {'B': 5, 'D': 2},
    'D': {'E': 3, 'F': 5},
    'E': {'F': 1},
    'F': {'A': 5}
}

# 그래프 : 본인 노드 이름 : {연결된 노드 이름 : 연결된 노드까지의 거리}
# distances : 시작 노드에서 특정 노드까지 거리
# start : 시작 노드
# heap : 다음 특정 노드 저장
# distance : 특정 노드까지 최소 거리
# current distance : 특정 노드까지 거리
# current destination : 특정 노드 노드명

import heapq  # 우선순위 큐 구현을 위함

def dijkstra(graph, start):
    # 초기값
    distances = {node: float('inf') for node in graph} # [시작 노드에서 특정 노드까지 거리] : 무한대
    distances[start] = 0 # [시작 노드에서 시작 노드까지 거리] : 0
    # print(distances) # 확인용 코드
    queue = [] # [정보 저장 heap] 생성
    heapq.heappush(queue, [distances[start], start])  # [[시작 노드에서 시작 노드까지 거리], [시작 노드명]]를 [정보 저장 heap]에 저장
    # print(distances[start], start) # 확인용 코드

    while queue:  # heap에서 노드가 빌 때까지 반복
        current_distance, current_destination = heapq.heappop(queue) # [정보 저장 heap]에서 가장 작은 [[시작 노드에서 특정 노드까지 거리], [특정 노드명]] 빼기
        # print(current_distance, current_destination)
        if distances[current_destination] < current_distance: # 가장 작은 [시작 노드에서 특정 노드까지 거리]가 [기존 시작 노드에서 특정 노드까지 거리]보다 크면 건너띔
            print(distances[current_destination], current_distance)
            continue

        for new_destination, new_distance in graph[current_destination].items(): # [특정 노드]에 연결된 [[특정 노드에서 다음 특정 노드까지 거리], [다음 특정 노드명]] 전부 돌기
            distance = current_distance + new_distance # [시작 노드에서 다음 특정 노드까지의 새로운 거리] = [정보 저장 heap]에서 가장 작은 [[시작 노드에서 특정 노드까지 거리], [특정 노드명]] + 특정 노드명에 연결된 [[특정 노드에서 다음 특정 노드까지 거리], [다음 특정 노드명]]
            if distance < distances[new_destination]: # [시작 노드에서 다음 특정 노드까지 거리] > [시작 노드에서 다음 특정 노드까지의 새로운 거리]라면
                distances[new_destination] = distance # [시작 노드에서 다음 특정 노드까지 거리] 최신화
                heapq.heappush(queue, [distance, new_destination]) # [[시작 노드에서 다음 특정 노드까지 거리], [다음 특정 노드명]]을 [정보 저장 heap]에 저장

    return distances

print(dijkstra(graph, 'A'))


import sys
from letter_map import recognize_letter

# Print debug messages
DEBUG = False

def debug(*args, **kwargs):
    if DEBUG:
        print(*args, file=sys.stderr, **kwargs)

INST_NOOP = 'noop'
INST_ADDX = 'addx'

CY_STEP = 40
CY_START = 20
CY_CNT = 6
CY_STOP = CY_START + (CY_CNT - 1) * CY_STEP

CRT_LINES = 6
CRT_COLUMNS = 40

# Using unicode blocks for better visibility
# https://en.wikipedia.org/wiki/Block_Elements
PIX_MODE = 2
if PIX_MODE == 1:
    PIX_LIT = '█'
    PIX_DARK = ' '
elif PIX_MODE == 2:
    PIX_LIT = '▓'
    PIX_DARK = '░'
else:
    PIX_LIT = '#'
    PIX_DARK = '.'

def main():
    cycle = 0
    inst_type = INST_NOOP
    inst_value = 0
    inst_wait = 0
    reg_x = 1
    score_sum = 0

    crt_image = []

    # with open('input_test.txt') as file_input:
    # with open('input_test_MB.txt') as file_input:
    with open('input_day_10.txt') as file_input:
        while cycle < CRT_LINES * CRT_COLUMNS:  # There should be 6 lines with 40 pixels per line - then this may stop
            current_cycle = cycle + 1
            debug('Running cycle: ' + str(current_cycle))
            if inst_wait <= 0:
                # Complete the instruction
                debug('\tInstruction complete.')
                if inst_type == INST_ADDX:
                    reg_x += inst_value
                    debug('\tAdded ' + str(inst_value) + ' - new reg value is ' + str(reg_x))

            # BUGFIX: Following code has to run in each cycle UNCONDITIONALLY

            # At this point the value for REG X is valid for this cycle
            debug('--- REG X value: ' + str(reg_x))

            if current_cycle in range(CY_START, CY_STOP + 1, CY_STEP):
                score = current_cycle * reg_x
                debug('  - Cycle ' + str(current_cycle) +
                      ' | REG X value: ' + str(reg_x) +
                      ' | Score: ' + str(score) + ' ---------------------------------------------')
                score_sum += score

            # BUGFIX: But the next part needs the condition again

            # Draw the image
            crt_line = cycle // 40
            crt_column = cycle % 40
            # Create new image line if needed
            if crt_column == 0:
                debug('' if len(crt_image) == 0 else crt_image[crt_line - 1])  # Debug print previous line
                crt_image.append([])
            # If sprite (REG X value +- 1) under the pixel (current cycle % 40)
            # Draw lit pixel
            if reg_x - 1 <= crt_column <= reg_x + 1:
                crt_image[crt_line].append(PIX_LIT)
            else:
                crt_image[crt_line].append(PIX_DARK)

            if inst_wait <= 0:
                # Load new instruction
                debug('\tLoading next instruction.')
                line = file_input.readline()
                if line == '':
                    # Break the loop at the end of input
                    break
                line = line.strip()  # Strip needed - this is probably due to input using CR/LF
                if line == INST_NOOP:
                    inst_type = INST_NOOP
                    inst_wait = 1
                    debug('\tRunning NOOP - finish in 1 cycle')
                else:
                    inst_type = INST_ADDX
                    inst_value = int(line.split(maxsplit=1)[1])
                    inst_wait = 2
                    debug('\tRunning ADDX ' + str(inst_value) + ' - finish in 2 cycles')

            # Complete the cycle
            cycle += 1
            inst_wait -= 1
        print('Signal strength sum: ' + str(score_sum))
        for line in crt_image:
            print(''.join(line))

        # BONUS
        # It seems that letters are 4 pix wide and 6 pix tall
        # (that makes sense, as there should be 8 letters on 40*6 grid - leaving 5 pix width per letter and space)
        crt_string = ''
        for letter_idx in range(8):
            offset = 5 * letter_idx
            letter = [line[0+offset:4+offset] for line in crt_image]
            for line in letter:
                debug(''.join(line))
            char = recognize_letter(letter, PIX_LIT, PIX_DARK)
            debug(char)
            debug('')
            crt_string += char
        print('CRT shows following text: ' + crt_string)

if __name__ == '__main__':
    main()


"""empty message

Revision ID: cadce5b06ea7
Revises: 5c4a7cc7d0e2
Create Date: 2020-03-25 11:23:09.141719

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = 'cadce5b06ea7'
down_revision = '5c4a7cc7d0e2'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index('ix_user_email', table_name='user')
    op.drop_index('ix_user_username', table_name='user')
    op.drop_table('user')
    op.drop_table('followers')
    op.drop_index('ix_post_timestamp', table_name='post')
    op.drop_table('post')
    op.add_column('label_data', sa.Column('is_labeled', sa.Integer(), nullable=True))
    op.create_index(op.f('ix_label_data_is_labeled'), 'label_data', ['is_labeled'], unique=False)
    op.drop_index('ix_label_data_is_labled', table_name='label_data')
    op.drop_column('label_data', 'is_labled')
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('label_data', sa.Column('is_labled', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
    op.create_index('ix_label_data_is_labled', 'label_data', ['is_labled'], unique=False)
    op.drop_index(op.f('ix_label_data_is_labeled'), table_name='label_data')
    op.drop_column('label_data', 'is_labeled')
    op.create_table('post',
    sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
    sa.Column('body', mysql.VARCHAR(length=140), nullable=True),
    sa.Column('timestamp', mysql.DATETIME(), nullable=True),
    sa.Column('user_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.Column('language', mysql.VARCHAR(length=5), nullable=True),
    sa.ForeignKeyConstraint(['user_id'], ['user.id'], name='post_ibfk_1'),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index('ix_post_timestamp', 'post', ['timestamp'], unique=False)
    op.create_table('followers',
    sa.Column('follower_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.Column('followed_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.ForeignKeyConstraint(['followed_id'], ['user.id'], name='followers_ibfk_1'),
    sa.ForeignKeyConstraint(['follower_id'], ['user.id'], name='followers_ibfk_2'),
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('user',
    sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=64), nullable=True),
    sa.Column('email', mysql.VARCHAR(length=120), nullable=True),
    sa.Column('password_hash', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('about_me', mysql.VARCHAR(length=140), nullable=True),
    sa.Column('last_seen', mysql.DATETIME(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index('ix_user_username', 'user', ['username'], unique=True)
    op.create_index('ix_user_email', 'user', ['email'], unique=True)
    # ### end Alembic commands ###


#!/usr/bin/python3
"""
 script that takes your GitHub credentials
"""

import requests
from sys import argv

def getgithub():
    url = "https://api.github.com/user"
    response = requests.get(url, auth=(argv[1], argv[2]))
    response = response.json()
    print(response.get("id"))

if __name__ == "__main__":
    getgithub()


import uuid
import datetime
import json

from oscal_pydantic import (
    catalog,
    assessment_plan,
    assessment_results,
    complete,
    component,
    poam,
    profile,
    ssp,
)

cg_uuid=uuid.uuid5(uuid.NAMESPACE_DNS, 'cloud.gov')

ssp_au_01 = ssp.SpecificControlStatement(
    statement_id="au_01_smt",
    uuid = str(uuid.uuid5(cg_uuid, 'ssp_au_01')),
    remarks = "This is a remark"
)

print(ssp_au_01.json())


"""
# PYES - PYthon Event Scheduling simulation library

Easy to use Python simulation library based on event scheduling strategy.

Author: Marko S. Djogatovic

Licence: MIT

## Installation
python setup.py install

## Requirements
1. Python >= 3.6

Utilities
"""
import time, random, statistics, bisect, math, itertools, sys
from collections import Counter
from collections.abc import Iterable
from datetime import timedelta
from pyes.base import clock, time_unit, start_time

import importlib
mpl_spec = importlib.util.find_spec("matplotlib")
mpl_found = mpl_spec is not None

if mpl_found:
  from matplotlib import pyplot as plt
else:
  print("Warning: matplotlib not found",file=sys.stderr)

class Rand(random.Random):
  def __init__(self,seed=None):
    super().__init__(seed)
    pass

  def exponential(self,mu):
    return self.expovariate(1.0/mu)

  def normal(self,mu,sigma):
    return self.gauss(mu,sigma)

  def lognorm(self,mu,sigma):
    return self.lognormvariate(mu,sigma)

  def gamma(self,alpha,beta):
    return self.gammavariate(alpha,beta)

  def beta(self,alpha,beta):
    return self.betavariate(alpha,beta)

  def erlang(self,k,mu):
    return self.gammavariate(k,mu)

  def pareto(self,alpha):
    return self.paretovariate(alpha)

  def weibull(self,alpha,beta):
    return self.weibullvariate(alpha,beta)

  def loglogistic(self,alpha,beta):
    assert alpha>0 and beta>0, "alpha and beta must be larger then 0!"
    r = self.random()
    return alpha*math.exp(math.log(r/(1-r))/beta)

  def rand(self):
    return self.random()

  def __call__(self):
    return self.random()

  def continuous(self,prob,vals):
    assert any(p2 - p1 <= 0 for p1, p2 in zip(prob[:-1], prob[1:])),"Probaility must be in strictly ascending order!"
    assert len(prob)!=len(vals), "Length of probabilities must be equal to length of values!"
    assert prob[0]!=0.0 or prob[-1]!=1.0, "First probability must be 0.0 and last probability must be 1.0!"
    r = self.random()
    i = bisect.bisect_left(prob,r)-1
    return vals[i]+(vals[i+1]-vals[i])/(prob[i+1]-prob[i])*(r-prob[i])

  def discrete(self,prob,vals):
    assert len(prob)!=len(vals), "Length of probabilities must be equal to length of values!"
    prob = list(itertools.accumulate(prob))
    assert prob[-1]!=1.0, "Sum of probabilities must be equal to 1.0!"
    return vals[bisect.bisect_left(prob,self.random())]

rn1  = Rand(3)
rn2  = Rand(5)
rn3  = Rand(9)
rn4  = Rand(11)
rn5  = Rand(9)
rn6  = Rand(11)
rn7  = Rand(13)
rn8  = Rand(15)
rn9  = Rand(17)
rn10 = Rand(19)
rn11 = Rand(21)
rn12 = Rand(23)
rn13 = Rand(25)
rn14 = Rand(27)
rn15 = Rand(29)

microsecond = timedelta(microseconds=1.0)
milisecond  = timedelta(milliseconds=1.0)
second      = timedelta(seconds=1.0)
minute      = timedelta(minutes=1.0)
hour        = timedelta(hours=1.0)
day         = timedelta(days=1.0)
week        = timedelta(weeks=1.0)

## Class timer
class timer:
  """Timer class"""
  def __init__(self, name=None, disp=False):
    """Timer class c-tor"""
    self.__name = name
    self.__disp = disp
    self.__elapsed = 0.0
    self.__tstart = 0.0

  def __enter__(self):
    self.tic()

  def __exit__(self, type, value, traceback):
    self.toc()
    if self.__disp:
      print(self)

  def tic(self):
    self.__tstart = time.time()

  def toc(self):
    self.__elapsed = time.time() - self.__tstart

  def __str__(self):
    s = ''
    if self.__name:
      s += '[{0}]: '.format(self.__name)
    s += 'Elapsed time: {0:.4f} sec'.format(self.__elapsed)
    return s

def timeit(fun):
    def wrapper(*args,**kwargs):
      with timer(disp=True):
        result = fun(*args,**kwargs)
      return result
    return wrapper

## Class Histogram
class histogram:
  def __init__(self,bn=10):
    self.__values    = []
    self.__bins      = bn
    self.__freq      = None
    self.__bin_edges = None
    pass

  def add(self, v):
    if not (isinstance(v,int) or isinstance(v,float) or isinstance(v,timedelta)):
      raise TypeError("int, float or timedelta is expected")
    if isinstance(v,timedelta):
      self.__values.append(v/time_unit())
    else:
      self.__values.append(v)

  def __call__(self):
    minv = min(self.__values)
    maxv = max(self.__values)
    bw = (maxv-minv)/self.__bins
    self.__bin_edges = [i*bw for i in range(self.__bins+1)]
    cnt = Counter([bisect.bisect_left(self.__bin_edges,v)-1 for v in self.__values])
    self.__freq = (len(self.__bin_edges)-1)*[0]
    for k,v in cnt.items():
      if k>0:
        self.__freq[k] = v
      else:
        self.__freq[0] += v
    return self.__freq,self.__bin_edges

  @property
  def freqs(self):
    self.__call__()
    return self.__freq

  @property
  def bin_edges(self):
    self.__call__()
    return self.__bin_edges

  @property
  def values(self):
    return self.__values

  def mean(self):
    return statistics.mean(self.__values)

  def std(self):
    return statistics.stdev(self.__values)

  def argsum(self):
    return math.fsum(self.__values)

  def plot(self):
    self.__call__()
    if mpl_found:
      plt.figure()
      plt.step(self.__bin_edges, [*self.__freq, 0.0], linestyle="None", where='post')
      plt.fill_between(self.__bin_edges, [*self.__freq, 0.0], step='post')
      plt.xlabel('Bins')
      plt.ylabel('Frequencies')
      _,t = plt.ylim()
      plt.ylim([0.0,t])
      plt.show()

  def __str__(self):
    self.__call__()
    s = ""
    s += "Entries: {}\n".format(len(self.__values))
    s += "Mean argument: {:.3f}\n".format(self.mean())
    s += "Standard deviation: {:.3f}\n".format(self.std())
    s += "Sum of arguments: {:.3f}\n".format(self.argsum())
    bin_center = [(l+h)/2.0 for l,h in zip(self.__bin_edges[:-1],self.__bin_edges[1:])]
    for i,be in enumerate(bin_center):
      s += "Bin center[{}]: {:.3f}\n".format(i,be)
    for i,f in enumerate(self.__freq):
      if i<len(self.__freq)-1:
        s += "Bin freq[{}]: {}\n".format(i,f)
      else:
        s += "Bin freq[{}]: {}".format(i,f)
    return s

class timeseries:
  def __init__(self):
    self.__times = []
    self.__values = []

  def add(self,v):
    self.__times.append((clock()-start_time())/time_unit())
    self.__values.append(v)

  @property
  def values(self):
    return self.__values

  @property
  def times(self):
    return self.__times

  def plot(self):
    if mpl_found:
      plt.figure()
      if isinstance(self.__values[0],Iterable):
        vals = zip(*self.__values)
        for i,v in enumerate(vals):
          plt.step(self.times, v, where='pre', label='TS {}'.format(i+1))
        plt.legend()
      else:
        plt.step(self.times, self.__values, where='pre')
      plt.title('Time-series')
      plt.xlabel('Time (t.u.)')
      plt.ylabel('Values')
      plt.show()


import time
import math
import torch
from torch import nn, Tensor
from torch.optim import Adam
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
from transformers import logging
from utils.utils import AverageMeter
from .adaptformer import AdaptFormer
from torch.nn.parallel import DistributedDataParallel as DDP

class AdaptFormerm(nn.Module):
    def __init__(self, num_frames, num_classes, global_pool=True):
        super().__init__()
        self.backbone = AdaptFormer(all_frames=num_frames, vpt_on=True, name_ckpt='pretrain_vit_base_1600.pth')
        self.layer_norm = nn.LayerNorm(self.backbone.num_features)
        self.classif = nn.Linear(in_features=self.backbone.num_features, out_features=num_classes)
        self.global_pool = global_pool

    def forward(self, images):
        x = self.backbone(images)
        if self.global_pool:
            x = x.mean(1)
        else:
            x = x[:, 0]
        x = self.layer_norm(x)
        out = self.classif(x)
        return out

class AdaptFormermExecutor:
    def __init__(self, train_loader, test_loader, criterion, eval_metric, class_list, test_every, distributed, gpu_id) -> None:
        super().__init__()
        self.train_loader = train_loader
        self.test_loader = test_loader
        self.criterion = criterion.to(gpu_id)
        self.eval_metric = eval_metric.to(gpu_id)
        self.class_list = class_list
        self.test_every = test_every
        self.distributed = distributed
        self.gpu_id = gpu_id
        num_frames = self.train_loader.dataset[0][0].shape[0]
        num_classes = len(class_list)
        logging.set_verbosity_error()
        model = AdaptFormerm(num_frames, num_classes).to(gpu_id)
        if distributed:
            self.model = DDP(model, device_ids=[gpu_id])
        else:
            self.model = model
        # train everything other than self.model.backbone
        for name, p in self.model.named_parameters():
            if not name.startswith('backbone'):
                p.requires_grad = True
        # within the backbone only train the missing keys in self.model.backbone
        for name, p in self.model.backbone.named_parameters():
            if name in self.model.backbone.msg.missing_keys:
                p.requires_grad = True
            else:
                p.requires_grad = False
        self.optimizer = self._construct_optimizer()
        self.scheduler = CosineAnnealingWarmRestarts(self.optimizer, T_0=10, eta_min=1e-8)

    def _construct_optimizer(self):
        optim_params = []
        for p in self.model.parameters():
            if p.requires_grad:
                optim_params.append(p)
        return Adam(optim_params, lr=0.01, weight_decay=0.)

    def _train_batch(self, data, label):
        self.optimizer.zero_grad()
        output = self.model(data)
        loss_this = self.criterion(output, label)
        loss_this.backward()
        self.optimizer.step()
        return loss_this.item()

    def _train_epoch(self, epoch):
        self.model.train()
        loss_meter = AverageMeter()
        start_time = time.time()
        for data, label in self.train_loader:
            data, label = data.to(self.gpu_id, non_blocking=True), label.to(self.gpu_id, non_blocking=True)
            loss_this = self._train_batch(data, label)
            loss_meter.update(loss_this, data.shape[0])
        elapsed_time = time.time() - start_time
        if (self.distributed and self.gpu_id == 0) or not self.distributed:
            print("Epoch [" + str(epoch + 1) + "]"
                  + "[" + str(time.strftime("%H:%M:%S", time.gmtime(elapsed_time))) + "]"
                  + " loss: " + "{:.4f}".format(loss_meter.avg), flush=True)

    def train(self, start_epoch, end_epoch):
        for epoch in range(start_epoch, end_epoch):
            self._train_epoch(epoch)
            self.scheduler.step()
            if (epoch + 1) % self.test_every == 0:
                eval = self.test()
                if (self.distributed and self.gpu_id == 0) or not self.distributed:
                    print("[INFO] Evaluation Metric: {:.2f}".format(eval * 100), flush=True)

    def test(self):
        self.model.eval()
        eval_meter = AverageMeter()
        for data, label in self.test_loader:
            data, label = data.to(self.gpu_id), label.long().to(self.gpu_id)
            with torch.no_grad():
                output = self.model(data)
            eval_this = self.eval_metric(output, label)
            eval_meter.update(eval_this.item(), data.shape[0])
        return eval_meter.avg

    def save(self, file_path="./checkpoint.pth"):
        backbone_state_dict = self.model.backbone.state_dict()
        optimizer_state_dict = self.optimizer.state_dict()
        torch.save({"backbone": backbone_state_dict,
                    "optimizer": optimizer_state_dict},
                    file_path)

    def load(self, file_path):
        checkpoint = torch.load(file_path)
        self.model.backbone.load_state_dict(checkpoint["backbone"])
        self.optimizer.load_state_dict(checkpoint["optimizer"])

class PositionalEncoding(nn.Module):
    """
    This is a more standard version of the position embedding, very similar to the one
    used by the Attention is all you need paper, generalized to work on images.
    """
    def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 2000):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)
        position = torch.arange(max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
        pe = torch.zeros(1, max_len, d_model)
        pe[0, :, 0::2] = torch.sin(position * div_term)
        pe[0, :, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe, persistent=False)

    def forward(self, x: Tensor) -> Tensor:
        """
        Args:
            x: Tensor, shape [batch_size, seq_len, embedding_dim]
        """
        x = x + self.pe[:, :x.size(1)]
        out = self.dropout(x)
        return out

class GroupWiseLinear(nn.Module):
    # could be changed to:
    # output = torch.einsum('ijk,zjk->ij', x, self.W)
    # or output = torch.einsum('ijk,jk->ij', x, self.W[0])
    def __init__(self, num_class, hidden_dim, bias=True):
        super().__init__()
        self.num_class = num_class
        self.hidden_dim = hidden_dim
        self.bias = bias

        self.W = nn.Parameter(torch.Tensor(1, num_class, hidden_dim))
        if bias:
            self.b = nn.Parameter(torch.Tensor(1, num_class))
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.W.size(2))
        for i in range(self.num_class):
            self.W[0][i].data.uniform_(-stdv, stdv)
        if self.bias:
            for i in range(self.num_class):
                self.b[0][i].data.uniform_(-stdv, stdv)

    def forward(self, x):
        # x: B,K,d
        x = (self.W * x).sum(-1)
        if self.bias:
            x = x + self.b
        return x


class Trie(object):
    class Node:
        def __init__(self):
            self.keys = dict()
            self.end = False

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = self.Node()

    def insert(self, word):
        """
        Inserts a word into the trie.
        :type word: str
        :rtype: None
        """
        node = self.root
        for i in range(len(word)):
            if word[i] not in node.keys:
                node.keys[word[i]] = self.Node()
            node = node.keys[word[i]]
            if i == len(word)-1:
                node.end = True

    def search(self, word):
        """
        Returns if the word is in the trie.
        :type word: str
        :rtype: bool
        """
        node = self.root
        for i in range(len(word)):
            if word[i] in node.keys:
                node = node.keys[word[i]]
            else:
                return False
            if i == len(word)-1 and node.end:
                return True
        return False

    def startsWith(self, prefix):
        """
        Returns if there is any word in the trie that starts with the given prefix.
        :type prefix: str
        :rtype: bool
        """
        node = self.root
        for i in range(len(prefix)):
            if prefix[i] in node.keys:
                node = node.keys[prefix[i]]
            else:
                return False
        return True

# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)