import pygame
from random import randint
from datetime import datetime


OBJ_OVER = "GameOver"
OBJ_BALL = "Ball"
OBJ_PADDLE = "Paddle"
OBJ_COUNTER = "Counter"
OBJ_HIGHSCORE = "Highscore"

###############################################################################

class GameObject():
    """
    Oberklasse für alle Objekte im Spiel
    Jedes Objekt sollte sich selbst aktualisieren und darstellen können
    """
    def update(self, game_objects):
        pass

    def draw(self, window):
        pass

    def signal_is_over(self, game_objects):
        pass

###############################################################################

class Window(GameObject):
    """
    Das eigentliche Fenster für das Spiel
    Stellt den schwarzen Hintergrund dar und wird daher als erstes gezeichnet
    Steuert auch die Spielgeschwindigkeit
    """
    def __init__(self):
        self.width = 640
        self.height = 480
        self.title = "Pong"

        pygame.init()
        pygame.display.set_caption(self.title)
        self.size = (self.width, self.height)
        self.screen = pygame.display.set_mode(self.size)
        self.clock = pygame.time.Clock()

    def update(self, game_objects):
        self.clock.tick(50)

    def draw(self, window=None):
        black = (0, 0, 0)
        self.screen.fill(black)

###############################################################################

class GameOverLabel(GameObject):
    """
    Boolsches "Objekt", das festhält, ob das Spiel verloren wurde
    Stellt in diesem Fall die Game Over Schrift bereit
    """
    def __init__(self, window):
        font = pygame.font.Font(None, 36)
        self.image = font.render('Game Over', 1, (255, 255, 255))
        self.rect = self.image.get_rect(centerx=window.width // 2,
                                        centery=window.height // 2)
        self.is_over = False

    def signal_is_over(self, game_objects):
        self.is_over = True

    def draw(self, window):
        if self.is_over:
            window.screen.blit(self.image, self.rect)

###############################################################################

class Counter(GameObject):
    """
    Zähler
    """
    def __init__(self, window):
        self.window = window
        self.counter = 0

    def increment(self):
        self.counter += 10

    def draw(self, window):
        font = pygame.font.Font(None, 36)
        self.image = font.render(f'{self.counter}', 1, (255, 255, 255))
        self.rect = self.image.get_rect(right=window.width-10 , top=10)
        window.screen.blit(self.image, self.rect)


###############################################################################

class Highscore(GameObject):
    """
    Verwaltung der Highscore-Liste
    """
    def __init__(self, window):
        self.window = window
        self.visible = False
        self.font = pygame.font.Font(None, 24)
        self.scores = {}
        self.last_score = None

        ##Falls es noch keine Highscore.txt gibt##
        # try:
        #     with open("A9/highscores.txt", "r") as file:
        #         for line in file:
        #             parts = line.split("#")
        #             score = int(parts[0])
        #             time = parts[1]
        #             self.scores[score] = time
        # except:
        #     pass

        with open("A9/highscores.txt", "r") as file:
            for line in file:
                parts = line.split("#")
                score = int(parts[0])
                time = parts[1]
                self.scores[score] = time



    def signal_is_over(self, game_objects):
        self.last_score = game_objects[OBJ_COUNTER].counter
        self.scores[self.last_score] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        with open("A9/highscores.txt", "w") as file:
            sorted_scores = sorted(self.scores.keys())
            sorted_scores.reverse()

            for score in sorted_scores[:5]:
                time = self.scores[score]
                file.write(f"{score}#{time}#\n")

        self.visible = True

    def draw(self, window):
        if self.visible:
            sorted_scores = sorted(self.scores.keys())
            sorted_scores.reverse()
            line_no = 1
            for index, key in enumerate(sorted_scores):
                if index < 4 or key == self.last_score:
                    color = (255, 0, 0) if key == self.last_score else (255, 255, 255)
                    image = self.font.render(f'{key} Punkte am {self.scores[key]}', 1, color)
                    rect = image.get_rect(centerx=window.width // 2, top=window.height // 2 + line_no * 30)
                    line_no += 1
                    window.screen.blit(image, rect)


###############################################################################

class BouncingBall(GameObject):
    """
    Das wandernde Ohm-Zeichen
    Abprallen wird im Rahmen der Aktualisierung berechnet
    Darstellung nur, wenn nicht gerade "Game Over angezeigt wird"
    """
    def __init__(self, window):
        self.window = window
        self.image = pygame.image.load('A9/ohm.png')
        self.rect = self.image.get_rect()
        self.rect.left = window.width // 2 - self.rect.width // 2
        self.rect.top = window.height // 2 - self.rect.height // 2
        maxspeed = 10
        self.dx = randint(2, maxspeed)
        self.dy = randint(2, maxspeed)

    def update(self, game_objects):
        self.is_visible = not game_objects[OBJ_OVER].is_over
        if self.is_visible:
            self.rect.left += self.dx
            self.rect.top += self.dy
            self.bounce(game_objects)

    def bounce(self, game_objects):
        if self.rect.left < 0 or self.rect.right > self.window.width:
            self.dx *= -1
        has_hit = self.hit(game_objects[OBJ_PADDLE])
        if has_hit:
            game_objects[OBJ_COUNTER].increment()
        if self.rect.top < 0 or has_hit:
            self.dy *= -1
        if self.rect.bottom > self.window.height:
            if not game_objects[OBJ_OVER].is_over:
                for obj in game_objects.values():
                    obj.signal_is_over(game_objects)

    def hit(self, paddle):
        if self.dy < 0:
            return False
        return paddle.rect.colliderect(self.rect)

    def draw(self, window):
        if self.is_visible:
            window.screen.blit(self.image, self.rect)

###############################################################################

class Paddle(GameObject):
    """
    Der Schläger
    Darstellung nur, wenn nicht gerade "Game Over angezeigt wird"
    """
    def __init__(self, window):
        self.width = 100
        self.height = 10
        x = window.width // 2 - self.width // 2
        y = window.height - 2 * self.height
        self.rect = pygame.Rect(x, y, self.width, self.height)
        self.color =  (255, 0, 0),  # rot
        self.speed = 10
        self.dx = 0
        self.dy = 0

    def update(self, game_objects):
        self.is_visible = not game_objects[OBJ_OVER].is_over
        if self.is_visible:
            self.rect.left += self.dx
            self.rect.top += self.dy

    def draw(self, window):
        if self.is_visible:
            pygame.draw.rect(window.screen, self.color, self.rect)

###############################################################################

def main():
    """
    Main-Funktion mit Game-Loop
    Hostet die beiden zentralen Variablen "window" und "game_objects"
    """
    window = Window()
    game_objects = dict()
    init(window, game_objects)

    running = True

    while running:
        running = user_input(window, game_objects)
        update(window, game_objects)
        draw(window, game_objects)


def init(window, game_objects):
    """
    Anlegen aller Objekte des Spiels
    """
    game_objects[OBJ_OVER] = GameOverLabel(window)
    game_objects[OBJ_BALL] = BouncingBall(window)
    game_objects[OBJ_PADDLE] = Paddle(window)
    game_objects[OBJ_COUNTER] = Counter(window)
    game_objects[OBJ_HIGHSCORE] = Highscore(window)


def user_input(window, game_objects):
    """
    Abarbeiten aller Events
    :return: False, falls das Spiel beendet wird, sonst Truepong-oo.py
    """

    def process_event():
        """
        Verarbeitung eines Events
        """
        if event.type == pygame.QUIT:
            return False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                return False
            if event.key == pygame.K_SPACE and game_objects[OBJ_OVER].is_over:
                init(window, game_objects)
            if event.key == pygame.K_LEFT:
                game_objects[OBJ_PADDLE].dx -= game_objects[OBJ_PADDLE].speed
            if event.key == pygame.K_RIGHT:
                game_objects[OBJ_PADDLE].dx += game_objects[OBJ_PADDLE].speed
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT:
                game_objects[OBJ_PADDLE].dx += game_objects[OBJ_PADDLE].speed
            if event.key == pygame.K_RIGHT:
                game_objects[OBJ_PADDLE].dx -= game_objects[OBJ_PADDLE].speed
        return True

    result = True
    for event in pygame.event.get():
        result = result and process_event()
    return result


def update(window, game_objects):
    """
    Aktualisierung aller Objekt
    """
    window.update(game_objects)
    for o in game_objects.values():
        o.update(game_objects)


def draw(window, game_objects):
    """
    Zeichnen aller Objekte
    """
    window.draw()
    for o in game_objects.values():
        o.draw(window)
    pygame.display.flip()


if __name__ == "__main__":
    main()
