import time
import board
import terminalio
import random
import math
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import Advertisement
from adafruit_display_text import label
from adafruit_matrixportal.matrixportal import MatrixPortal
import displayio
from digitalio import DigitalInOut, Direction, Pull


class ChristmasPresenceDisplay:
    def __init__(self):
        # Colors need to be defined first since they're used in setup_display_layers
        self.COLORS = {
            'GREEN': 0x00FF00,
            'BROWN': 0x964B00,
            'BLACK': 0x000000,
            'RED': 0xFF0000,
            'BLUE': 0x0000FF,
            'YELLOW': 0xFFFF00,
            'PURPLE': 0xFF00FF,
            'GOLD': 0xFFD700,
            'WHITE': 0xFFFFFF
        }

        # Add pause state tracking
        self.paused = False
        self.pause_time = None
        self.total_pause_time = 0

        # Display setup
        self.matrixportal = MatrixPortal(status_neopixel=board.NEOPIXEL, debug=True, rotation=270)

        # Initialize BLE
        self.ble = BLERadio()
        self.TARGET_NAME = "WearableFeather"
        self.RSSI_THRESHOLD = -80

        # Time tracking
        self.start_time = None
        self.disconnected_time = None
        self.elapsed_time = 0

        # Initialize buttons with debouncing
        self.button_up = DigitalInOut(board.BUTTON_UP)
        self.button_up.direction = Direction.INPUT
        self.button_up.pull = Pull.UP

        self.button_down = DigitalInOut(board.BUTTON_DOWN)
        self.button_down.direction = Direction.INPUT
        self.button_down.pull = Pull.UP

        # Button debouncing state
        self.last_button_states = {'up': True, 'down': True}
        self.last_button_times = {'up': 0, 'down': 0}
        self.debounce_time = 0.2

        # Display groups setup
        self.main_group = displayio.Group()

        # Animation state
        self.current_state = "WAITING"
        self.animation_states = {
            "WAITING": 0,
            "GROWING": 1,
            "DECORATING": 2,
            "STAR": 3,
            "SNOWING": 4
        }

        # Time settings
        self.time_setting = 0  # 0: short, 1: medium, 2: long
        self.time_settings = {
            0: {  # Short
                "GROWING": 30,
                "DECORATING": 60,
                "STAR": 90,
                "SNOWING": float('inf')
            },
            1: {  # Medium
                "GROWING": 60,
                "DECORATING": 120,
                "STAR": 300,
                "SNOWING": float('inf')
            },
            2: {  # Long
                "GROWING": 300,
                "DECORATING": 600,
                "STAR": 900,
                "SNOWING": float('inf')
            }
        }

        # Animation progress tracking
        self.ornaments_added = 0
        self.star_added = False
        self.snowflakes = []
        self.max_snowflakes = 10

        # Setup display layers
        self.setup_display_layers()

    def setup_display_layers(self):
        # Timer text setup
        self.timer_text = label.Label(
            terminalio.FONT,
            text="00:00",
            color=0xFC6900
        )
        self.timer_text.x = 2
        self.timer_text.y = 5

        # Create bitmap for tree animations
        self.tree_bitmap = displayio.Bitmap(32, 64, 9)
        self.tree_palette = displayio.Palette(9)
        self.setup_palette()

        # Create tile grid for tree
        self.tree_tile_grid = displayio.TileGrid(
            self.tree_bitmap,
            pixel_shader=self.tree_palette
        )

        # Add layers to main group
        self.main_group.append(self.tree_tile_grid)
        self.main_group.append(self.timer_text)

        # Set as root group
        self.matrixportal.display.root_group = self.main_group

    def setup_palette(self):
        # Set up color palette for tree
        colors = [
            self.COLORS['BLACK'],  # 0
            self.COLORS['GREEN'],  # 1
            self.COLORS['BROWN'],  # 2
            self.COLORS['RED'],  # 3
            self.COLORS['BLUE'],  # 4
            self.COLORS['YELLOW'],  # 5
            self.COLORS['PURPLE'],  # 6
            self.COLORS['GOLD'],  # 7
            self.COLORS['WHITE']  # 8
        ]
        for i, color in enumerate(colors):
            self.tree_palette[i] = color

    def print_current_timings(self):
        """Print the current time dependency settings"""
        setting_name = ['Short', 'Medium', 'Long'][self.time_setting]
        current_settings = self.time_settings[self.time_setting]

        print(f"\nCurrent Time Setting: {setting_name}")
        print("Time Dependencies:")
        print(f"Growing: 0-{current_settings['GROWING']} seconds")
        print(f"Decorating: {current_settings['GROWING']}-{current_settings['DECORATING']} seconds")
        print(f"Star: {current_settings['DECORATING']}-{current_settings['STAR']} seconds")
        print(f"Snowing: {current_settings['STAR']}+ seconds\n")

    def setup_display_layers(self):
        # Timer text setup (existing)
        self.timer_text = label.Label(
            terminalio.FONT,
            text="00:00",
            color=0xFC6900
        )
        self.timer_text.x = 2
        self.timer_text.y = 5

        # Add mode text label
        self.mode_text = label.Label(
            terminalio.FONT,
            text="",  # Start empty
            color=0xFFFFFF
        )
        self.mode_text.x = 2
        self.mode_text.y = 15  # Position just under timer

        # Rest of setup_display_layers remains the same
        self.tree_bitmap = displayio.Bitmap(32, 64, 9)
        self.tree_palette = displayio.Palette(9)
        self.setup_palette()

        self.tree_tile_grid = displayio.TileGrid(
            self.tree_bitmap,
            pixel_shader=self.tree_palette
        )

        # Add both text layers and tree to main group
        self.main_group.append(self.tree_tile_grid)
        self.main_group.append(self.timer_text)
        self.main_group.append(self.mode_text)

        self.matrixportal.display.root_group = self.main_group

    def clear_display(self):
        """Clear the entire display"""
        for x in range(32):
            for y in range(64):
                self.tree_bitmap[x, y] = 0

    def show_mode_text(self, mode):
        """Show mode text briefly then clear it"""
        self.mode_text.text = mode
        time.sleep(1)  # Show for 1 second
        self.mode_text.text = ""  # Clear text

    def check_button(self):
        """Check if buttons are pressed and handle states with debouncing"""
        current_time = time.monotonic()
        up_button_state = self.button_up.value
        down_button_state = self.button_down.value

        # Handle UP button (pause) with debouncing
        if (up_button_state != self.last_button_states['up'] and
                current_time - self.last_button_times['up'] > self.debounce_time):

            if not up_button_state:  # Button is pressed
                if not self.paused:
                    self.paused = True
                    self.pause_time = current_time
                    self.timer_text.color = 0xFF0000
                    print("Animation Paused")
                else:
                    self.paused = False
                    if self.pause_time:
                        self.total_pause_time += current_time - self.pause_time
                    self.pause_time = None
                    self.timer_text.color = 0xFC6900
                    print("Animation Resumed")

            self.last_button_states['up'] = up_button_state
            self.last_button_times['up'] = current_time

        # Handle DOWN button (time settings) with debouncing
        if (down_button_state != self.last_button_states['down'] and
                current_time - self.last_button_times['down'] > self.debounce_time):

            if not down_button_state:  # Button is pressed
                # Complete reset of timing variables
                self.start_time = None
                self.elapsed_time = 0
                self.total_pause_time = 0
                self.pause_time = None
                self.paused = False
                self.timer_text.text = "00:00"

                # Clear display
                self.clear_display()

                # Cycle through time settings
                self.time_setting = (self.time_setting + 1) % 3
                mode_names = ["Short", "Medium", "Long"]

                # Visual feedback for setting change
                original_color = self.timer_text.color
                self.timer_text.color = [0x00FF00, 0xFFFF00, 0xFF0000][self.time_setting]
                self.show_mode_text(mode_names[self.time_setting])
                time.sleep(0.2)
                self.timer_text.color = original_color

                # Reset animation states
                self.current_state = "WAITING"
                self.ornaments_added = 0
                self.star_added = False
                self.snowflakes = []

                # Print current timing settings
                self.print_current_timings()

            self.last_button_states['down'] = down_button_state
            self.last_button_times['down'] = current_time

    def check_bluetooth_presence(self):
        """Check for BLE device presence and update timing"""
        print("Scanning for Wearable Feather...")
        for advertisement in self.ble.start_scan(Advertisement, timeout=2.0):
            if advertisement.complete_name == self.TARGET_NAME:
                rssi = advertisement.rssi
                print(f"Detected {self.TARGET_NAME} with RSSI {rssi} dBm")

                if rssi > self.RSSI_THRESHOLD:
                    if self.start_time is None:
                        self.start_time = time.monotonic()
                        print("Device within range. Timer started.")
                    self.disconnected_time = None
                    break
        self.ble.stop_scan()

    def update_presence_tracking(self):
        """Update timing and handle disconnections"""
        if self.start_time is not None and not self.paused:
            current_time = time.monotonic()
            self.elapsed_time = max(0, current_time - self.start_time - self.total_pause_time)

            device_in_range = False
            for advertisement in self.ble.start_scan(Advertisement, timeout=2.0):
                if (advertisement.complete_name == self.TARGET_NAME and
                        advertisement.rssi > self.RSSI_THRESHOLD):
                    device_in_range = True
                    break
            self.ble.stop_scan()

            if not device_in_range:
                if self.disconnected_time is None:
                    self.disconnected_time = current_time
                elif current_time - self.disconnected_time > 10:
                    self.reset_timing()
            else:
                self.disconnected_time = None

    def reset_timing(self):
        """Reset all timing-related variables and animation states"""
        self.start_time = None
        self.disconnected_time = None
        self.elapsed_time = 0
        self.paused = False
        self.pause_time = None
        self.total_pause_time = 0

        # Clear the entire display
        for x in range(32):
            for y in range(64):
                self.tree_bitmap[x, y] = 0

        # Reset animation states
        self.current_state = "WAITING"
        self.ornaments_added = 0
        self.star_added = False
        self.snowflakes = []

        # Reset and redisplay timer
        self.timer_text.text = "00:00"

    def update_timer_display(self):
        """Update the timer display"""
        if self.start_time is not None:
            minutes = int(self.elapsed_time // 60)
            seconds = int(self.elapsed_time % 60)
            self.timer_text.text = f"{minutes:02}:{seconds:02}"
            print(f"Timer updated: {minutes:02}:{seconds:02}")  # Debug print

    def draw_stump(self):
        """Draw the tree stump"""
        for x in range(15, 18):
            for y in range(59, 63):
                self.tree_bitmap[x, y] = 2

    def draw_tree(self, height):
        """Draw the main tree shape"""
        # Clear the tree bitmap (except the stump)
        for x in range(32):
            for y in range(59):
                self.tree_bitmap[x, y] = 0

        tree_width = height * 2 - 1
        start_y = 59 - tree_width

        # Draw the tree leaves
        for i in range(height):
            for x in range(16 - i, 16 + i + 1):
                for y in range(start_y + i * 2, start_y + i * 2 + 2):
                    if 0 <= x < 32 and 0 <= y < 64:
                        self.tree_bitmap[x, y] = 1

    def get_tree_bounds(self, height):
        """Get valid coordinates within the tree shape"""
        valid_positions = []
        tree_width = height * 2 - 1
        start_y = 59 - tree_width

        for i in range(height):
            width_at_height = (i * 2) + 1
            y_pos = start_y + (i * 2)
            x_start = 16 - (width_at_height // 2)

            for x in range(x_start, x_start + width_at_height):
                if 0 <= x < 32 and 0 <= y_pos < 59:
                    valid_positions.append((x, y_pos))

        return valid_positions

    def draw_ornaments(self, num_ornaments, tree_height):
        """Draw ornaments with distribution"""
        valid_positions = self.get_tree_bounds(tree_height)
        if not valid_positions:
            return

        num_ornaments = min(num_ornaments, len(valid_positions))
        chosen_positions = []
        attempts = 0
        max_attempts = 100

        while len(chosen_positions) < num_ornaments and attempts < max_attempts:
            pos = random.choice(valid_positions)
            min_distance = 3
            is_valid = True

            for chosen_pos in chosen_positions:
                distance = math.sqrt((pos[0] - chosen_pos[0]) ** 2 +
                                     (pos[1] - chosen_pos[1]) ** 2)
                if distance < min_distance:
                    is_valid = False
                    break

            if is_valid:
                chosen_positions.append(pos)
                x, y = pos
                color = random.randint(3, 6)
                for dx in range(2):
                    for dy in range(2):
                        if 0 <= x + dx < 32 and 0 <= y + dy < 64:
                            self.tree_bitmap[x + dx, y + dy] = color

            attempts += 1

    def draw_star(self, x, y, color):
        """Draw star pattern"""
        star_pixels = [
            (0, 0),
            (0, -2), (0, -1),
            (1, -1), (2, 0),
            (1, 1),
            (-1, 1),
            (-2, 0), (-1, -1)
        ]

        for dx, dy in star_pixels:
            if 0 <= x + dx < 32 and 0 <= y + dy < 64:
                self.tree_bitmap[x + dx, y + dy] = color

    def clear_star(self, x, y):
        """Clear star pattern"""
        for dx in range(-2, 3):
            for dy in range(-2, 2):
                if 0 <= x + dx < 32 and 0 <= y + dy < 64:
                    self.tree_bitmap[x + dx, y + dy] = 0

    def animate_star(self):
        """Animate falling star"""
        tree_top = 0
        for y in range(64):
            for x in range(32):
                if self.tree_bitmap[x, y] == 1:
                    tree_top = y
                    break
            if tree_top > 0:
                break

        start_y = tree_top - 2
        star_x = 16

        self.draw_star(star_x, 5, 0)
        time.sleep(0.5)
        self.draw_star(star_x, 5, 7)
        time.sleep(0.5)

        current_y = 5
        while current_y < start_y:
            self.clear_star(star_x, current_y)
            current_y += 1
            self.draw_star(star_x, current_y, 7)
            time.sleep(0.1)

    def update_snow(self):
        """Update snow animation"""
        # Adjust these values to change snow behavior
        self.max_snowflakes = 15  # More snowflakes
        snow_chance = 0.5  # Higher chance of new snowflakes

        # Add new snowflakes
        if len(self.snowflakes) < self.max_snowflakes and random.random() < snow_chance:
            x = random.randint(0, 31)
            self.snowflakes.append([x, 0])

        # Clear old positions
        for x, y in self.snowflakes:
            if 0 <= x < 32 and 0 <= y < 64:
                if self.tree_bitmap[x, y] == 8:
                    self.tree_bitmap[x, y] = 0

        # Update positions
        new_snowflakes = []
        for x, y in self.snowflakes:
            new_y = y + 1
            new_x = x + random.choice([-1, 0, 1])

            if new_y < 64 and 0 <= new_x < 32 and self.tree_bitmap[new_x, new_y] == 0:
                new_snowflakes.append([new_x, new_y])

        self.snowflakes = new_snowflakes

        # Draw new positions
        for x, y in self.snowflakes:
            if 0 <= x < 32 and 0 <= y < 64 and self.tree_bitmap[x, y] == 0:
                self.tree_bitmap[x, y] = 8

    def determine_animation_state(self):
        """Determine animation state based on elapsed time and current time setting"""
        current_settings = self.time_settings[self.time_setting]

        if self.elapsed_time < current_settings["GROWING"]:
            return "GROWING"
        elif self.elapsed_time < current_settings["DECORATING"]:
            return "DECORATING"
        elif self.elapsed_time < current_settings["STAR"]:
            return "STAR"
        else:
            return "SNOWING"

    def handle_animation_state(self):
        """Handle current animation state and updates"""
        print(f"Current animation state: {self.current_state}")  # Debug print
        print(f"Elapsed time: {self.elapsed_time}")  # Debug print

        if self.current_state == "WAITING":
            print("In waiting state, clearing display")  # Debug print
            for x in range(32):
                for y in range(64):
                    self.tree_bitmap[x, y] = 0

        elif self.current_state == "GROWING":
            print("In growing state")  # Debug print
            current_settings = self.time_settings[self.time_setting]
            progress = min(self.elapsed_time / current_settings["GROWING"], 1)
            height = int(progress * 16) + 1
            print(f"Tree height: {height}")  # Debug print
            self.draw_stump()
            self.draw_tree(height)

        elif self.current_state == "DECORATING":
            if self.ornaments_added < 12:
                self.draw_ornaments(1, 16)
                self.ornaments_added += 1
                time.sleep(0.5)

        elif self.current_state == "STAR":
            if not self.star_added:
                self.animate_star()
                self.star_added = True

        elif self.current_state == "SNOWING":
            self.update_snow()

    def main_loop(self):
        """Main program loop"""
        self.draw_stump()

        # Print initial time settings
        self.print_current_timings()

        while True:
            # Check button state
            self.check_button()

            # Only update if not paused
            if not self.paused:
                self.check_bluetooth_presence()
                self.update_presence_tracking()
                self.update_timer_display()

                if self.start_time is not None:
                    new_state = self.determine_animation_state()
                    if new_state != self.current_state:
                        print(f"Transitioning to state: {new_state}")
                        self.current_state = new_state

                    self.handle_animation_state()

            time.sleep(0.1)

# Create and run the display controller
if __name__ == "__main__":
    display = ChristmasPresenceDisplay()
    display.main_loop()