"""
PingPong.py - A simple ping-pong game for the Raspberry Pi Pico
Author: Arijit Sengupta
"""

from machine import Pin, I2C
import time
from Buzzer import *
from Displays import *
from LightStrip import *
from Button import *
from Log import *

class Player:
    """
    Represents a player in the ping-pong game
    """
    def __init__(self, name, button_pin, handler):
        """
        Initialize the player with a name and button
        """
        self.name = name
        self.button = Button(button_pin, name, handler=handler)
        self.score = 0

    def increment_score(self):
        """
        Increment the player's score
        """
        self.score += 1

class Ball:
    """
    Represents the ball in the ping-pong game
    """
    def __init__(self, lightstrip, start_position=0, start_direction=0):
        """
        Initialize the ball with the lightstrip and initial position and direction
        """
        self.lightstrip = lightstrip
        self.position = start_position
        self.direction = start_direction # 0 for stopped, 1 for right, -1 for left
        self.color = RED # default to player 1 color
        
        
    def move(self):
        """
        Move the ball one step in the current direction,
        but only if it doesn't result in going out of bounds
        """
        if self.direction != 0:
            new_position = self.position + self.direction

            # Check for out-of-bounds
            if 0 <= new_position <= 7:
                self.lightstrip.setPixel(self.position, BLACK, show=False)
                self.position = new_position
                self.lightstrip.setPixel(self.position, self.color, show=False)
                self.lightstrip.show()

    def set_color(self, color):
        """
        Set the color of the ball
        """
        self.color = color
        self.lightstrip.setPixel(self.position, self.color)

    def reset(self, position, direction, color):
        """
        Reset the ball to a specific position, direction and color
        """
        self.lightstrip.off()
        self.position = position
        self.direction = direction
        self.color = color
        self.lightstrip.setPixel(self.position, self.color)
        

class Game:
    """
    Represents the ping-pong game logic
    """
    def __init__(self, player1, player2, ball, buzzer, display):
        """
        Initialize the game with players, ball, buzzer, and display
        """
        self.player1 = player1
        self.player2 = player2
        self.ball = ball
        self.buzzer = buzzer
        self.display = display
        self.game_over = False
        self.last_hit_by = 1 # which player last hit the ball
        self.serving = 1 # which player is serving

    def check_for_hit(self):
        """
        Check if the ball has reached an end and should be considered hit or miss
        """
        if self.ball.position == 0 or self.ball.position == 7:
            self.buzzer.beep(tones['C5'], 50)  # Ball reaches end

    def handle_player_action(self, player):
        """
        Handle a player's button press
        """

        if self.game_over:
            return

        if player == self.player1:
            if self.ball.position == 0 and self.ball.direction <= 0 and self.serving == 1:  # Serve
                self.ball.direction = 1
                self.ball.set_color(BLUE)
                self.last_hit_by = 1
                self.serving = 2  # switch serve to the other player
                self.display.clear(1)
                self.buzzer.beep(MI, 50)
            elif self.ball.position == 0 and self.ball.direction == 1:  # Miss
                self.buzzer.beep(tones['G5'], 100)
                self.player2.increment_score()
                self.ball.direction = -1  # Reverse for miss
                self.ball.set_color(RED)
                self.last_hit_by = 2
                self.serving = 2
                if self.check_game_over():
                    return
                
                # Actually move the ball for a miss
                while self.ball.position > 0:
                    self.ball.move()
                    time.sleep(0.3)  # Adjust speed of ball movement after miss if needed
                
                time.sleep(1)
                self.reset_ball()
            elif self.ball.position != 0:  # Foul
                self.buzzer.beep(tones['G5'], 100)
                self.player2.increment_score()
                self.ball.direction = -1  # Move to player 2 for foul
                self.ball.set_color(RED)
                self.last_hit_by = 2
                self.serving = 2
                if self.check_game_over():
                    return
                
                # Actually move the ball for a foul
                while self.ball.position > 0:
                    self.ball.move()
                    time.sleep(0.3)  # Adjust speed of ball movement after foul if needed

                time.sleep(1)
                self.reset_ball()

        elif player == self.player2:
            if self.ball.position == 7 and self.ball.direction >= 0 and self.serving == 2:  # Serve
                self.ball.direction = -1
                self.ball.set_color(RED)
                self.last_hit_by = 2
                self.serving = 1  # switch serve to player 1
                self.display.clear(1)
                self.buzzer.beep(MI, 50)
            elif self.ball.position == 7 and self.ball.direction == -1:  # Miss
                self.buzzer.beep(tones['G5'], 100)
                self.player1.increment_score()
                self.ball.direction = 1  # Reverse for miss
                self.ball.set_color(BLUE)
                self.last_hit_by = 1
                self.serving = 1
                if self.check_game_over():
                    return
                
                # Actually move the ball for a miss
                while self.ball.position < 7:
                    self.ball.move()
                    time.sleep(0.3) # Adjust speed of ball movement after miss if needed

                time.sleep(1)
                self.reset_ball()
            elif self.ball.position != 7:  # Foul
                self.buzzer.beep(tones['G5'], 100)
                self.player1.increment_score()
                self.ball.direction = 1  # Move to player 1 for foul
                self.ball.set_color(BLUE)
                self.last_hit_by = 1
                self.serving = 1
                if self.check_game_over():
                    return

                # Actually move the ball for a foul
                while self.ball.position < 7:
                    self.ball.move()
                    time.sleep(0.3) # Adjust speed of ball movement after foul if needed
                
                time.sleep(1)
                self.reset_ball()

    def reset_ball(self):
        """
        Reset the ball to the starting position
        """

        self.ball.reset(0 if self.serving == 1 else 7, 0, RED if self.serving == 1 else BLUE)
        self.display.showText("Press to serve", 1, 0)

    def check_game_over(self):
        """
        Check if the game is over and display the winner
        """
        if self.player1.score >= 10 or self.player2.score >= 10:
            self.game_over = True
            winner = self.player1.name if self.player1.score >= 10 else self.player2.name
            self.display.clear()
            self.display.showText(f"{winner} Wins!", 0, 0)
            self.ball.lightstrip.off()
            for _ in range(3):
                self.buzzer.beep(DO2, 100)
                time.sleep(0.2)
            return True
        return False

class GameController:
    """
    Coordinates the game, handling hardware and game logic
    """

    def __init__(self, button1_pin, button2_pin, neopixel_pin, buzzer_pin, lcd_sda, lcd_scl):
        """
        Initialize the game controller
        """
        Log.name = 'PingPong'
        Log.level = ALL

        self.display = LCDDisplay(sda=lcd_sda, scl=lcd_scl)
        self.lightstrip = LightStrip(pin=neopixel_pin, numleds=8)
        self.buzzer = PassiveBuzzer(pin=buzzer_pin)

        self.player1 = Player("Player 1", button1_pin, self)
        self.player2 = Player("Player 2", button2_pin, self)
        self.ball = Ball(self.lightstrip)
        self.game = Game(self.player1, self.player2, self.ball, self.buzzer, self.display)

        # Display initial score
        self.display_score()
        self.game.reset_ball()
        self.buzzer.beep(DO, 200)

    def display_score(self):
        """
        Display the current score on the LCD
        """
        self.display.showNumbers(self.game.player1.score, self.game.player2.score, row=0, col=0)

    def buttonPressed(self, button_name):
        """
        Handle button presses, delegating to the game logic
        """
        if button_name == "Player 1":
            self.game.handle_player_action(self.game.player1)
        elif button_name == "Player 2":
            self.game.handle_player_action(self.game.player2)
        self.display_score()

    def buttonReleased(self, button_name):
        """
        Handle button releases (currently not used)
        """
        pass

    def run(self):
        """
        Main game loop
        """
        try:
            while not self.game.game_over:
                self.ball.move()
                self.game.check_for_hit()
                time.sleep(0.3)  # Adjust ball speed here
        except Exception as e:
            Log.e(f'Exception: {e}')
            self.display.clear()
            self.display.showText("Error", 0, 0)
            self.display.showText(f'{e}', 1, 0)
            self.lightstrip.off()
        finally:
            self.display.clear()
            self.display.showText("Game Over", 0, 0)
            self.lightstrip.off()
            self.buzzer.stop()

# Initialize and run the game
controller = GameController(button1_pin=18, button2_pin=17, neopixel_pin=2, buzzer_pin=16, lcd_sda=0, lcd_scl=1)
controller.run()
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT