import time
import pygame

import physicsEngine
import gameHandler

from render import WIDTH
from render import HEIGHT

import functools
from classes import *
from constants import *

pygame.init()

regularFont = pygame.font.Font("./assets/fonts/mem8YaGs126MiZpBA-UFVZ0b.ttf",20)
semiboldFont = pygame.font.Font("./assets/fonts/mem5YaGs126MiZpBA-UNirkOUuhp.ttf",20)
boldFont = pygame.font.Font("./assets/fonts/mem5YaGs126MiZpBA-UN7rgOUuhp.ttf",20)

lightFont = pygame.font.Font("./assets/fonts/mem8YaGs126MiZpBA-UFVZ0b.ttf",18)
buttonFont = pygame.font.Font("./assets/fonts/mem5YaGs126MiZpBA-UN7rgOUuhp.ttf",18)
bigFont = pygame.font.Font("./assets/fonts/mem5YaGs126MiZpBA-UN7rgOUuhp.ttf",22)
gigaFont = pygame.font.Font("./assets/fonts/mem5YaGs126MiZpBA-UN7rgOUuhp.ttf",100)

buttonList = []
playerBarList = []
dropdownList = []

selectedPlayerBar = None

isTyping = False
textBox = None
textBoxUnicode = True
typedObject = None
typedObjectAttribute = None
typedObjectUpdate = None
typeOnce = False
typeRawInput = False
typeResult = None
typedInO = None
typedInTB = None

isDragging = False
dragPointX = 0
dragPointY = 0

isDropdownListActive = False
dropdownListBox = None
dropdownSelectedItem = None
dropdownListX = 0
dropdownListY = 0
dropdownListW = 0
dropdownListH = 0
overDropdownList = False

redTeamBox = None
spectatorTeamBox = None
blueTeamBox = None

gameSection = 0

#these 2 functions comes from a reply by @unutbu on https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects
def rsetattr(obj, attr, val):
    pre, _, post = attr.rpartition('.')
    return setattr(rgetattr(obj, pre) if pre else obj, post, val)

def rgetattr(obj, attr, *args):
    def _getattr(obj, attr):
        return getattr(obj, attr, *args)
    return functools.reduce(_getattr, [obj] + attr.split('.'))

#BUTTON FUNCTIONS

def goToSection( button, argument):
    global gameSection
    gameSection = argument

def goToExit( button, argument):
    gameHandler.saveRecord()
    gameHandler.run = False

def addPlayer( button, argument):
    gameHandler.joinedSound.play()
    Player("Player " + str(gameHandler.redPlayersCount+gameHandler.bluePlayersCount+gameHandler.spectatorsCount + 1), "NONE", NO_KEYS,)

def doNothing( button, argument):
    pass

def changeNick( button, argument):
    global isTyping
    global textBox
    global textBoxUnicode
    global typedObject
    global typedObjectAttribute
    global typedObjectUpdate
    global typeResult
    global typeOnce
    global typeRawInput
    global selectedPlayerBar

    isTyping = True
    textBox = button
    textBoxUnicode = True
    typedObject = selectedPlayerBar
    typedObjectAttribute = "player.nick"
    typedObjectUpdate = selectedPlayerBar.updateName
    typeResult = selectedPlayerBar.player.nick
    typeOnce = False
    typeRawInput = False

def goToPlayerControls( button, argument):
    global gameSection
    global selectedPlayerBar
    gameSection = 5

    global upKeyBox
    global downKeyBox
    global leftKeyBox
    global rightKeyBox
    global kickKeyBox

    upKeyBox.string = pygame.key.name( selectedPlayerBar.player.keyUp)
    downKeyBox.string = pygame.key.name( selectedPlayerBar.player.keyDown)
    leftKeyBox.string = pygame.key.name( selectedPlayerBar.player.keyLeft)
    rightKeyBox.string = pygame.key.name( selectedPlayerBar.player.keyRight)
    kickKeyBox.string = pygame.key.name( selectedPlayerBar.player.keyKick)

    upKeyBox.update()
    downKeyBox.update()
    leftKeyBox.update()
    rightKeyBox.update()
    kickKeyBox.update()

def changePlayerControls( button, argument):
    global isTyping
    global textBox
    global textBoxUnicode
    global typedObject
    global typedObjectAttribute
    global typedObjectUpdate
    global typeResult
    global typeOnce
    global typeRawInput
    global selectedPlayerBar

    isTyping = True
    textBox = button
    textBoxUnicode = False
    typedObject = selectedPlayerBar
    typedObjectUpdate = selectedPlayerBar.updateName #does it work?

    typeOnce = True
    typeRawInput = True

    global upKeyBox
    global downKeyBox
    global leftKeyBox
    global rightKeyBox
    global kickKeyBox

    if argument == 0:
        textBox = upKeyBox
        typedObjectAttribute = "player.keyUp"
    elif argument == 1:
        textBox = downKeyBox
        typedObjectAttribute = "player.keyDown"
    elif argument == 2:
        textBox = leftKeyBox
        typedObjectAttribute = "player.keyLeft"
    elif argument == 3:
        textBox = rightKeyBox
        typedObjectAttribute = "player.keyRight"
    elif argument == 4:
        textBox = kickKeyBox
        typedObjectAttribute = "player.keyKick"

def deletePlayer( button, argument):
    gameHandler.leftSound.play()
    global selectedPlayerBar
    if selectedPlayerBar.player.team == "BLUE":
        gameHandler.bluePlayersCount -= 1
    elif selectedPlayerBar.player.team == "RED":
        gameHandler.redPlayersCount -= 1
    else:
        gameHandler.spectatorsCount -= 1

    for bar in playerBarList:
        if bar.player.team == selectedPlayerBar.player.team:
            if bar.pos > selectedPlayerBar.pos:
                bar.pos -= 1
                bar.updateCoordinates()
                bar.updateName()
    physicsEngine.playerList.remove( selectedPlayerBar.player)
    playerBarList.remove( selectedPlayerBar)

    goToSection( button, 1)

def startGame( button, argument):
    global gameSection

    if not gameHandler.started:
        gameSection = 2

        gameHandler.startNewMatch()
        button.color = RED_BUTTON
        button.colorOver = RED_OVER
        button.colorPressed = RED_PRESSED
        button.string = "Stop game"
        button.update()
    else:
        gameHandler.started = False

        button.color = GREEN_BUTTON
        button.colorOver = GREEN_OVER
        button.colorPressed = GREEN_PRESSED
        button.string = "Start game"
        button.update()

def switchReplay( button, argument):
    if gameHandler.replaysTurnedOn:
        button.string = "Replays OFF"
        gameHandler.replaysTurnedOn = False
    else:
        button.string = "Replays ON"
        gameHandler.replaysTurnedOn = True
    button.update()

def dropdownTimeLimit( button, argument):
    if not gameHandler.started:
        global dropdownList
        global isDropdownListActive
        global dropdownListBox
        global dropdownSelectedItem

        button.string = str(gameHandler.timeLimit)
        button.update()

        dropdownList.clear()
        isDropdownListActive = True
        dropdownListBox = button

        for i in range (argument):
            DropdownItem( button.x, button.y + (i+1)*button.h, button.w, button.h, str(i), button.font, BOX_DARKGRAY, DROPDOWN_BLUE, setTimeLimit, i)
        dropdownSelectedItem = dropdownList[ gameHandler.timeLimit]

def dropdownScoreLimit( button, argument):
    if not gameHandler.started:
        global dropdownList
        global isDropdownListActive
        global dropdownListBox
        global dropdownSelectedItem

        button.string = str(gameHandler.scoreLimit)
        button.update()

        dropdownList.clear()
        isDropdownListActive = True
        dropdownListBox = button

        for i in range (argument):
            DropdownItem( button.x, button.y + (i+1)*button.h, button.w, button.h, str(i), button.font, BOX_DARKGRAY, DROPDOWN_BLUE, setScoreLimit, i)
        dropdownSelectedItem = dropdownList[ gameHandler.scoreLimit]

def dropdownStadiums( button, argument):
    if not gameHandler.started:
        global dropdownList
        global isDropdownListActive
        global dropdownListBox
        global dropdownSelectedItem

        button.string = str(gameHandler.stadium)
        button.update()

        dropdownList.clear()
        isDropdownListActive = True
        dropdownListBox = button

        for i, stadium in enumerate(gameHandler.stadiums()):
            if stadium == gameHandler.stadium:
                dropdownSelectedItem = DropdownItem( button.x, button.y + (i+1)*button.h, button.w, button.h, stadium, button.font, BOX_DARKGRAY, DROPDOWN_BLUE, setStadium, stadium)
            else:
                DropdownItem( button.x, button.y + (i+1)*button.h, button.w, button.h, stadium, button.font, BOX_DARKGRAY, DROPDOWN_BLUE, setStadium, stadium)

#DROPDOWN LIST FUNCTIONS

def setTimeLimit( argument):
    gameHandler.timeLimit = argument

def setScoreLimit( argument):
    gameHandler.scoreLimit = argument

def setStadium( argument):
    gameHandler.stadium = argument
    gameHandler.loadStadium( argument)

############################################################################################################################################################################################################################################
#MENU FUNCTIONS

def init():

    #MAIN MENU
    Button( 0, WIDTH/2 - 150, HEIGHT/2 - 200,300,400, "", False, buttonFont, WHITE, (26,33,37), (26,33,37), (26,33,37), doNothing, None)
    Button( 0, WIDTH/2 + 50, HEIGHT/2 - 235,0,0, "CLONE", False, bigFont, WHITE, BACKGROUND_GREEN, BACKGROUND_GREEN, BACKGROUND_GREEN, doNothing, None)
    Button( 0, WIDTH/2 - 60, HEIGHT/2 - 150,120,60, "Play", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), goToSection, 1)
    Button( 0, WIDTH/2 - 60, HEIGHT/2 - 50,120,60, "Settings", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), goToSection, 3)
    Button( 0, WIDTH/2 - 60, HEIGHT/2 + 50,120,60, "Exit", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), goToExit, None)

    #LOBBY
    global redTeamBox
    global spectatorTeamBox
    global blueTeamBox
    global startButton

    Button( 1, WIDTH/2 - 450, HEIGHT/2 - 275,900,550, "", False, buttonFont, WHITE, (26,33,37), (26,33,37), (26,33,37), doNothing, None)

    Button( 1, WIDTH/2 - 230 - 60, HEIGHT/2 - 210,120,25, "Red", False, buttonFont, LABEL_RED, GRAY_BUTTON, GRAY_BUTTON, GRAY_BUTTON, doNothing, None)
    Button( 1, WIDTH/2 - 60, HEIGHT/2 - 210,120,25, "Spectators", False, buttonFont, WHITE, GRAY_BUTTON, GRAY_BUTTON, GRAY_BUTTON, doNothing, None)
    Button( 1, WIDTH/2 + 170, HEIGHT/2 - 210,120,25, "Blue", False, buttonFont, LABEL_BLUE, GRAY_BUTTON, GRAY_BUTTON, GRAY_BUTTON, doNothing, None)
    redTeamBox = Button( 1, WIDTH/2 - 340, HEIGHT/2 - 180,220,290, "", False, buttonFont, WHITE, (17,22,25), (17,22,25), (17,22,25), doNothing, None)
    spectatorTeamBox = Button( 1, WIDTH/2 - 110, HEIGHT/2 - 180,220,290, "", False, buttonFont, WHITE, (17,22,25), (17,22,25), (17,22,25), doNothing, None)
    blueTeamBox = Button( 1, WIDTH/2 + 120, HEIGHT/2 - 180,220,290, "", False, buttonFont, WHITE, (17,22,25), (17,22,25), (17,22,25), doNothing, None)

    Button( 1, WIDTH/2 - 445, HEIGHT/2 - 140,100,25, "Add Player", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_OVER, BLUE_PRESSED, addPlayer, None)
    Button( 1, WIDTH/2 + 340, HEIGHT/2 - 265,100,25, "Leave", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_OVER, BLUE_PRESSED, goToSection, 0)

    Button( 1, WIDTH/2 - 190, HEIGHT/2 + 125,150,20, "Time limit", True, lightFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    Button( 1, WIDTH/2 - 190, HEIGHT/2 + 150,150,20, "Score limit", True, lightFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    Button( 1, WIDTH/2 - 190, HEIGHT/2 + 175,150,20, "Stadium", True, lightFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    startButton = Button( 1, WIDTH/2 - 65, HEIGHT/2 + 220,130,25, "Start game", False, buttonFont, WHITE, (58,153,51), (70,184,61), (46,122,41), startGame, None)

    timeLimitBox = Button( 1, WIDTH/2 - 75, HEIGHT/2 + 125,150,20, str(gameHandler.timeLimit), True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, dropdownTimeLimit, 6)
    scoreLimitBox = Button( 1, WIDTH/2 - 75, HEIGHT/2 + 150,150,20, str(gameHandler.scoreLimit), True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, dropdownScoreLimit, 6)
    stadiumBox = Button( 1, WIDTH/2 - 75, HEIGHT/2 + 175,150,20, str(gameHandler.stadium), True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, dropdownStadiums, None)

    #GAME
    global scoreBar
    global timeBar
    global overtimeSprite
    global upperShadow
    global lowerShadow
    global upperInfo
    global lowerInfo
    global pauseBar

    Button( 2, WIDTH/2 - 220,0,440,40, "", False, buttonFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    scoreBar = Button( 2, WIDTH/2 - 180,0,50,40, "Score", False, bigFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    timeBar = Button( 2, WIDTH/2 + 160,0,50,40, "Time", False, bigFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    overtimeSprite = Button( 2, WIDTH/2 + 35,0,50,40, "", False, bigFont, WHITE, MENU_GRAY, MENU_GRAY, MENU_GRAY, doNothing, None)
    upperShadow = Button( 2, WIDTH/2 + 5, HEIGHT/2 - 50 + 5,0,0, "", False, gigaFont, BLACK, BACKGROUND_GREEN, BACKGROUND_GREEN, BACKGROUND_GREEN, doNothing, None)
    lowerShadow = Button( 2, WIDTH/2 + 5, HEIGHT/2 + 50 + 5,0,0, "", False, gigaFont, BLACK, BACKGROUND_GREEN, BACKGROUND_GREEN, BACKGROUND_GREEN, doNothing, None)
    upperInfo = Button( 2, WIDTH/2, HEIGHT/2 - 50,0,0, "", False, gigaFont, WHITE, BACKGROUND_GREEN, BACKGROUND_GREEN, BACKGROUND_GREEN, doNothing, None)
    lowerInfo = Button( 2, WIDTH/2, HEIGHT/2 + 50,0,0, "", False, gigaFont, WHITE, BACKGROUND_GREEN, BACKGROUND_GREEN, BACKGROUND_GREEN, doNothing, None)
    pauseBar = Button( 2, WIDTH/2, HEIGHT/2 + 100,0,10, "", False, buttonFont, WHITE, WHITE, WHITE, WHITE, doNothing, None)

    Button( 2, WIDTH/2 - 215,10,25,25, "", False, buttonFont, WHITE, PLAYER_RED, PLAYER_RED, PLAYER_RED, doNothing, None)
    Button( 2, WIDTH/2 - 120,10,25,25, "", False, buttonFont, WHITE, PLAYER_BLUE, PLAYER_BLUE, PLAYER_BLUE, doNothing, None)

    #SETTINGS
    Button( 3, WIDTH/2 - 150, HEIGHT/2 - 200,300,400, "", False, buttonFont, WHITE, (26,33,37), (26,33,37), (26,33,37), doNothing, None)
    Button( 3, WIDTH/2 - 60, HEIGHT/2 - 150,120,60, "Replays ON", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), switchReplay, None)
    Button( 3, WIDTH/2 - 60, HEIGHT/2 - 50,120,60, "Close", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), goToSection, 0)

    #PLAYER SETTINGS
    global nickBox

    Button( 4, WIDTH/2 - 150, HEIGHT/2 - 275,300,550, "", False, buttonFont, WHITE, (26,33,37), (26,33,37), (26,33,37), doNothing, None)
    Button( 4, WIDTH/2 - 110, HEIGHT/2 - 250,50,60, "Nick:", False, buttonFont, WHITE, (36,73,103), (36,73,103), (36,73,103), doNothing, None)
    nickBox = Button( 4, WIDTH/2 - 60, HEIGHT/2 - 250,180,60, "", True, lightFont, WHITE, (17,22,25), (17,22,25), (17,22,25), changeNick, None)

    Button( 4, WIDTH/2 - 110, HEIGHT/2 - 170,220,60, "Change controls", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), goToPlayerControls, None)
    #Button( 4, WIDTH/2 - 110, HEIGHT/2 - 90,220,60, "Change avatar", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), goToSection, 1)
    Button( 4, WIDTH/2 - 110, HEIGHT/2 - 90,220,60, "Close", False, buttonFont, WHITE,(36,73,103), (47,94,133), (25,52,73), goToSection, 1)
    Button( 4, WIDTH/2 - 110, HEIGHT/2 - 10,220,60, "Delete player", False, buttonFont, WHITE, (36,73,103), (47,94,133), (25,52,73), deletePlayer, None)

    #CONTROLS
    global upKeyBox
    global downKeyBox
    global leftKeyBox
    global rightKeyBox
    global kickKeyBox

    Button( 5, WIDTH/2 - 150, HEIGHT/2 - 275,300,550, "", False, buttonFont, WHITE, (26,33,37), (26,33,37), (26,33,37), doNothing, None)
    Button( 5, WIDTH/2 - 110, HEIGHT/2 - 250,80,60, "Up", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_BUTTON, BLUE_BUTTON, doNothing, None)
    Button( 5, WIDTH/2 - 110, HEIGHT/2 - 170,80,60, "Down", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_BUTTON, BLUE_BUTTON, doNothing, None)
    Button( 5, WIDTH/2 - 110, HEIGHT/2 - 90,80,60, "Left", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_BUTTON, BLUE_BUTTON, doNothing, None)
    Button( 5, WIDTH/2 - 110, HEIGHT/2 - 10,80,60, "Right", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_BUTTON, BLUE_BUTTON, doNothing, None)
    Button( 5, WIDTH/2 - 110, HEIGHT/2 + 70,80,60, "Kick", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_BUTTON, BLUE_BUTTON, doNothing, None)
    Button( 5, WIDTH/2 - 110, HEIGHT/2 + 150,220,60, "Close", False, buttonFont, WHITE, BLUE_BUTTON, BLUE_OVER, BLUE_PRESSED, goToSection, 4)

    upKeyBox = Button( 5, WIDTH/2 - 30, HEIGHT/2 - 250,160,60, "", True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, changePlayerControls, 0)
    downKeyBox = Button( 5, WIDTH/2 - 30, HEIGHT/2 - 170,160,60, "", True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, changePlayerControls, 1)
    leftKeyBox = Button( 5, WIDTH/2 - 30, HEIGHT/2 - 90,160,60, "", True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, changePlayerControls, 2)
    rightKeyBox = Button( 5, WIDTH/2 - 30, HEIGHT/2 - 10,160,60, "", True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, changePlayerControls, 3)
    kickKeyBox = Button( 5, WIDTH/2 - 30, HEIGHT/2 + 70,160,60, "", True, lightFont, WHITE, BOX_DARKGRAY, BOX_DARKGRAY, BOX_DARKGRAY, changePlayerControls, 4)

def update(previousKeys, currentKeys, mouseWasPressed, mousePressed, events):
    global isDragging
    global dragPointX
    global dragPointY

    global isTyping
    global textBox
    global textBoxUnicode
    global typedObject
    global typedObjectAttribute
    global typedObjectUpdate
    global typeResult
    global typeOnce
    global typeRawInput
    global selectedPlayerBar
    global typedInO
    global typedInTB

    global isDropdownListActive
    global dropdownListBox
    global dropdownSelectedItem

    global redTeamBox
    global blueTeamBox
    global spectatorTeamBox

    mousePos = pygame.mouse.get_pos()

    #FROM GAME TO MENU
    if gameSection == 2 and currentKeys[pygame.K_ESCAPE] and not previousKeys[pygame.K_ESCAPE]:
        goToSection( None, 1)
        gameHandler.pauseMatch()
    elif gameSection == 1 and currentKeys[pygame.K_ESCAPE] and not previousKeys[pygame.K_ESCAPE] and gameHandler.started:
        goToSection( None, 2)
        gameHandler.resumeMatch()

    if gameSection == 2 and currentKeys[pygame.K_p] and not previousKeys[pygame.K_p] and not gameHandler.paused:
        gameHandler.pauseMatch()
    elif gameSection == 2 and currentKeys[pygame.K_p] and not previousKeys[pygame.K_p]:
        gameHandler.resumeMatch()

    #MENU LOGIC
    if isTyping:
        if currentKeys[pygame.K_ESCAPE] or currentKeys[pygame.K_RETURN] or mousePressed[0] and ( textBox.x > mousePos[0] or  mousePos[0] > textBox.x + textBox.w or textBox.y > mousePos[1] or mousePos[1] > textBox.y + textBox.h ):
            isTyping = False

            rsetattr( typedObject, typedObjectAttribute, typeResult)
            typedObjectUpdate()

        else:
            for event in events:
                if event.type == pygame.KEYDOWN:
                    if typeRawInput:
                        typedInO = event.key
                    else:
                        typedInO = event.unicode

                    if textBoxUnicode:
                        typedInTB = event.unicode
                    else:
                        typedInTB = pygame.key.name(event.key)

                    if typeOnce:
                        textBox.string = typedInTB
                        typeResult = typedInO
                        rsetattr( typedObject, typedObjectAttribute, typeResult)
                        typedObjectUpdate()
                        isTyping = False
                    else:
                        if event.key == 8: #backspace
                            textBox.string = textBox.string[0:-1]
                            typeResult = typeResult[0:-1]
                        else:
                            textBox.string += typedInTB
                            typeResult += typedInO
                    textBox.update()

    for button in buttonList:
        button.isOver = False

        if button.section == gameSection and not isDropdownListActive :
            if button.x <= mousePos[0] and  mousePos[0] <= button.x + button.w:
                if button.y <= mousePos[1] and  mousePos[1] <= button.y + button.h:
                    button.isOver = True

            if mouseWasPressed[0] and not mousePressed[0]:
                if button.isOver and button.isPressed:
                    button.f( button, button.argument)
                button.isPressed = False

            elif not mouseWasPressed[0] and mousePressed[0] and button.isOver:
                button.isPressed = True

    if isDropdownListActive and gameSection == 1:
        overDropdownList = False

        for item in dropdownList:
            if item.x <= mousePos[0] and  mousePos[0] <= item.x + item.w:
                if item.y <= mousePos[1] and  mousePos[1] <= item.y + item.h:
                    dropdownSelectedItem = item
                    overDropdownList = True

                    if mouseWasPressed[0] and not mousePressed[0]:
                        item.f( item.argument)
                        dropdownListBox.string = item.string
                        dropdownListBox.update()
                        isDropdownListActive = False

        if mousePressed[0] and not overDropdownList:
            isDropdownListActive = False

    if isDragging:
        if not mousePressed[0]:
            isDragging = False

            if redTeamBox.isOver or blueTeamBox.isOver or spectatorTeamBox.isOver:
                if selectedPlayerBar.player.team == "RED":
                    gameHandler.redPlayersCount -= 1
                elif selectedPlayerBar.player.team == "NONE":
                    gameHandler.spectatorsCount -= 1
                else:
                    gameHandler.bluePlayersCount -= 1

                for bar in playerBarList:
                    if bar.player.team == selectedPlayerBar.player.team:
                        if bar.pos > selectedPlayerBar.pos:
                            bar.pos -= 1
                            bar.updateCoordinates()
                            bar.updateName()

                if redTeamBox.isOver:
                    gameHandler.redPlayersCount += 1
                    if selectedPlayerBar.player.team != "RED":
                        selectedPlayerBar.player.team = "RED"
                        gameHandler.putPlayerOnPitch( selectedPlayerBar.player)
                    selectedPlayerBar.pos = gameHandler.redPlayersCount
                elif spectatorTeamBox.isOver:
                    gameHandler.spectatorsCount += 1
                    if selectedPlayerBar.player.team != "NONE":
                        selectedPlayerBar.player.team = "NONE"
                        gameHandler.putPlayerOnPitch( selectedPlayerBar.player)
                    selectedPlayerBar.pos = gameHandler.spectatorsCount
                elif blueTeamBox.isOver:
                    gameHandler.bluePlayersCount += 1
                    if selectedPlayerBar.player.team != "BLUE":
                        selectedPlayerBar.player.team = "BLUE"
                        gameHandler.putPlayerOnPitch( selectedPlayerBar.player)
                    selectedPlayerBar.pos = gameHandler.bluePlayersCount

            selectedPlayerBar.updateCoordinates()
            selectedPlayerBar.updateName()

        else:
            selectedPlayerBar.x = mousePos[0] - dragPointX
            selectedPlayerBar.y = mousePos[1] - dragPointY
            selectedPlayerBar.updateName()

    for bar in playerBarList:
        bar.isOver = False
        if bar.x <= mousePos[0] and  mousePos[0] <= bar.x + bar.w and not isDragging:
            if bar.y <= mousePos[1] and  mousePos[1] <= bar.y + bar.h:
                bar.isOver = True
        if bar == selectedPlayerBar and isDragging:
            bar.isOver = True

        if gameSection == 1 and bar.isOver and mousePressed[0] and not mouseWasPressed[0]:
            isDragging = True
            selectedPlayerBar = bar
            playerBarList[ playerBarList.index( bar) ], playerBarList[ len(playerBarList)-1]  = playerBarList[ len(playerBarList)-1], playerBarList[ playerBarList.index( bar) ]
            #swaping dragged player bar with the last in the list so it won't be overlapped
            dragPointX = mousePos[0] - bar.x
            dragPointY = mousePos[1] - bar.y

        if bar.isOver and mousePressed[2] and not mouseWasPressed[2]:
            bar.openOptions()


import numpy as np
from pprint import pprint

attribute = {1: 'Credit History', 2: 'Wealth', 3: 'Assets'}
def entropy(x):
    res = 0
    val, counts = np.unique (x, return_counts=True)
    freqs = counts.astype ('float') / len (x)
    for p in freqs:
        if p != 0.0:
            res -= p * np.log2 (p)
    return res

def Entropy_gain(y, x):
    res = entropy (y)
    val, counts = np.unique (x, return_counts=True)
    freqs = counts.astype ('float') / len (x)

    for p, v in zip (freqs, val):
        res -= p * entropy (y[x == v])
    return res

def divide(a):
    return {b: (a == b).nonzero ()[0] for b in np.unique (a)}

def split(x, y):
    if len (set (y)) == 1 or len (y) == 0:
        return y
    gain = np.array ([Entropy_gain (y, x_attr) for x_attr in x.T])
    selected_attr = np.argmax (gain)
    if np.all (gain < 1e-6):
        return y

    sets = divide (x[:, selected_attr])
    res = {}
    for k, v in sets.items ():
        y_subset = y.take (v, axis=0)
        x_subset = x.take (v, axis=0)
        res["%s = %d" % (attribute[selected_attr + 1], k)] = \
            split (x_subset, y_subset)

    return res

x1 = [0, 0, 1, 1, 2, 2]
x2 = [0, 1, 1, 0, 0, 1]
x3 = [1, 0, 0, 0, 1, 1]
y = np.array ([0, 0, 1, 0, 1, 1])

X = np.array ([x1, x2, x3]).T
pprint(split (X, y))


# Python program to demonstrate Range Update
# and Range Queries using BIT

# Returns sum of arr[0..index]. This function assumes
# that the array is preprocessed and partial sums of
# array elements are stored in BITree[]
def getSum(BITree: list, index: int) -> int:
    summ = 0 # Initialize result

    # index in BITree[] is 1 more than the index in arr[]
    index = index + 1

    # Traverse ancestors of BITree[index]
    while index > 0:

        # Add current element of BITree to sum
        summ += BITree[index]

        # Move index to parent node in getSum View
        index -= index & (-index)
    return summ

# Updates a node in Binary Index Tree (BITree) at given
# index in BITree. The given value 'val' is added to
# BITree[i] and all of its ancestors in tree.
def updateBit(BITTree: list, n: int, index: int, val: int) -> None:

    # index in BITree[] is 1 more than the index in arr[]
    index = index + 1

    # Traverse all ancestors and add 'val'
    while index <= n:

        # Add 'val' to current node of BI Tree
        BITTree[index] += val

        # Update index to that of parent in update View
        index += index & (-index)

# Returns the sum of array from [0, x]
def summation(x: int, BITTree1: list, BITTree2: list) -> int:
    return (getSum(BITTree1, x) * x) - getSum(BITTree2, x)

def updateRange(BITTree1: list, BITTree2: list, n: int, val: int, l: int,
                r: int) -> None:

    # Update Both the Binary Index Trees
    # As discussed in the article

    # Update BIT1
    updateBit(BITTree1, n, l, val)
    updateBit(BITTree1, n, r + 1, -val)

    # Update BIT2
    updateBit(BITTree2, n, l, val * (l - 1))
    updateBit(BITTree2, n, r + 1, -val * r)

def rangeSum(l: int, r: int, BITTree1: list, BITTree2: list) -> int:

    # Find sum from [0,r] then subtract sum
    # from [0,l-1] in order to find sum from
    # [l,r]
    return summation(r, BITTree1, BITTree2) - summation(
        l - 1, BITTree1, BITTree2)

# Driver Code
if __name__ == "__main__":
    n = 5

    # BIT1 to get element at any index
    # in the array
    BITTree1 = [0] * (n + 1)

    # BIT 2 maintains the extra term
    # which needs to be subtracted
    BITTree2 = [0] * (n + 1)

    # Add 5 to all the elements from [0,4]
    l = 0
    r = 4
    val = 5
    updateRange(BITTree1, BITTree2, n, val, l, r)

    # Add 2 to all the elements from [2,4]
    l = 2
    r = 4
    val = 10
    updateRange(BITTree1, BITTree2, n, val, l, r)
    l =1
    r = 1
    val=-10
    updateRange(BITTree1, BITTree2, n, val, l, r)
    # Find sum of all the elements from
    # [1,4]
    l = 1
    r = 4
    print("Sum of elements from [%d,%d] is %d" %
        (l, r, rangeSum(l, r, BITTree1, BITTree2)))

class BIT:
    def __init__(self, n):
        self._n = n
        self.data = [0] * n

    def add(self, p, x):
        assert 0 <= p < self._n
        p += 1
        while p <= self._n:
            self.data[p - 1] += x
            p += p & -p

    def sumrange(self, l, r):
        assert 0 <= l <= r <= self._n
        return self._sum(r) - self._sum(l)

    def _sum(self, r):
        s = 0
        while r > 0:
            s += self.data[r - 1]
            r -= r & -r
        return s

class BinaryTrie:
    """
    Reference:
     - https://atcoder.jp/contests/arc028/submissions/19916627
     - https://judge.yosupo.jp/submission/35057
    """

    def __init__(self, max_log: int = 60, allow_multiple_elements: bool = True, add_query_limit: int = 10 ** 6):
        self.max_log = max_log
        self.x_end = (1 << max_log)
        self.v_list = [0] * (max_log + 1)
        self.multiset = allow_multiple_elements
        self.add_query_count = 0
        self.add_query_limit = add_query_limit
        n = max_log * add_query_limit + 1
        self.edges = [-1] * (2 * n)
        self.size = [0] * n
        self.is_end = [0] * n
        self.max_v = 0
        self.lazy = 0

    def xor_all(self, x: int):
        # assert 0 <= x < self.x_end
        self.lazy ^= x

    def __ixor__(self, x: int):
        self.xor_all(x)
        return self

    def add(self, x: int):
        # assert 0 <= x < self.x_end
        # assert 0 <= self.add_query_count < self.add_query_limit
        x ^= self.lazy
        v = 0
        for i in reversed(range(self.max_log)):
            d = (x >> i) % 2
            if self.edges[2 * v + d] == -1:
                self.max_v += 1
                self.edges[2 * v + d] = self.max_v
            v = self.edges[2 * v + d]
            self.v_list[i] = v
        if self.multiset or self.is_end[v] == 0:
            self.is_end[v] += 1
            for v in self.v_list:
                self.size[v] += 1
        self.add_query_count += 1

    def discard(self, x: int):
        if not 0 <= x < self.x_end:
            return
        x ^= self.lazy
        v = 0
        for i in reversed(range(self.max_log)):
            d = (x >> i) % 2
            if self.edges[2 * v + d] == -1:
                return
            v = self.edges[2 * v + d]
            self.v_list[i] = v
        if self.is_end[v] > 0:
            self.is_end[v] -= 1
            for v in self.v_list:
                self.size[v] -= 1

    def erase(self, x: int, count: int = -1):
        # assert -1 <= count
        if not 0 <= x < self.x_end:
            return
        x ^= self.lazy
        v = 0
        for i in reversed(range(self.max_log)):
            d = (x >> i) % 2
            if self.edges[2 * v + d] == -1:
                return
            v = self.edges[2 * v + d]
            self.v_list[i] = v
        if count == -1 or self.is_end[v] < count:
            count = self.is_end[v]
        if self.is_end[v] > 0:
            self.is_end[v] -= count
            for v in self.v_list:
                self.size[v] -= count

    def count(self, x: int) -> int:
        if not 0 <= x < self.x_end:
            return 0
        x ^= self.lazy
        v = 0
        for i in reversed(range(self.max_log)):
            d = (x >> i) % 2
            if self.edges[2 * v + d] == -1:
                return 0
            v = self.edges[2 * v + d]
        return self.is_end[v]

    def __contains__(self, x: int) -> bool:
        return bool(self.count(x))

    def __len__(self):
        return self.size[0]

    def __bool__(self):
        return bool(len(self))

    def bisect_left(self, x: int) -> int:
        if x < 0:
            return 0
        if self.x_end <= x:
            return len(self)
        v = 0
        ret = 0
        for i in reversed(range(self.max_log)):
            d = (x >> i) % 2
            l = (self.lazy >> i) % 2
            lc = self.edges[2*v]
            rc = self.edges[2*v+1]
            if l == 1:
                lc, rc = rc, lc
            if d:
                if lc != -1:
                    ret += self.size[lc]
                if rc == -1:
                    return ret
                v = rc
            else:
                if lc == -1:
                    return ret
                v = lc
        return ret

    def bisect_right(self, x: int) -> int:
        return self.bisect_left(x + 1)

    def index(self, x: int) -> int:
        if x not in self:
            raise ValueError(f"{x} is not in BinaryTrie")
        return self.bisect_left(x)

    def find(self, x: int) -> int:
        if x not in self:
            return -1
        return self.bisect_left(x)

    def kth_elem(self, k: int) -> int:
        if k < 0:
            k += self.size[0]
        # assert 0 <= k < self.size[0]
        v = 0
        ret = 0
        for i in reversed(range(self.max_log)):
            l = (self.lazy >> i) % 2
            lc = self.edges[2*v]
            rc = self.edges[2*v+1]
            if l == 1:
                lc, rc = rc, lc
            if lc == -1:
                v = rc
                ret |= 1 << i
                continue
            if self.size[lc] <= k:
                k -= self.size[lc]
                v = rc
                ret |= 1 << i
            else:
                v = lc
        return ret

    def minimum(self) -> int:
        return self.kth_elem(0)

    def maximum(self) -> int:
        return self.kth_elem(-1)

    def __iter__(self):
        q = [(0, 0)]
        for i in reversed(range(self.max_log)):
            l = (self.lazy >> i) % 2
            nq = []
            for v, x in q:
                lc = self.edges[2*v]
                rc = self.edges[2*v+1]
                if l == 1:
                    lc, rc = rc, lc
                if lc != -1:
                    nq.append((lc, 2 * x))
                if rc != -1:
                    nq.append((rc, 2 * x + 1))
            q = nq
        for v, x in q:
            for _ in range(self.is_end[v]):
                yield x

    def __str__(self):
        prefix = "BinaryTrie("
        content = list(map(str, self))
        suffix = ")"
        if content:
            content[0] = prefix + content[0]
            content[-1] = content[-1] + suffix
        else:
            content = [prefix + suffix]
        return ', '.join(content)

    def __getitem__(self, k):
        return self.kth_elem(k)

from heapq import *

N, Q = map(int, input().split())
STXs = [tuple(map(int, input().split())) for _ in range(N)]
Ds = [int(input()) for _ in range(Q)]

i2X = sorted(map(lambda t: t[2], STXs))
X2i = {X:i for i, X in enumerate(i2X)}
max_i = len(i2X) - 1
mbit = BinaryTrie(max_log=30, allow_multiple_elements=True, add_query_limit=2*10**5)

class Bit:
    def __init__(self, n):
        self.size = n
        self.tree = [0] * (n + 1)

    def sum(self, i):
        s = 0
        while i > 0:
            s += self.tree[i]
            i -= i & -i
        return s

    def add(self, i, x):
        while i <= self.size:
            self.tree[i] += x
            i += i & -i
class RangeUpdate:
    #1 -index 1~n
    def __init__(self, n):
        self.p = Bit(n + 1)
        self.q = Bit(n + 1)

    def add(self, s, t, x):
        t += 1
        self.p.add(s, -x * s)
        self.p.add(t, x * t)
        self.q.add(s, x)
        self.q.add(t, -x)

    def sum(self, s, t):
        t += 1
        return self.p.sum(t) + self.q.sum(t) * t - \
               self.p.sum(s) - self.q.sum(s) * s


from pydantic import BaseModel, Field

class CreateListSchema(BaseModel):
    title: str = Field(...)
    uid: str = Field(...)

    class Config:
        schema_extra = {
            "example": {
                "title": "Sample Title",
                "uid": "123456-1234-1234-123456"
            }
        }

class UpdateListSchema(BaseModel):
    lid: str = Field(...)
    title: str = Field(...)

    class Config:
        schema_extra = {
            "example": {
                "lid": "123456-1234-1234-123456",
                "title": "Sample Title"
            }
        }


import os
import sys
import json
import random
import collections
import numpy as np
from alive_progress import alive_bar
from time import sleep

print ("This is the name of the script: ", sys.argv[0])
print ("Number of arguments: ", len(sys.argv))
print ("The arguments are: " , str(sys.argv))

if(len(sys.argv) != 2):
    print("Utilisation : RL.py nombreDeCombats")
    exit(1)

nbFight = int(sys.argv[1])
tabAssociatif = {}
tabRL = []
file_scenario = "test/scenario/scenario-lvl301.json"

freeIndex = 0

with open('test/ai/IA-train.leek', 'w') as f:
    f.write("")
    f.close()

with alive_bar(nbFight) as bar:
    for i in range(nbFight) :
        with open(file_scenario) as jsonFile:
            scenario = json.load(jsonFile)
            jsonFile.close()

        scenario["random_seed"] = random.randint(0,10000000)

        with open(file_scenario, 'w') as f:
            json.dump(scenario, f)

        fileName = 'resultat.json'

        os.system('java -jar generator.jar '+ file_scenario +' > ' + fileName)

        with open(fileName, "r") as f:
            lines = f.readlines()
        with open(fileName, "w") as f:
            for line in lines:
                if line.strip("\n") != "db_resolver false folder=0 farmer=0":
                    f.write(line)

        with open(fileName) as jsonFile:
            resultat = json.load(jsonFile)
            jsonFile.close()

        logs = resultat['logs']['0']

        sortedLogs = collections.OrderedDict(sorted(logs.items()))

        winnerID = int(resultat['winner'])
        winnerName = scenario["entities"][winnerID][0]["name"]
        duration = resultat["duration"]
        execution_time = resultat["execution_time"]

        print("Combat " + str(i + 1) + " : Winner -> " + winnerName + " // Duree : " + str(duration) + " tours // Temps : " +  str(execution_time / 10000000) + " sec")

        for log in sortedLogs:
            rl = str(sortedLogs[log][0][2])
            rl = rl.split('/')
            #print(rl)
            #tabRL[rl[0]] = int(rl[1])

            maxCpt = 0
            maxVector = ""
            if rl[0] not in tabAssociatif :
                if freeIndex == 130 :
                    for k, v in tabAssociatif.items():
                        cpt = 0
                        print(k +" , "+ rl[0])
                        for i in range(len(k)):
                            if k[i] == rl[0][i]:
                                cpt += 1
                        if cpt > maxCpt:
                            maxVector = k
                            maxCpt = cpt

                    tabRL[(tabAssociatif[maxVector]*44)+int(rl[1])] += int(rl[2])
                else :
                    tabAssociatif[str(rl[0])] = freeIndex
                    for i in range(44):
                        tabRL.append(1)
                    if int(rl[2]) == 0 :
                        tabRL[(freeIndex*44)+int(rl[1])] = 1
                        freeIndex += 1
                    else:
                        tabRL[(freeIndex*44)+int(rl[1])] = int(rl[2])
                        freeIndex += 1
            else :
                tabRL[(tabAssociatif[rl[0]]*44)+int(rl[1])] += int(rl[2])

        with open('test/ai/IA-train.leek', 'w') as f:
            f.write("var tabAssiocatif = []; \n")
            for elem in tabAssociatif:
                f.write("tabAssiocatif['"+str(elem)+"'] = "+str(tabAssociatif[elem])+"; \n")
            f.write("var tabRL = [ ")
            for elem in tabRL :
                f.write(str(elem)+",")
            f.write("];\n")
            f.write("\n")
            f.write("\n")
            with open('test/ai/IA-train_Script.leek',"r") as script:
                for line in script.readlines():
                    f.write(line)
                script.close()
            f.close()

        print("--------------------------------------------------------------")
        print("// Tableau de renforcement")
        print("global tab = [];")
        print(tabAssociatif)
        print(tabRL)
        bar()


class BinaryTree():
    """
    A class to implement a binary tree node and by extension a full binary tree

    Attributes
    ----------
    left - the child node to the left of the current node - data is less than parent
    right - the child node to the right of the current node - data is greater than parent
    data - the value of the current node

    Methods
    -------
    findMax - finds max value in the tree
    findVal - finds a value in the tree and returns true/false based on result
    binarySearch - performs a binary search to look for a value - same purpose as findVal
    printAll - prints all values in the binary tree using a breadth first method
    insert - inserts a new data point into the tree as a new node.
    inOrder - returns an array with the tree in inorder
    preOrder - same as inorder except array is in preorder
    postOrder - same as inorder except array is in postorder
    """

    def __init__(self,data,left,right):
        self.left = left
        self.right = right
        dataPoints = [data]
        leftExists = False
        if left != None:
            leftExists = True
            dataPoints.append(left.data)
        if right != None:
            dataPoints.append(right.data)
        dataPoints.sort()
        if len(dataPoints) not in [1,2,3]:
            raise Exception("Missing Parameter")
        elif len(dataPoints) == 1:
            self.data = data
        elif len(dataPoints) == 2:
            if leftExists:
                self.left.data = dataPoints[0]
                self.data = dataPoints[1]
            else:
                self.data = dataPoints[0]
                self.right.data = datapoints[1]
        else:
            self.left.data = dataPoints[0]
            self.data = dataPoints[1]
            self.right.data = dataPoints[2]

    def findMax(self):
        currentMax = 0
        if self.left != None:
            val = self.left.findMax()
            if val > currentMax:
                currentMax = val
        if self.right != None:
            val = self.right.findMax()
            if val > currentMax:
                currentMax = val
        if self.data > currentMax:
            return self.data
        else:
            return currentMax

    def findVal(self,val):
        if self.data == val:
            return True
        if self.left != None:
            if self.left.findVal(val):
                return True
        if self.right != None:
            if self.right.findVal(val):
                return True
        return False

    def binarySearch(self,val):
        if val == self.data:
            return True
        elif val < self.data and self.left != None:
            return self.left.binarySearch(val)
        elif val > self.data and self.right != None:
            return self.right.binarySearch(val)
        else:
            return False

    def printAll(self):
        nodes = [self]
        while len(nodes) > 0:
            print(nodes[0].data)
            if nodes[0].left != None:
                nodes.append(nodes[0].left)
            if nodes[0].right != None:
                nodes.append(nodes[0].right)
            del nodes[0]

    def insert(self, item):
        if item > self.data:
            if self.right != None:
                self.right.insert(item)
            else:
                self.right = BinaryTree(item,None,None)
        else:
            if self.left != None:
                self.left.insert(item)
            else:
                self.left = BinaryTree(item,None,None)

    def inOrder(self):
        nodes = [self]
        count = 0
        while len(nodes) > count:
            if isinstance(nodes[count],int):
                count += 1
            else:
                toAdd = []
                current = nodes[count]
                if current.left != None:
                    toAdd.insert(0,current.left)
                toAdd.insert(0,current.data)
                if current.right != None:
                    toAdd.insert(0,current.right)

                for item in toAdd:
                    nodes.insert(count,item)
                del nodes[count+len(toAdd)]

        return nodes

    def preOrder(self):
        nodes = [self]
        count = 0
        while len(nodes) > count:
            if isinstance(nodes[count],int):
                count += 1
            else:
                toAdd = []
                current = nodes[count]
                toAdd.insert(0,current.data)
                if current.left != None:
                    toAdd.insert(0,current.left)
                if current.right != None:
                    toAdd.insert(0,current.right)

                for item in toAdd:
                    nodes.insert(count,item)
                del nodes[count+len(toAdd)]

        return nodes

    def postOrder(self):
        nodes = [self]
        count = 0
        while len(nodes) > count:
            if isinstance(nodes[count],int):
                count += 1
            else:
                toAdd = []
                current = nodes[count]
                if current.left != None:
                    toAdd.insert(0,current.left)
                if current.right != None:
                    toAdd.insert(0,current.right)
                toAdd.insert(0,current.data)

                for item in toAdd:
                    nodes.insert(count,item)
                del nodes[count+len(toAdd)]

        return nodes

leafOne = BinaryTree(2,None,None)
leafTwo = BinaryTree(4,None,None)
leafThree = BinaryTree(6,None,None)
leafFour = BinaryTree(8,None,None)
middleOne = BinaryTree(10,leafOne,leafTwo)
middleTwo = BinaryTree(12,leafThree,leafFour)
root = BinaryTree(14,middleOne,middleTwo)

root.printAll()
print("")
print(root.postOrder())


# -*- coding: utf-8 -*-
#
# Small helper functions for system services
#
# !!! This file is python 3.x compliant !!!
#

import logging
import os
import re
import shlex
import signal
import subprocess
import sys

try:
    import requests
    import urllib3

    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
except ImportError:
    pass

_logger = logging.getLogger("ultimarc")

class TerminalColors(object):
    """
    Simple class for setting terminal colors.
    https://en.wikipedia.org/wiki/ANSI_escape_code
    """

    reset = '\033[0m'
    bold = '\033[1m'
    underline = '\033[4m'

    fg_black = '\033[38;5;0m'
    fg_red = '\033[38;5;1m'
    fg_green = '\033[38;5;2m'
    fg_yellow = '\033[38;5;3m'
    fg_blue = '\033[38;5;4m'
    fg_magenta = '\033[38;5;5m'
    fg_cyan = '\033[38;5;6m'
    fg_white = '\033[38;5;7m'

    fg_bright_black = '\033[38;5;8m'
    fg_bright_red = '\033[38;5;9m'
    fg_bright_green = '\033[38;5;10m'
    fg_bright_yellow = '\033[38;5;11m'
    fg_bright_blue = '\033[38;5;12m'
    fg_bright_magenta = '\033[38;5;13m'
    fg_bright_cyan = '\033[38;5;14m'
    fg_bright_white = '\033[38;5;15m'

    bg_black = '\033[48;5;0m'
    bg_red = '\033[48;5;1m'
    bg_green = '\033[48;5;2m'
    bg_yellow = '\033[48;5;3m'
    bg_blue = '\033[48;5;4m'
    bg_magenta = '\033[48;5;5m'
    bg_cyan = '\033[48;5;6m'
    bg_white = '\033[48;5;7m'

    bg_bright_black = '\033[48;5;8m'
    bg_bright_red = '\033[48;5;9m'
    bg_bright_green = '\033[48;5;10m'
    bg_bright_yellow = '\033[48;5;11m'
    bg_bright_blue = '\033[48;5;12m'
    bg_bright_magenta = '\033[48;5;13m'
    bg_bright_cyan = '\033[48;5;14m'
    bg_bright_white = '\033[48;5;15m'

    _default_format = ''
    _default_background = ''
    _default_foreground = ''

    def custom_fg_color(self, index: int) -> str:
        """
        Get a custom color seq
        :param index: intger 0 - 255
        :return: string
        """
        return f'\033[38;5;{index}m'

    def custom_bg_color(self, index: int) -> str:
        """
        Get a custom color seq
        :param index: intger 0 - 255
        :return: string
        """
        return f'\033[48;5;{index}m'

    def set_default_formatting(self, *args):
        """
        Set the default colors for formatting.
        :param args: list of colors.
        """
        self._default_format = ''
        for arg in args:
            self._default_format += arg

    def set_default_background(self, *args):
        """
        Set default background colors
        :param args: list of colors
        """
        self._default_background = ''
        for arg in args:
            self._default_background += arg

    def set_default_foreground(self, *args):
        """
        Set default foreground colors
        :param args: list of colors
        """
        self._default_foreground = ''
        for arg in args:
            self._default_foreground += arg

    def fmt(self, line: str, *args) -> str:
        """
        Color a line of text
        :param line: string
        :param args: list of colors.
        :return: string
        """
        if not args:
            l = self._default_format
        else:
            l = ''
            for arg in args:
                l += arg

        l += str(line)
        l += self.reset

        l += self._default_background
        l += self._default_foreground

        return l

tc = TerminalColors()

class _ToolLoggingFormatter(logging.Formatter):
    """
    Add colorization to logging messages.
    """

    _color = TerminalColors()

    def format(self, record):
        msg = super(_ToolLoggingFormatter, self).format(record)
        if record.levelno == logging.DEBUG:
            msg = self._color.fmt(msg, self._color.fg_cyan)
        elif record.levelno == logging.ERROR:
            msg = self._color.fmt(msg, self._color.fg_bright_red)
        elif record.levelno == logging.WARNING:
            msg = self._color.fmt(msg, self._color.fg_bright_yellow)

        return msg

def setup_logging(logger, progname, debug=False, quiet=False, logfile=None):
    """
  Setup Python logging
  :param logger: Handle to logger object
  :param progname: Name of application or service
  :param debug: True if debugging enabled
  :param quiet: True if quiet output selected.
  :param logfile: Path and filename to log file to output to
  :return: Nothing
  """
    if not logger:
        return False

    # Set our logging options and formatter now that we have the program arguments.
    if debug:
        logging.basicConfig(filename=os.devnull, datefmt="%Y-%m-%d %H:%M:%S", level=logging.DEBUG)
        formatter = _ToolLoggingFormatter("%(asctime)s {0}: %(levelname)s: %(message)s".format(progname))
    else:
        logging.basicConfig(filename=os.devnull, datefmt="%Y-%m-%d %H:%M:%S",
                            level=logging.WARNING if quiet else logging.INFO)
        # formatter = logging.Formatter('%(levelname)s: {0}: %(message)s'.format(progname))
        formatter = _ToolLoggingFormatter("%(message)s")

    # Setup stream logging handler
    handler = logging.StreamHandler(sys.stdout)
    handler.flush = sys.stdout.flush
    handler.setFormatter(formatter)

    logger.addHandler(handler)

    # Setup file logging handler
    if logfile:

        # make sure the path exists
        logpath = os.path.dirname(os.path.abspath(os.path.expanduser(logfile)))

        if not os.path.exists(logpath):
            os.makedirs(logpath)

        handler = logging.FileHandler(logfile)
        handler.setFormatter(logging.Formatter("%(asctime)s {0}: %(levelname)s: %(message)s".format(progname)))

        logger.addHandler(handler)

def which(program):
    """
  Find the path for a given program
  http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
  :param program: name of executable file to find
  """

    try:
        path_spec = os.environ["PATH"]
    except KeyError:
        # for weird times when we don't have a good environment
        path_spec = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin"

    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    # pylint: disable=unused-variable
    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in path_spec.split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None

def run_external_program(args, cwd=None, env=None, shell=False, debug=False):
    """
  Run an external program, arguments
  :param args: program name plus arguments in a list
  :param cwd: Current working directory
  :param env: A modified environment to use
  :param shell: Use shell to execute command
  :param debug: Add '--debug' to args if '--debug' is in sys.argv
  :return: exit code, stdoutdata, stderrdata
  """

    if not args:
        _logger.debug("run_external_program: bad arguments")
        return -1, None, None

    if debug is True and "--debug" in sys.argv and "--debug" not in args:
        args.append("--debug")

    _logger.debug("external: {0}".format(os.path.basename(args[0])))

    p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, shell=shell)
    stdoutdata, stderrdata = p.communicate()
    p.wait()

    if isinstance(stdoutdata, (bytes, bytearray)):
        stdoutdata = stdoutdata.decode("utf-8")
    if isinstance(stderrdata, (bytes, bytearray)):
        stderrdata = stderrdata.decode("utf-8")

    return p.returncode, stdoutdata, stderrdata

def is_valid_email(email):
    """
  Validate email parameter is a valid formatted email address
  :param email: string containing email address
  :return: True if email is valid otherwise False
  """
    if not email:
        return False

    return bool(re.match("^.+@(\[?)[a-zA-Z0-9-.]+.([a-zA-Z]{2,3}|[0-9]{1,3})(]?)$", email))

def signal_process(name, signal_code=signal.SIGHUP):
    """
  Send a signal to a program
  :param name: name of the executable, not a systemd service name
  :param signal_code: signal code from signal object
  """
    pid = None

    prog = which("pidof")

    if not prog:
        _logger.error('unable to locate "pidof" executable.')
        return False

    # Get the process id
    try:

        args = shlex.split("{0} {1}".format(prog, name))
        pid = int(subprocess.check_output(args).decode("utf-8").strip())
    except subprocess.CalledProcessError:
        _logger.error("failed to get program pid ({0})".format(name))

    # Send signal to process
    if pid:
        os.kill(pid, signal_code)
        _logger.debug("send signal to {0} complete".format(name))
        return True

    _logger.debug("send signal to {0} failed".format(name))

    return False

def pid_is_running(pid: int):
    """
  Check For the existence of a unix pid.
  :param pid: integer ID of this process
  :return: True if process with this ID is running, otherwise False
  """
    # See if there is a currently running mysqld instance
    # pylint: disable=unused-variable
    args = ['ps', '-eo', 'ruid,pid,ppid,args']
    code, so, se = run_external_program(args=args)
    if code == 0:
        lines = so.split('\n')
        for line in lines:
            if str(pid) in line:
                while '  ' in line:
                    line = line.replace('  ', ' ')
                if pid == int(line.strip().split(' ')[1]) and '<defunct>' not in line:
                    return True
    return False

def get_process_pids(matches: list) -> list:
    """
    Get the pids of a currently running process.  May match more than one running process.
    :param matches: parts of process command to match in process list
    :return: List of PIDs
    """
    pids = list()

    if not matches or len(matches) == 0:
        raise ValueError('matches list may not be empty.')

    for match in matches:
        if not isinstance(match, str):
            raise ValueError('invalid match value, must be string.')

    # See if there is a currently running mysqld instance
    # pylint: disable=unused-variable
    args = ['ps', '-ef']
    code, so, se = run_external_program(args=args)
    if code == 0:
        lines = so.split('\n')
        for line in lines:
            no_match = False
            for match in matches:
                if match not in line:
                    no_match = True
                    break

            if no_match is True:
                continue
            while '  ' in line:
                line = line.replace('  ', ' ')
            pids.append(int(line.split(' ')[1]))

    return pids

def write_pidfile_or_die(progname: str, pid_file: str = None):
    """
  Attempt to write our PID to the given PID file or raise an exception.
  :param progname: Name of this program
  :param pid_file: an alternate path and pid file to use
  :return: pid path and filename
  """
    if not pid_file:
        home = os.path.expanduser("~")
        pid_path = os.path.join(home, ".local/run")
        pid_file = os.path.join(pid_path, "{0}.pid".format(progname))
    else:
        pid_path = os.path.dirname(pid_file)

    if not os.path.exists(pid_path):
        os.makedirs(pid_path)

    if os.path.exists(pid_file):
        pid = int(open(pid_file).read())

        if pid_is_running(pid):
            _logger.warning("program is already running, aborting.")
            raise SystemExit

        else:
            os.remove(pid_file)

    open(pid_file, "w").write(str(os.getpid()))

    return pid_file

def remove_pidfile(progname: str, pid_file: str = None):
    """
  Remove the PID file for the given program
  :param progname: Name of this program
  :param pid_file: an alternate pid file to use
  """
    if not pid_file:
        home = os.path.expanduser("~")
        pid_path = os.path.join(home, ".local/run")
        pid_file = os.path.join(pid_path, "{0}.pid".format(progname))

    if os.path.exists(pid_file):
        os.remove(pid_file)

def print_progress_bar(iteration, total, prefix="", suffix="", decimals=1, bar_length=90, fill="█"):
    """
  Call in a loop to create terminal progress bar.
  https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
  https://gist.github.com/aubricus/f91fb55dc6ba5557fbab06119420dd6a
  :param iteration: Required  : current iteration (Int)
  :param total: Required  : total iterations (Int)
  :param prefix: Optional  : prefix string (Str)
  :param suffix: Optional  : suffix string (Str)
  :param decimals: Optional  : positive number of decimals in percent complete (Int)
  :param bar_length: Optional  : character length of bar (Int)
  :param fill: Optional  : bar fill character (Str)
  """
    str_format = "{0:." + str(decimals) + "f}"
    percents = str_format.format(100 * (iteration / float(total)))
    filled_length = int(round(bar_length * iteration / float(total)))
    bar = fill * filled_length + "-" * (bar_length - filled_length)

    sys.stdout.write("\r{0} [{1}] {2}{3} {4}".format(prefix, bar, percents, "%", suffix))

    if iteration == total:
        sys.stdout.write("\n")
    sys.stdout.flush()

def git_project_root(path=None):
    """
    Figure out the git project top level directory.
    :param path: optional: path to check.
    :return: Git project root path or None
    """
    cwd = os.curdir
    if path:
        if not os.path.exists(path):
            raise ValueError('Invalid directory path argument')
        os.chdir(path)

    args = ['git', 'rev-parse', '--show-toplevel']
    # pylint: disable=unused-variable
    code, so, se = run_external_program(args=args)

    os.chdir(cwd)

    if code == 0:
        return so.strip()

    return None


import unittest

from datastructures_and_algorithms.doubly_linked_list import DoublyLinkedList
from datastructures_and_algorithms.exceptions.StackEmptyException import StackEmptyException

class TestStack(unittest.TestCase):

    def test_front(self):

        dll = DoublyLinkedList()
        dll.insert_front(9)
        dll.insert_front(2)
        dll.insert_front(3)

        pop1 = dll.pop_front()
        pop2 = dll.pop_front()
        pop3 = dll.pop_front()

        self.assertEqual(3, pop1, "Pop front is broken")
        self.assertEqual(2, pop2, "Pop front is broken ")
        self.assertEqual(9, pop3, "Pop front is broken")

    def test_back(self):

        dll = DoublyLinkedList()
        dll.insert_back(9)
        dll.insert_back(2)
        dll.insert_back(3)

        pop1 = dll.pop_back()
        pop2 = dll.pop_back()
        pop3 = dll.pop_back()

        self.assertEqual(3, pop1, "Pop back is broken")
        self.assertEqual(2, pop2, "Pop back is broken ")
        self.assertEqual(9, pop3, "Pop back is broken")

    def test_queue(self):

        dll = DoublyLinkedList()
        dll.insert_front(9)
        dll.insert_front(2)
        dll.insert_front(3)

        pop1 = dll.pop_back()
        pop2 = dll.pop_back()
        pop3 = dll.pop_back()

        self.assertEqual(9, pop1, "Pop back is broken")
        self.assertEqual(2, pop2, "Pop back is broken ")
        self.assertEqual(3, pop3, "Pop back is broken")

    def test_reverse_queue(self):

        dll = DoublyLinkedList()
        dll.insert_back(9)
        dll.insert_back(2)
        dll.insert_back(3)

        pop1 = dll.pop_front()
        pop2 = dll.pop_front()
        pop3 = dll.pop_front()

        self.assertEqual(9, pop1, "Pop back is broken")
        self.assertEqual(2, pop2, "Pop back is broken ")
        self.assertEqual(3, pop3, "Pop back is broken")

    def test_counter_works(self):

        dll = DoublyLinkedList()
        counter = len(dll)

        self.assertEqual(0, counter, "Counter is broken (0)")

        dll.insert_front(9)
        dll.insert_front(2)
        dll.insert_front(3)

        counter3 = len(dll)

        self.assertEqual(3, counter3, "Counter is broken (After insert)")

        dll.pop_front()
        dll.pop_front()

        counter1 = len(dll)

        self.assertEqual(1, counter1, "Counter is broken (After insert, then pop)")

    def test_raises_exception_for_empty_list(self):

        dll = DoublyLinkedList()
        self.assertRaises(StackEmptyException, dll.pop_front)


# -*- coding: utf-8 -*-
"""
Created on Thu Aug 22 10:12:00 2019

@author: Nejc Coz
@copyright: ZRC SAZU (Novi trg 2, 1000 Ljubljana, Slovenia)

IMPORTANT: Set your SciHUB account credentials in apihub.txt!

The script creates CSV file containing the list of files to be downloaded from
SciHUB API. The list includes file ID, title, and downloaded status.
"""

import os
import sys

from shapely.geometry import box
from sentinelsat import SentinelAPI
# from sentinelsat import read_geojson, geojson_to_wkt

def main(csvpath, apipath, qp):
    # Read password file
    # ==================
    try:
        with open(apipath) as f:
            (usrnam, psswrd) = f.readline().split(" ")
            if psswrd.endswith("\n"):
                psswrd = psswrd[:-1]
    except IOError:
        sys.exit("Error reading the password file!")

    # Connect to API using <username> and <password>
    # ===============================================
    print("Connecting to SciHub API...")
    api = SentinelAPI(usrnam, psswrd, "https://scihub.copernicus.eu/dhus")

    # Search by SciHub query keywords
    # ===============================
    products = api.query(qp['footprint'],
                         beginposition=(qp['strtime'], qp['endtime']),
                         endposition=(qp['strtime'], qp['endtime']),
                         platformname=qp['platformname'],
                         producttype=qp['producttype'])

    # Convert to Pandas DataFrame and sort by date ascending
    # ======================================================
    products_df = api.to_dataframe(products)
    products_df_sorted = products_df.sort_values('beginposition', ascending=True)

    # Save to CSV file
    # ================
    print(f"Saving list to {os.path.basename(csvpath)}")
    prep_csv = products_df_sorted[['uuid', 'title']]
    prep_csv.insert(2, "downloaded", False, allow_duplicates=True)
    prep_csv.to_csv(csvpath, index=False)

    print('Finished!')

if __name__ == "__main__":
    # Path to CSV file with a list of products to be triggered
    csv_pth = ".\\userfiles\\slc_list.csv"

    # Path to file with SciHub credentials
    api_pth = ".\\userfiles\\apihub.txt"

    # Set query parameters
    ############################################################################
    #   * (Date-type query parameter 'beginposition' expects a two-element tuple
    #     of str or datetime objects.)
    #   * Search for last 24 hrs: date=('NOW-8HOURS', 'NOW') or NOW-<n>DAY(S) or
    #     datetime(2017, 1, 5, 23, 59, 59, 999999) + import datetime or string
    #     '2017-12-31T23:59:59.999Z'
    strtime = '2017-01-01T00:00:00.000Z'  # 'NOW-14DAYS'
    endtime = '2017-12-31T23:59:59.999Z'  # '2019-07-31T23:59:59.999Z'

    # Platform name:
    platformname = 'Sentinel-1'

    # Product type:
    producttype = 'SLC'

    # Geographical extents (minx, miny, maxx, maxy)
    footprint = box(13.278422963870495, 45.33663869316604,
                    16.687265418304985, 46.96845660190081)
    # nam_aoi = 'polygon.geojson'
    # pth_aoi = join(wrkdir, nam_aoi)
    # footprint = geojson_to_wkt(read_geojson(pth_aoi))

    query_params = {
        'strtime': strtime,
        'endtime': endtime,
        'platformname': platformname,
        'producttype': producttype,
        'footprint': footprint
    }

    main(csv_pth, api_pth, query_params)


import json
import random
import string
import time
from os import environ
from os.path import splitext, basename
from urlparse import urlparse
import sys

import boto3
from botocore.exceptions import ClientError
import requests
from slackclient import SlackClient

'''
The logic in this script wires a slack channel to s3

The partner to this script simply displays the latest image_filename
(reading the values we update below in current.json)

Be sure to create settings.sh (from the example) and run it
before running this script,

$ source ./settings.sh
'''

aws_access_key_id = environ['AWS_ACCESS_KEY_ID']
aws_secret_access_key = environ['AWS_SECRET_ACCESS_KEY']
aws_bucket_name = environ['AWS_BUCKET_NAME']
slack_token = environ['SLACK_TOKEN']
slack_channel = environ['SLACK_CHANNEL']

# Possible values for screens. config this.
screens = ['racehorse', 'icecream', 'strawberry', 'pepper', 'balloon', 'banana', 'blowfish']
# the screen where an image will appear when it's first posted
default_screen = screens[0]

def main():
    existing_status = get_start_state()
    # Our connection to the Slack API
    sc = SlackClient(slack_token)
    if sc.rtm_connect():
        while True:
            existing_status = build_config(existing_status, sc)
            time.sleep(1)
    else:
        print("Connection to Slack unavailable")

def get_start_state():
    """ Get current.json from the S3 bucket """
    s3 = s3_connect()
    try:
        s3.Bucket(aws_bucket_name).download_file('current.json', 'current.json')
    except ClientError as e:
        if e.response['Error']['Code'] == "404":
            sys.exit("current.json does not exist -- stopping")
        else:
            raise
    with open('current.json', 'r') as json_file:
        data = json.load(json_file)
    return data

def s3_connect():
    """ Connect to S3 and return the session resource """
    session = boto3.Session(aws_access_key_id=aws_access_key_id,
                            aws_secret_access_key=aws_secret_access_key)
    return session.resource("s3")

def post_image_to_aws(image_url, filename, file_ext, held_by_slack=False):
    """ Put an image in an S3 bucket """

    # Slack headers
    slack_request_headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 '
                                           '(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
    auth_value = 'Bearer %s' % slack_token
    slack_request_headers['Authorization'] = auth_value

    if held_by_slack:
        response = requests.get(image_url, headers=slack_request_headers)
    else:
        response = requests.get(image_url)

    data = response.content

    image_filename = '%s%s' % (filename, file_ext)

    # post image with new filename to s3
    s3 = s3_connect()
    s3.Bucket(aws_bucket_name).put_object(Key=image_filename, Body=data)

    return image_filename

def update_current(generated_status):
    """ Write current.json to the S3 bucket """
    s3 = s3_connect()
    s3.Object(aws_bucket_name, 'current.json').put(Body=json.dumps(generated_status), CacheControl='max-age=1')

def extract_reactions(obj):
    """ Get reactions aka emoji from a Slack message or file """
    destinations = []
    bgcolor = '#fff'
    if 'reactions' in obj.keys():
        for reaction in obj['reactions']:
            if reaction['name'] in screens:
                destinations.append(reaction['name'])
            elif 'night' in reaction['name']:
                bgcolor = "#000"
    if not destinations:
        destinations = [default_screen]
    return destinations, bgcolor

def generate_filename(status):
    """ Create a random filename -- don't collide with recent filenames """
    existing = [x.split('.')[0] for x in status['_mapping'].values()]
    while True:
        filename = ''.join(random.choice(string.ascii_uppercase) for _ in range(6))
        if filename not in existing:
            return filename

def update_status(destinations, bgcolor, existing_status, generated_status, slack_image_id, url, held_by_slack):
    """ Upload image to S3 if necessary, then update the generated status """
    url_pieces = urlparse(url)
    file_ext = splitext(basename(url_pieces.path))[1]
    not_uploaded = True
    image_filename = None
    if slack_image_id in existing_status['_mapping']:
        image_filename = existing_status['_mapping'][slack_image_id]
        s3 = s3_connect()
        try:
            s3.Object(aws_bucket_name, image_filename).load()
            not_uploaded = False
        except ClientError as e:
            if e.response['Error']['Code'] == "404":
                not_uploaded = True
            else:
                raise
    if not_uploaded:
        print("uploading to bucket...")
        filename = generate_filename(existing_status)
        image_filename = post_image_to_aws(url, filename, file_ext, held_by_slack=held_by_slack)
        existing_status['_mapping'][slack_image_id] = image_filename
    for destination in destinations:
        generated_status[destination] = {'id': slack_image_id, 'url': image_filename, 'bg': bgcolor}
    generated_status['_mapping'] = existing_status['_mapping']
    return generated_status

def build_config(existing_status, sc):
    """

    Our destinations will be bundled in a json object that kinda looks like
    this (and is the thing watched by the js, current.js, in the screen's
    browser to see if it needs to update its pic)

    {
    'racehorse': { 'url': 'http:// bucket at s3'},
                    'id': 'some slack generated guid',
                    'bg': '#fff'},

    'icecream':  { 'url': 'http:// bucket at s3'},
                    'id': 'some slack generated guid',
                    'bg': '#000'},
    }
    """

    generated_status = {}
    # retrieve recent posts in our channel
    history = sc.api_call('channels.history', channel=slack_channel, inclusive='true', count=len(screens))

    # find images posted to channel and build status to update
    # probably not great to rely on order like this:
    messages = history['messages']
    messages.reverse()
    for kl in messages:
        if 'ts' in kl:
            slack_image_id = kl['ts']
            # uploaded image
            if 'files' in kl:
                file_info = kl['files'][0]
                if 'url_private' in file_info:
                    # see if we have reactions along with our uploaded file
                    destinations, bgcolor = extract_reactions(file_info)
                    url = file_info['url_private']
                    generated_status = update_status(destinations, bgcolor, existing_status, generated_status,
                                                     slack_image_id, url, held_by_slack=True)

            # if someone pastes text with a link to an image in it
            elif 'attachments' in kl.keys() and 'image_url' in kl['attachments'][0].keys():
                destinations, bgcolor = extract_reactions(kl)
                url = kl['attachments'][0]['image_url']
                generated_status = update_status(destinations, bgcolor, existing_status, generated_status,
                                                 slack_image_id, url, held_by_slack=False)

    if generated_status:
        update_current(generated_status)
        existing_status = generated_status

    return existing_status

if __name__ == "__main__":
    main()


import math
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import r2_score
from keras.models import Sequential
from keras.layers import Dense, LSTM
from utilities import *
import altair as alt

# Function to create a model which will predict the stock price for the next day.
def stock_predictor(ticker, start, end):
    # Variable to hold the long name of a specific ticker.
    name = get_company_name_long(ticker)
    # Set seed to produce consistent results.
    np.random.seed(8)
    # Get the stock data
    df = get_stock_data(ticker, start, end)
    # Create new dataframe with only close column
    data = df.filter(['Close'])
    # Convert to numpy array
    dataset = data.values
    # Get the number of rows to train the model on. In this case, we selected 80% of the data (4 years) to train
    # the model with. This number can be shifted either up or down to see how that would change the results
    # of the model.
    training_data_len = math.ceil(len(dataset) * .8)
    # Scale the data - this will squeeze the values to be between 0 and 1 for processing by the model.
    scaler = MinMaxScaler(feature_range=(0, 1))
    scaled_data = scaler.fit_transform(dataset)
    # Create the scaled training dataset
    train_data = scaled_data[0:training_data_len, :]
    # Split the data into x_train and y_train data sets
    x_train = []
    y_train = []
    for i in range(60, len(train_data)):
        x_train.append(train_data[i - 60:i, 0])
        y_train.append(train_data[i, 0])
    # Convert the x_train and y_train to numpy arrays
    x_train, y_train = np.array(x_train), np.array(y_train)
    # Reshape the x_train dataset
    x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
    # Build the LSTM model
    model = Sequential()
    model.add(LSTM(50, return_sequences=True, input_shape=(x_train.shape[1], 1)))
    model.add(LSTM(50, return_sequences=False))
    model.add(Dense(25))
    model.add(Dense(1))
    # Compile the model
    model.compile(optimizer='adam', loss='mean_squared_error')
    # Train the model. Here we set the epochs to 1 but this number can be anything in the range of 1 - N. However,
    # this causes the processing of the model to increase at a rate of O(N) meaning 10 epochs will take approximately
    # 10 times as much time to process as 1 epoch.
    model.fit(x_train, y_train, batch_size=1, epochs=1)
    # Create the testing data set with scaled data.
    test_data = scaled_data[training_data_len - 60:, :]
    # Create the data sets x_test and y_test to test the prediction capability of the model.
    x_test = []
    # y_test will hold the actual values for the stock market days we are trying to predict.
    y_test = dataset[training_data_len:, :]
    for i in range(60, len(test_data)):
        x_test.append(test_data[i - 60:i, 0])
    # Convert the data to a numpy array
    x_test = np.array(x_test)
    # Reshape the data
    x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))
    # Get the models predictions
    predictions = model.predict(x_test)
    predictions = scaler.inverse_transform(predictions)
    # Get the r2 value to evaluate the accuracy of the model.
    accuracy = r2_score(y_test, predictions)
    st.write(f"R2 score of this model is {accuracy:.2f}")
    # Plot the data. This plot shows predicted vs. actual closing price for the selected stock.
    valid = data[training_data_len:]
    valid['Predictions'] = predictions
    data = valid.reset_index().melt('Date')
    data2 = data.rename(columns={"value": "Amount", "variable": "Measure"}, errors="raise")
    alt_chart = alt.Chart(data2).mark_line().encode(
        x='Date',
        y='Amount',
        color='Measure'
    ).properties(title=f"Predicted vs. Actual Closing Price for {name}", height=500, width=750).interactive()
    st.write(alt_chart)
    # Scatter plot for predicted vs close prices; this shows how close the model's predicted values aligned with
    # the actual closing prices for the selected stock. A 45-degree angle would mean the model perfectly predicted
    # the closing price.
    scatter = alt.Chart(valid).mark_circle(size=60).encode(
        alt.X('Close',
              scale=alt.Scale(zero=False)
              ),
        alt.Y('Predictions',
              scale=alt.Scale(zero=False)
              )
    ).properties(title=f"Predicted vs. Actual Closing Price for {name}", height=450, width=670).interactive()
    st.write(scatter)
    # Histogram chart showing the distribution of errors in terms of % for predicted vs. actual stock prices.
    valid['Error $'] = valid['Predictions'] - valid['Close']
    valid['Error %'] = valid['Error $'] / valid['Close']
    valid['Error %'] = valid['Error %'] * 100
    histogram = alt.Chart(valid).mark_bar().encode(
        alt.X("Error %", bin=True),
        y='count()',
    ).properties(title=f"Predicted price error for {name}", height=450, width=670).interactive()
    st.write(histogram)
    # Predict the next day's closing price
    # Get the quote
    stock_data = get_stock_data(ticker, start, end)
    new_df = stock_data.filter(['Close'])
    # Get the last 60 days closing price and convert the dataframe to an array
    last_60_days = new_df[-60:].values
    # Scale the data to be values between 0 and 1
    last_60_days_scaled = scaler.transform(last_60_days)
    # Create a list with the last 60 days.
    X_test = [last_60_days_scaled]
    # Convert to numpy array
    X_test = np.array(X_test)
    # Reshape the data
    X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
    # Get predicted scaled price
    predicted_price = model.predict(X_test)
    # Undo the scaling
    predicted_price = scaler.inverse_transform(predicted_price)
    # Get last predicted value
    length = len(valid)
    prediction_values = valid['Predictions']
    last_day = prediction_values.iloc[length - 1]
    predicted_price_value = predicted_price.item(0)
    # Check if predicted value went up or down. If predicted price is higher, recommend buying the stock.
    # If the predicted price is lower, recommend selling the stock.
    if last_day < predicted_price:
        st.write(f"Predicted stock price for {name} tomorrow is {predicted_price_value:.2f}. This is greater"
                 f" than the previous predicted price of {last_day:.2f}, therefore The Prophet "
                 f"recommends buying {name} stock.")
    else:
        st.write(f"Predicted stock price for {name} tomorrow is {predicted_price_value:.2f}. This is less"
                 f" than the previous predicted price of {last_day:.2f}, therefore The Prophet "
                 f"recommends selling {name} stock.")


from typing import Optional
from user import User

class InconsistentException(Exception):
    """
    Exception raised when the social network is inconsistent.
    """
    def __init__(self, friend, user):
        Exception.__init__(self, f"Inconsistent social network. User '{friend}' is friend with user '{user}' but the "
                                 f"reverse is not true.")

class SocialNetwork:
    """
    Encapsulation of the users and their friends.
    """
    users: dict[str, User]
    common_friends: dict[(str, str), int]

    def __init__(self):
        """
        Initialises the map of users to empty and common friends to None.
        """
        self.users = {}
        self.common_friends = None

    def add_user(self, user_name: str) -> User:
        """
        Adds a user to the network if it does not exist.
        :param user_name: name of the user to add.
        :return: user object corresponding to the given name.
        """
        if user_name not in self.users:
            self.users[user_name] = User(user_name)
        return self.users[user_name]

    def add_friend(self, user_name: str, friend_name: str) -> User:
        """
        Associates a user to a new friend.
        :param user_name: name of user to associate.
        :param friend_name: name of friend to associate with.
        :return: user object corresponding to the friend.
        """
        user: User = self.add_user(user_name)
        friend: User = self.add_user(friend_name)
        user.add_friend(friend_name)
        # friend.add_friend(user_name)
        return friend

    def get_user(self, user_name: str) -> Optional[User]:
        """
        Gets the user associated with given name, if any.
        :param user_name: name of user to get.
        :return: user object corresponding to the name or None.
        """
        if user_name in self.users:
            return self.users[user_name]
        return None

    def get_friends(self, user_name: str) -> list[User]:
        """
        Gets the list of objects corresponding to the friends of the given user.
        :param user_name: name of user to get friends of, if any.
        :return: list of friend objects or empty list if user does not exist.
        """
        user: User = self.get_user(user_name)
        if user is None:
            return []
        result: list[User] = []
        for friend_name in user.friend_names:
            friend: User = self.get_user(friend_name)
            result += [friend]
        return result

    # Feature 2 i.
    def generate_common_friends(self, common_friends: dict[(str, str), int]) -> dict[str, list[int]]:
        """
        Generates a matrix containing the number of common friends for every pair of users in the social network.
        :param common_friends: dictionary containing a pair of users as the keys and the number of common friends as
        the values.
        :return: dictionary containing each username as keys and lists of common friends associated with
        the other users as the values.
        """
        common_matrix: dict = {}
        for name in sorted(self.users.keys()):
            common_matrix[name] = []
        for user in common_matrix:
            ln: dict[str, int] = {}
            if common_friends is None:
                common_friends: dict = self._compute_common_friends()
            for (n1, n2) in common_friends:
                if user == n1:
                    ln[n2] = common_friends[(n1, n2)]
            u: User = self.users[user]
            ln[user]: list = len(u.friend_names)
            for n2 in sorted(ln.keys()):
                common_matrix[user] += [ln[n2]]
        return common_matrix

    def get_common_friends(self) -> dict[(str, str), int]:
        """
        Gets the number of common friends for each pair of friends in the social network.
        :return: dictionary containing number of common friends for each pair.
        """
        if self.common_friends is None:
            self.common_friends = self._compute_common_friends()
        return self.common_friends

    def _compute_common_friends(self) -> dict[(str, str), int]:
        result: dict[(str, str), int] = {}
        for name1 in self.users.keys():
            for name2 in self.users.keys():
                if name1 != name2:
                    result[(name1, name2)] = self._compute_friends_count(name1, name2)
        return result

    def _compute_friends_count(self, name1: str, name2: str) -> int:
        l1: list = self.users[name1].friend_names
        l2: list = self.users[name2].friend_names
        common_num: int = 0
        for n in l1:
            if n == name1 or n == name2:
                continue
            if n in l2:
                common_num += 1
        return common_num

    # Feature 2 ii.
    def recommend_friend(self, user_name: str) -> Optional[str]:
        """
        Gets a username as a parameter and recommends a new friend based on the number of common friends with that
        potential friend.
        :param user_name: name of a user seeking a new friend.
        :return: recommended friend or None.
        """
        u: User = self.users[user_name]
        if not u.friend_names:
            return None
        cf: dict[(str, str), int] = self.get_common_friends()
        max_common: int = 0
        result = None
        for (n1, n2) in cf.keys():
            if n1 == user_name:
                if n2 in u.friend_names:
                    continue
                if cf[(n1, n2)] > max_common:
                    max_common = cf[(n1, n2)]
                    result = n2
        return result

    # Feature 3 iii.
    def get_user_relationship(self, name: str) -> dict[str, list[str]]:
        """
        Gets a list of friends corresponding to the given user.
        :param name: name of user whose friends shall be returned.
        :return: dictionary containing strings as keys and lists of strings as their value.
        """
        result: dict[str, list[str]] = {}
        user: User = self.users[name]
        result[name] = user.friend_names
        return result

    # Feature 3 iv.
    def get_indirect_relationships(self) -> dict[str, list[str]]:
        """
        Gets indirect relationships between users (i.e. friend of a friend of a user).
        :return: dictionary containing strings as keys and lists of strings as their value.
        """
        names = self.compute_friendships()
        indirect_friends: dict[str, list[str]] = {}
        for u in names:
            indirect_friends[u] = []
        for n1 in names:
            for n2 in names[n1]:
                for n3 in names[n2]:
                    if n1 != n3 and n3 not in names[n1]:
                        indirect_friends[n1] += [n3]
        return indirect_friends

    def compute_friendships(self) -> dict[str, list[str]]:
        """
        Adds the usernames (as keys) and their friends (as values) to a dictionary 'names'.
        :return: dictionary containing strings as keys and lists of strings as their value.
        """
        names: dict[str, list[str]] = {}
        for name in sorted(self.users.keys()):
            user: User = self.users[name]
            names[name]: list = user.friend_names
        return names

    # Feature 3 v.
    def validate(self):
        """
        Checks if a user is friends with another user and vice versa.
        :return: None
        """
        for user in self.users:
            u: User = self.users[user]
            for friend in u.friend_names:
                f: User = self.users[friend]
                if user in f.friend_names:
                    pass
                else:
                    raise InconsistentException(friend, user)


"""
Utility functions related to image processing
"""

import numpy as np
import pandas as pd

def image_to_dataframe(X, y=None, bands=None, target_feature="target"):
    """
    Converts an image array (height, width, bands) to a pandas dataframe
    (height * width, bands). If ``y`` is not ``None``, a feature with name
    ``target_feature`` will be added to the dataset.

    .. note:: Some workflows use image arrays of format (bands, width, height). In that
        case, you can simply transpose the image before calling this function.

    Parameters
    ----------
    X : array-like of shape (h, w, b)
        Matrix containing the image data.

    y : array-like of shape (h, w), default=None
        The target values (class labels) as integers or strings.

    bands : array-like of shape (b,), default=None
        The names of the bands in the image.

    target_feature : str, default="target"
        Target feature name.

    Returns
    -------
    df_image : pd.DataFrame, shape (h * w, b[+1])
        Dataframe with pixel coordinates (h, w) as index, counting from the top left
        corner.

    Examples
    --------

    >>> import numpy as np
    >>> X = np.random.default_rng(42).random((4,5,3))
    >>> y = np.random.default_rng(42).integers(0,2,4*5).reshape(4,5)
    >>> image_to_dataframe(X).head(5)
    ...             0         1         2
    ... h w
    ... 0 0  0.773956  0.438878  0.858598
    ...   1  0.697368  0.094177  0.975622
    ...   2  0.761140  0.786064  0.128114
    ...   3  0.450386  0.370798  0.926765
    ...   4  0.643865  0.822762  0.443414
    >>> image_to_dataframe(X, y, bands=["r","g", "b"], target_feature="classes").head(5)
    ...             r         g         b  classes
    ... h w
    ... 0 0  0.773956  0.438878  0.858598        0
    ...   1  0.697368  0.094177  0.975622        1
    ...   2  0.761140  0.786064  0.128114        1
    ...   3  0.450386  0.370798  0.926765        0
    ...   4  0.643865  0.822762  0.443414        0
    """
    # Check y's dimensionality
    if y is not None and len(y.shape) == 2:
        y = np.expand_dims(y, axis=-1)

    # Collect metadata
    shp = X.shape
    columns = [i for i in range(shp[-1])] if bands is None else bands
    indices = np.indices(shp[:-1]).reshape((2, shp[0] * shp[1]))
    indices = pd.MultiIndex.from_arrays(indices, names=["h", "w"])

    if y is None:
        dat = np.moveaxis(X, -1, 0).reshape((len(columns), shp[0] * shp[1]))
        df_image = pd.DataFrame(data=dat.T, columns=columns, index=indices)
    else:
        columns = columns + [target_feature]
        dat = np.moveaxis(np.append(X, y, axis=-1), -1, 0).reshape(
            (len(columns), shp[0] * shp[1])
        )
        df_image = pd.DataFrame(data=dat.T, columns=columns, index=indices)
        df_image[target_feature] = df_image[target_feature].astype(int)

    return df_image

def dataframe_to_image(df, bands=None, target_feature=None):
    """
    Converts a pandas dataframe to an image. The height ("h"), and width ("w")
    coordinates of the image must be in the index.

    Arguments
    ---------
    df : pd.DataFrame
        Dataframe with pixel coordinates (h, w) as index, counting from the top left
        corner.

    bands : array-like of shape (b,), default=None
        The names of the bands in the dataframe to be passed to the image.

    target_feature : str, default=None
        Target feature name.

    Returns
    -------
    X : array-like of shape (h, w, b)
        Matrix containing the image data.

    y : array-like of shape (h, w), default=None
        The target values (class labels) as integers or strings.

    bands : array-like of shape (b,), default=None
        The names of the bands in the image.
    """

    if (np.sort(df.index.names) != ["h", "w"]).any():
        err_msg = (
            'Image coordinates with names ["h", "w"] must be in index,'
            f" got {list(df.index.names)} instead."
        )
        raise IndexError(err_msg)

    df_ = df.reset_index().pivot(index="h", columns="w")
    y = df_[target_feature].values if target_feature is not None else None

    if bands is None:
        bands = (
            df.columns if target_feature is None else df.columns.drop(target_feature)
        )

    X = np.array([df_[band] for band in bands])
    X = np.moveaxis(X, 0, -1)

    return X, y, bands


from PyQt5.QtWidgets import (QVBoxLayout, QHBoxLayout, QAction, QMessageBox,
                             QMainWindow, QWidget, QScrollArea, QSplitter)
from PyQt5.QtCore import Qt
from .ConsoleView import ConsoleView
from .SignalButton import SignalButton
from .SignalView import SignalView

class MainWindow(QMainWindow):
    def __init__(self, signalNames):
        super(MainWindow, self).__init__()

        widget = QWidget()
        self.setCentralWidget(widget)

        self.consoleView = ConsoleView()
        self.signalView = SignalView()
        buttonsWidget = QWidget()

        scroll = QScrollArea()
        scroll.setWidget(self.signalView)
        scroll.setWidgetResizable(True)

        hboxSignalButtons = QHBoxLayout()
        self.signalButtons = self.createSignalButtons(signalNames,
                                                      hboxSignalButtons)
        buttonsWidget.setLayout(hboxSignalButtons)

        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(scroll)
        splitter.addWidget(self.consoleView)
        vbox = QVBoxLayout()
        vbox.addWidget(buttonsWidget)
        vbox.addWidget(splitter)

        self.createActions()
        self.createMenu()

        widget.setLayout(vbox)
        self.setWindowTitle("kit")

    def createSignalButtons(self, signalNames, buttonsPanel):
        buttons = []
        for name in signalNames:
            button = SignalButton(name)
            buttonsPanel.addWidget(button)
            buttons.append(button)
        return buttons

    def signalViewConnect(self, controller):
        self.signalView.connect(controller.model,
                                controller.button)

    def createActions(self):
        self.aboutAct = QAction("&About", self,
                                statusTip="learn what it is all about",
                                triggered=self.about)

    def createMenu(self):
        helpMenu = self.menuBar().addMenu("&Help")
        helpMenu.addAction(self.aboutAct)

    def about(self):
        QMessageBox.about(self, "About", "dummy about message")