import board
import neopixel
import digitalio
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.raw_text_packet import RawTextPacket
import time
import random

# Setup BLE connection
ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)

advertisement.complete_name = "ACF-r"
ble.name = advertisement.complete_name
print("Running Receiver Code!")

# NeoPixel setup
pixel = neopixel.NeoPixel(board.A1, n=256, brightness=0.3, auto_write=False)

# Initialize car position in the center lane
car_position = [8, 15]  # Starting position on a 16x16 grid

# Initialize obstacle properties
obstacle_speed = 0.5  # Speed for obstacles
last_obstacle_update = time.monotonic()  # Track last update time
obstacle_interval = 2  # Time interval in seconds to add new obstacles
last_obstacle_spawn = time.monotonic()

# Define list of obstacles, confined to the road
obstacles = [{"x": random.randint(3, 12), "y": 0, "width": random.randint(1, 3), "original_x": random.randint(3, 12)}]

# Finish line properties
finish_line_y = -1  # Start off-screen
finish_line_trigger_time = 15  # Time in seconds to trigger finish line (increased wait time)
last_obstacle_hit_time = time.monotonic()  # Last time an obstacle was hit

# Function to reset an obstacle within the "road"
def reset_obstacle(obstacle):
    obstacle["y"] = 0  # Reset to the top
    obstacle["x"] = random.randint(3, 12)  # Restrict x position within the "road" area
    obstacle["original_x"] = obstacle["x"]  # Set starting x position for mirroring
    obstacle["width"] = random.randint(1, 3)  # Random width between 1 and 3 pixels

# Function to reset the game
def reset_game():
    global car_position, obstacles, last_obstacle_hit_time, finish_line_y
    car_position = [8, 15]  # Reset car position
    obstacles = [{"x": random.randint(3, 12), "y": 0, "width": random.randint(1, 3), "original_x": random.randint(3, 12)}]
    last_obstacle_hit_time = time.monotonic()  # Reset the timer
    finish_line_y = -1  # Reset finish line off-screen

while True:
    ble.start_advertising(advertisement)  # Start advertising.
    print(f"Advertising as: {advertisement.complete_name}")
    was_connected = False

    while not was_connected or ble.connected:
        if ble.connected:  # If BLE is connected...
            was_connected = True

            # Check for new data from the SENDER.
            if uart.in_waiting:
                try:
                    packet = Packet.from_stream(uart)  # Create the packet object.
                    print(f"packet: {packet}")
                except ValueError:
                    continue

                if isinstance(packet, RawTextPacket):  # If the packet is a RawTextPacket
                    message = packet.text.decode().strip()
                    print(f"Message Received: {message}")

                    # Parse the incoming message for accelerometer values
                    try:
                        values = message.split(',')
                        x_accel = float(values[0])
                        print(f"Accelerometer values: X: {x_accel}")

                        # Move the car based on accelerometer values, restricted within the "road"
                        if x_accel < -2.5 and car_position[0] < 12:  # Tilted left
                            car_position[0] += 1  # Move right
                        elif x_accel > 2.5 and car_position[0] > 3:  # Tilted right
                            car_position[0] -= 1  # Move left

                        print(f"Car position: X: {car_position[0]}, Y: {car_position[1]}")
                    except Exception as e:
                        print(f"Error parsing accelerometer data: {e}")

            # Update obstacle and finish line positions
            current_time = time.monotonic()
            if current_time - last_obstacle_update >= obstacle_speed:
                # Move finish line down if it has started falling
                if finish_line_y >= 0 and finish_line_y < 15:
                    finish_line_y += 1  # Move finish line down by 1 row

                for obstacle in obstacles:
                    # Mirror the obstacle x-position based on the row number (y-value)
                    if obstacle["y"] % 2 == 0:
                        obstacle["x"] = obstacle["original_x"]  # Even row, use original x
                    else:
                        obstacle["x"] = 15 - obstacle["original_x"]  # Odd row, mirror x

                    obstacle["y"] += 1  # Move down one row

                    # Check if any part of the obstacle line hit the car
                    for i in range(obstacle["width"]):
                        if obstacle["x"] + i == car_position[0] and obstacle["y"] == car_position[1]:
                            print("Game Over!")
                            reset_game()  # Automatically restart the game
                            break

                    # If the obstacle reaches the bottom, reset it to the top with a new random x position
                    if obstacle["y"] > 15:
                        reset_obstacle(obstacle)

                last_obstacle_update = current_time

            # Spawn new obstacles at intervals within the "road"
            if current_time - last_obstacle_spawn >= obstacle_interval:
                obstacles.append({
                    "x": random.randint(3, 12),
                    "y": 0,
                    "width": random.randint(1, 3),  # Random width between 1 and 3 pixels
                    "original_x": random.randint(3, 12)
                })
                last_obstacle_spawn = current_time

            # Check for finish line condition
            if current_time - last_obstacle_hit_time >= finish_line_trigger_time and finish_line_y == -1:
                finish_line_y = 0  # Start falling only once
                print("Finish line started falling")  # Debug statement

            # Display the car, road lines, obstacles, and finish line on the LED matrix
            pixel.fill((0, 0, 0))  # Clear all pixels

            # Draw road lines
            for y in range(16):
                pixel[y * 16 + 2] = (128, 128, 128)  # Left road line in gray
                pixel[y * 16 + 13] = (128, 128, 128)  # Right road line in gray

            # Convert car position to 1D index and set color
            car_index = car_position[1] * 16 + car_position[0]
            pixel[car_index] = (0, 255, 0)  # Car in green

            # Set obstacles on the matrix as horizontal lines within the "road"
            for obstacle in obstacles:
                for i in range(obstacle["width"]):
                    if 3 <= obstacle["x"] + i <= 12 and 0 <= obstacle["y"] < 16:
                        obstacle_index = obstacle["y"] * 16 + (obstacle["x"] + i)
                        pixel[obstacle_index] = (255, 0, 0)  # Obstacles in red

            # Draw the finish line if it is falling
            if finish_line_y >= 0 and finish_line_y < 16:
                for x in range(3, 13):  # Draw the finish line across the road
                    pixel[int(finish_line_y) * 16 + x] = (255, 255, 0)  # Finish line in yellow
                print(f"Finish line at y: {finish_line_y}")  # Debug statement

                # Check for collision with finish line
                if finish_line_y == car_position[1] and car_position[0] in range(3, 13):
                    print("Game Over! Hit the Finish Line!")
                    reset_game()  # Automatically restart the game on finish line hit

            pixel.show()  # Update the LED matrix

