from machine import I2C, PWM, Pin
import time
import random
from time import sleep
import i2c_lcd
from machine import SoftI2C
from i2c_lcd import I2cLcd

def scan(i2c):
    devices = i2c.scan()
    if len(devices) == 0:
        print("No i2c device !")
    else:
        print('i2c devices found:', len(devices))
    return devices


def try_LCD():
    sdaPIN = Pin(14)  # for ESP32
    sclPIN = Pin(15)

    i2c = SoftI2C(sda=sdaPIN, scl=sclPIN, freq=10000)
    devices = scan(i2c)
    if devices:
        display_id = scan(i2c)[0]

        totalRows = 2
        totalColumns = 16

        lcd = I2cLcd(i2c, display_id, totalRows, totalColumns)

        lcd.putstr("Light show!")
        sleep(2)
        lcd.clear()

        return lcd  # Return the created LCD object

# Get the LCD object from the function
lcd_object = try_LCD()  # This will call the try_LCD function only once and store the returned object

# if __name__ == '__main__':
#     try_LCD()

# Seed the random number generator (adjust as needed)
random.seed()  # Use current time as the seed (different each run)

# Define Standard LEDs
# Create an array of pins and corresponding colors
leds = [(28, "red"), (27, "green"), (26, "blue"), (22, "yellow"),
        (21, "orange"), (20, "white"), (19, "purple"), (18, "pink"),
        (17, "lightgreen"), (16, "cyan")]

# Define RGB LEDs pin connections using your dictionary
LED_PINS = [
    {"red": Pin(2, Pin.OUT), "green": Pin(1, Pin.OUT), "blue": Pin(0, Pin.OUT)},
    {"red": Pin(5, Pin.OUT), "green": Pin(4, Pin.OUT), "blue": Pin(3, Pin.OUT)},
    {"red": Pin(8, Pin.OUT), "green": Pin(7, Pin.OUT), "blue": Pin(6, Pin.OUT)},
    {"red": Pin(11, Pin.OUT), "green": Pin(10, Pin.OUT), "blue": Pin(9, Pin.OUT)},
]
# Define delays and randomization parameters (adjust as needed)
delay_ms = 1
base_delay = 0.2
shift_amount = 1
random_delay_range = 0.1

# Define a function to turn on/off a single LED
def led_control(pin, color, state):
    led = Pin(pin, Pin.OUT)
    led.value(1 if state == "on" else 0)  # Set value based on state
    time.sleep(0.2)  # Adjust delay as needed

# Set LED brightness using PWM control
def set_led_brightness(red_pin, green_pin, blue_pin, red_val, green_val, blue_val):
    red_pwm = PWM(Pin(red_pin))
    red_pwm.init(freq=1000)
    green_pwm = PWM(Pin(green_pin))
    green_pwm.init(freq=1000)
    blue_pwm = PWM(Pin(blue_pin))
    blue_pwm.init(freq=1000)

    red_pwm.duty_u16(red_val * 65535 // 255)
    green_pwm.duty_u16(green_val * 65535 // 255)
    blue_pwm.duty_u16(blue_val * 65535 // 255)

    time.sleep_ms(delay_ms)

    red_pwm.deinit()
    green_pwm.deinit()
    blue_pwm.deinit()
"""
   Functions for RGB LED simulations.
"""
# Fading simulation
def fading_simulation(duration):
    start_time = time.time()
    while time.time() - start_time < duration:
        # Red fading (increasing red, decreasing others for each LED)
        for led_dict in LED_PINS:
            red_pin = led_dict["red"]
            green_pin = led_dict["green"]
            blue_pin = led_dict["blue"]

            for red_val in range(0, 256):
                green_val = 255 - red_val
                blue_val = 0

                # Calling the set_led_brightness function
                set_led_brightness(red_pin, green_pin, blue_pin, red_val, green_val, blue_val)

        # Green fading (increasing green, decreasing others)
        for led_dict in LED_PINS:
            red_pin = led_dict["red"]
            green_pin = led_dict["green"]
            blue_pin = led_dict["blue"]

            for green_val in range(0, 256):
                red_val = 0
                blue_val = 255 - green_val

                # Calling the set_led_brightness function
                set_led_brightness(red_pin, green_pin, blue_pin, red_val, green_val, blue_val)

        # Blue fading (increasing blue, decreasing others)
        for led_dict in LED_PINS:
            red_pin = led_dict["red"]
            green_pin = led_dict["green"]
            blue_pin = led_dict["blue"]

            for blue_val in range(0, 256):
                red_val = 255 - blue_val
                green_val = 0

                # Calling the set_led_brightness function
                set_led_brightness(red_pin, green_pin, blue_pin, red_val, green_val, blue_val)
            time.sleep(base_delay)

# Fire Simulation
def fire_simulation():
    """
    Simulates multiple LEDs transitioning through a fire-like color spectrum,
    focusing on red, orange, yellow, and dark yellow, with smooth transitions
    across all LEDs, while also incorporating predefined target colors.
    """

    # Predefined color targets (replace with your desired sequence)
    color_targets = [
        (255, 0, 0),  # Red
        (255, 128, 0),  # Orange
        (255, 255, 0),  # Yellow
        (128, 64, 0),  # Dark Yellow
    ]

    current_color_index = 0  # Track the current color target index

    while True:
        # Choose the next color target from the predefined sequence
        target_red, target_green, target_blue = color_targets[current_color_index]
        step_size = 3
        red_targets = [target_red for _ in range(len(LED_PINS))]
        green_targets = [min(target_green + step_size, 255) for _ in range(len(LED_PINS))]
        blue_targets = [target_blue for _ in range(len(LED_PINS))]

        # Define transition step size
        step_size = 1

        # Transition loop
        while True:
            # Update all LEDs simultaneously
            for i, led_dict in enumerate(LED_PINS):
                red_pin = led_dict["red"]
                green_pin = led_dict["green"]
                blue_pin = led_dict["blue"]

                # Calculate color values based on target and index, clamp within range
                red_val = max(0, min(255, red_targets[i] - step_size))
                green_val = max(0, min(255, green_targets[i]))  # Clamp green_val to 255
                blue_val = max(0, blue_targets[i])

                # Set LED brightness with smooth transition using a small intermediate step
                intermediate_red = (red_val + red_targets[i]) // 2
                intermediate_green = (green_val + green_targets[i]) // 2
                intermediate_blue = (blue_val + blue_targets[i]) // 2

                # Set intermediate color first
                set_led_brightness(red_pin, green_pin, blue_pin, intermediate_red, intermediate_green, intermediate_blue)
                time.sleep(0.2)  # Adjust delay for smoother transition

                # Then set the final target color
                set_led_brightness(red_pin, green_pin, blue_pin, red_val, green_val, blue_val)

            # Exit the outer loop after one iteration of the inner loop
            break

            # Adjust delay for flickering effect
            time.sleep(1)

        # Update color target index for next iteration
        current_color_index += 1
        if current_color_index >= len(color_targets):
            current_color_index = 0  # Loop back to the first color

        step_size *= -1  # Optionally reverse direction
    time.sleep(base_delay)

# Water Simulation
def water_simulation():
    """
    Simulates water-like effects on multiple LEDs using four colors:
    blue, cyan, light blue, and white, with smooth transitions and flickering.
    """

    # Define LED colors
    colors = [(0, 0, 255),  # Blue
              (0, 255, 255),  # Cyan
              (135, 206, 250),  # Light Blue
              (255, 255, 255)]  # White

    # Define transition step size
    step_size = 1

    while True:
        # Choose a random starting color and direction
        start_color_index = random.randint(0, len(colors) - 1)
        direction = random.choice([-1, 1])  # -1 for forward, 1 for backward

        # Loop through color sequence with smooth transitions
        for i in range(len(colors)):
            # Calculate target color index based on starting index, direction, and offset
            target_color_index = (start_color_index + i * direction) % len(colors)

            # Calculate intermediate color for smoother transition
            intermediate_color = [
                (colors[start_color_index][j] + colors[target_color_index][j]) // 2
                for j in range(3)
            ]

            # Update all LEDs simultaneously
            for led_dict in LED_PINS:
                red_pin = led_dict["red"]
                green_pin = led_dict["green"]
                blue_pin = led_dict["blue"]

                # Set intermediate color first
                set_led_brightness(red_pin, green_pin, blue_pin,
                                   intermediate_color[0], intermediate_color[1], intermediate_color[2])
                time.sleep(0.1)  # Adjust delay for smoother transition

                # Then set the final target color
                set_led_brightness(red_pin, green_pin, blue_pin,
                                   colors[target_color_index][0],
                                   colors[target_color_index][1],
                                   colors[target_color_index][2])

                # Add random flickering effect
                flicker_amount = random.randint(-5, 5)
                flicker_color = [c + flicker_amount for c in colors[target_color_index]]
                set_led_brightness(red_pin, green_pin, blue_pin,
                                   min(255, flicker_color[0]),
                                   min(255, flicker_color[1]),
                                   min(255, flicker_color[2]))
                time.sleep(0.02)  # Adjust delay for flickering speed

            # Adjust delay for overall water flow speed
            time.sleep(0.2)

        # Optionally change direction after a complete cycle
        direction *= -1  # Uncomment to reverse direction after a cycle
    time.sleep(base_delay)

# Cascading Simulation
def cascading_simulation():
    """
    Simulates cascading with varying durations and random
    color bursts across multiple LEDs, creating a dynamic and visually
    appealing effect.
    """

    while True:
        # Choose a random number of LEDs to turn on (1 to 4)
        num_leds_on = random.randint(1, len(LED_PINS))

        # Create a copy of LED_PINS to shuffle and avoid modifying the original list
        shuffled_leds = LED_PINS[:]
        
        # Shuffle the copied list
        for i in range(len(shuffled_leds) - 1, 0, -1):
            j = random.randint(0, i)
            shuffled_leds[i], shuffled_leds[j] = shuffled_leds[j], shuffled_leds[i]

        # Select the first 'num_leds_on' elements from the shuffled list
        on_leds = shuffled_leds[:num_leds_on]

        # Choose a random base color for the lightning strike
        base_red = random.randint(128, 255)
        base_green = random.randint(128, 255)
        base_blue = random.randint(128, 255)

        # Simulate multiple rapid flashes with varying durations
        for _ in range(3):
            # Set random brightness for each flash
            brightness = random.uniform(0.5, 1.0)

            # Turn on selected LEDs with random color and brightness
            for led in on_leds:
                set_led_brightness(led["red"], led["green"], led["blue"],
                                   int(base_red * brightness),
                                   int(base_green * brightness),
                                   int(base_blue * brightness))

            # Short duration for each flash
            time.sleep(0.02)

            # Turn off all LEDs briefly between flashes
            for led in LED_PINS:
                set_led_brightness(led["red"], led["green"], led["blue"], 0, 0, 0)

            time.sleep(0.05)

        # Random delay for next strike
        time.sleep(random.uniform(0.5, 2.0))
    time.sleep(base_delay)

# Existing set_led_brightness function remains unchanged

"""
    Functions for Standard LED patterns
"""
# Forward pattern
def forward_pattern():
    for pin, color in leds:  # Iterate through each LED in the array
        led_control(pin, color, "on")  # Turn LED on
        time.sleep(0.2)  # Adjust delay as needed
        led_control(pin, color, "off")  # Turn LED off
        time.sleep(base_delay)  # Adjust delay as needed

# Reverse pattern
def reverse_pattern():
    for pin, color in reversed(leds):  # Iterate in reverse order using reversed()
        led_control(pin, color, "on")
        time.sleep(0.2)
        led_control(pin, color, "off")
        time.sleep(base_delay)

# Shifting pattern
def shifting_pattern():
    global leds  # Access global leds array
    shifted_leds = leds[shift_amount:] + leds[:shift_amount]
    for pin, color in shifted_leds:
        led_control(pin, color, "on")
        time.sleep(base_delay)
        led_control(pin, color, "off")
        time.sleep(base_delay)

# Mirrored pattern
def mirrored_pattern():
    global leds
    for i in range(len(leds)):
        pin1, color1 = leds[i]
        pin2, color2 = leds[len(leds) - 1 - i]  # Access mirrored element directly
        # Turn both LEDs on simultaneously:
        led_control(pin1, color1, "on")
        led_control(pin2, color2, "on")
        time.sleep(base_delay)  # Wait for specified duration
        # Turn both LEDs off simultaneously:
        led_control(pin1, color1, "off")
        led_control(pin2, color2, "off")
        time.sleep(base_delay)  # Wait for specified duration

current_pos = 0  # Initialize current LED position

# Spiral pattern
def spiral_pattern():
    global leds, current_pos

    # Choose a random spiral path
    clockwise_spiral = [(0, 0), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1)]
    counterclockwise_spiral = reversed(clockwise_spiral)
    square_spiral = clockwise_spiral * 4  # Repeat clockwise path 4 times for a square
    spiral_paths = [clockwise_spiral, counterclockwise_spiral, square_spiral]
    chosen_path = random.choice(spiral_paths)

    # Choose the desired spiral path
    if random.random() < 0.5:
        chosen_path = list(chosen_path)  # Convert to list first
        chosen_path.reverse()  # Apply reverse method directly

    # Center LED blink at the beginning
    if current_pos == 0:
        led_control(leds[0][0], "white", "on")  # Access LED 0 directly
        time.sleep(base_delay)
        led_control(leds[0][0], "white", "off")
        time.sleep(base_delay)

    # Loop through the chosen spiral path
    for x, y in chosen_path:
        num_leds = 10  # Adjust if the number changes

        # Address edge cases and update current_pos
        new_pos = (current_pos + x * num_leds + y) % num_leds  # Handle wraparound
        current_pos = new_pos

        # Turn on the LED at the current position with its color
        led_control(leds[current_pos][0], leds[current_pos][1], "on")
        time.sleep(base_delay + random.random() * random_delay_range)
        led_control(leds[current_pos][0], leds[current_pos][1], "off")
        time.sleep(base_delay + random.random() * random_delay_range)

# Waves pattern
def waves_pattern():
    global leds
    num_leds = len(leds)
    max_duration = 10  # Adjust as needed
    start_time = time.time()  # Capture start time

    # Choose random starting point, direction, initial group size, and color
    start_index = random.randrange(num_leds)
    direction = random.choice([-1, 1])
    group_size = random.randrange(2, 5)
    color = random.choice(range(256)) * 3  # RGB color triplet (0-255)

    while True:
        # Turn on LEDs in the current group
        for i in range(start_index, start_index + group_size):
            index = (i + direction * num_leds) % num_leds
            led_control(leds[index][0], color, "on")

        # Randomly change direction or group size at intervals
        if random.random() < 0.2:  # 20% chance of change
            if random.random() < 0.5:  # 50% chance to change direction
                direction *= -1
            else:
                group_size = random.randrange(2, 5)

        # Delay and turn off previous group
        time.sleep(random.uniform(0.1, 0.5))  # Random delay 0.1-0.5 seconds
        for i in range(start_index, start_index + group_size):
            index = (i + direction * num_leds) % num_leds
            led_control(leds[index][0], leds[index][1], "off")  # Include color argument

        # Shift starting point for the next group
        start_index = (start_index + direction * group_size) % num_leds    

        elapsed_time = time.time() - start_time
        # Break if either random condition or max duration is reached
        if random.random() < 0.01 or elapsed_time > max_duration:
            break

"""
    Standard LED patterns and RGB Simulation
"""
# Standard LED patterns
standard_patterns = {
    "forward": forward_pattern,
    "reverse": reverse_pattern,
    "shifting": shifting_pattern,
    "mirrored": mirrored_pattern,
    "spiral": spiral_pattern,
    "waves": waves_pattern,
}

# RGB Simulation
rgb_simulations = {
    "fading": fading_simulation,
    "fire": fire_simulation,
    "water": water_simulation,  # Add any remaining simulations here
    "cascading": cascading_simulation,
}

# Function to play a random sequence of standard patterns (without repetition)
def play_standard_patterns(lcd):  # Receive `lcd` as an argument
        played_patterns = set()
        for _ in range(6):
            while True:
                pattern_name = random.choice(list(standard_patterns.keys()))
                if pattern_name not in played_patterns:
                    # Display pattern name on LCD
                    lcd.clear()
                    lcd.putstr(f"{pattern_name}")
                    time.sleep(2)
                    # Execute the chosen pattern function
                    standard_patterns[pattern_name]()
                    played_patterns.add(pattern_name)
                    break

# Function to play a random RGB simulation
def play_rgb_simulation(lcd):
    simulations = list(rgb_simulations.values())  # Get a list of simulation functions
    random.choice(simulations)  # Shuffle the list for random order

    for simulation_fn in simulations:  # Iterate through each simulation function
        try:
            lcd.clear()
            lcd.putstr(f"{simulation_fn.__name__}")  # Display simulation name

            # Run the simulation for a specified duration or until user input
            run_duration = 10  # Set desired duration in seconds
            start_time_ms = time.ticks_ms()

            while True:
                simulation_fn()

                # Check for user input (e.g., button press) to exit the simulation
                if user_input_received():  # Replace with your specific user input detection method
                    break

                # Exit after the specified duration
                if time.ticks_ms() - start_time_ms >= run_duration * 10:
                    break

            # Ensure a brief pause between simulations
            time.sleep(0.5)

        except KeyboardInterrupt:  # Allow exiting with Ctrl+C
            break

    # Clear the LCD at the end
    lcd.clear()

# Replace with your actual user input detection method (e.g., button press logic)
def user_input_received():
    # Implement your specific code to check for user input (e.g., button press)
    # Return True if user input is detected, otherwise return False
    return False  # Example: Replace with actual logic
# Main loop with alternating calls
while True:
    # play_standard_patterns(lcd_object)  # Pass the `lcd_object`
    play_rgb_simulation(lcd_object)  # Pass the `lcd_object`
$abcdeabcde151015202530fghijfghij
$abcdeabcde151015202530fghijfghij
BOOTSELLED1239USBRaspberryPiPico©2020RP2-8020/21P64M15.00TTT