import random

import board
import digitalio
import time

# Pin Mapping for the 4x4x4 LED cube
# Define pins for columns and layers

column_pins = [
    board.GP2, board.GP3, board.GP4, board.GP5,
    board.GP6, board.GP7, board.GP8, board.GP9,
    board.GP10, board.GP11, board.GP12, board.GP13,
    board.GP14, board.GP15, board.GP26, board.GP27
]

layer_pins = [board.GP28, board.GP18, board.GP16, board.GP17]  # GP18 instead of GP29
# layer_pins = [board.GP16]
# Initialize all pins
columns = []
for pin in column_pins:
    col = digitalio.DigitalInOut(pin)
    col.direction = digitalio.Direction.OUTPUT
    col.value = True  # HIGH = LED off for columns (common anode)
    columns.append(col)

layers = []
for pin in layer_pins:
    lay = digitalio.DigitalInOut(pin)
    lay.direction = digitalio.Direction.OUTPUT
    lay.value = False  # LOW = layer off
    layers.append(lay)


# Utility function to turn everything off
def turn_everything_off():
    for col in columns:
        col.value = True  # Turn off columns (HIGH)
    for lay in layers:
        lay.value = False  # Turn off layers (LOW)


# Function to turn on all LEDs one by one and keep them on
def turn_on_all_one_by_one():
    """
    Turn on every LED in the 4x4x4 cube one by one, keeping each one on.
    Goes through each layer, lighting up all 16 LEDs in that layer before
    moving to the next layer.
    """
    # First, make sure everything is off to start
    turn_everything_off()

    # Delay between turning on each LED
    delay = 0.1  # 100ms

    # Go through each layer (0-3)
    for layer_num in range(len(layer_pins)):
        # Turn on the current layer
        turn_everything_off()

        layers[layer_num].value = True

        # Go through each column (0-15) in this layer
        for col_num in range(len(column_pins)):
            # Turn on this specific LED
            columns[col_num].value = False  # Remember, columns are active LOW
            time.sleep(delay)
        time.sleep(0.5)
            # We don't turn off the column - this keeps the LED on

def random_lighting():
    for i in range(300):
        turn_everything_off()
        plane = random.randint(0,3)
        pole = random.randint(0, 15)
        layers[plane].value = True
        columns[pole].value = False
        time.sleep(0.15)

def moving_wall(bounce=False):
    turn_everything_off()
    delay = 0.4
    for col_num in range(len(column_pins)):
        columns[col_num].value = False

    for layer_num in range(len(layer_pins)):
        layers[layer_num].value = True
        time.sleep(delay)
        layers[layer_num].value = False

    if bounce:
        for layer_num in reversed(range(len(layer_pins)-1)):
            layers[layer_num].value = True
            time.sleep(delay)
            layers[layer_num].value = False


# Vertical and planar animations optimized for 4x4x4 LED cube
# Each animation function plus main loop

def rotating_fan(repeats=10, speed=0.2):
    """
    Creates a rotating fan effect with vertical columns.
    """
    patterns = [
        [0, 4, 8, 12],  # diagonal 1
        [1, 5, 9, 13],  # offset diagonal 1
        [2, 6, 10, 14],  # offset diagonal 1
        [3, 7, 11, 15],  # offset diagonal 1
        [3, 6, 9, 12],  # diagonal 2
        [2, 5, 8, 15],  # offset diagonal 2
        [1, 4, 11, 14],  # offset diagonal 2
        [0, 7, 10, 13]  # offset diagonal 2
    ]

    for _ in range(repeats):
        for pattern in patterns:
            turn_everything_off()

            # Turn on all layers
            for layer in layers:
                layer.value = True

            # Turn on the specific columns for this pattern
            for col_idx in pattern:
                columns[col_idx].value = False

            time.sleep(speed)


def moving_wall(repeats=5, speed=0.2, bounce=True):
    """
    A wall that moves across the cube.
    """
    # Define the walls (columns in each wall)
    walls = [
        [0, 4, 8, 12],  # Front wall (x=0)
        [1, 5, 9, 13],  # Second wall (x=1)
        [2, 6, 10, 14],  # Third wall (x=2)
        [3, 7, 11, 15]  # Back wall (x=3)
    ]

    for _ in range(repeats):
        # Forward movement
        for wall in walls:
            turn_everything_off()

            # Turn on all layers
            for layer in layers:
                layer.value = True

            # Turn on the columns for this wall
            for col_idx in wall:
                columns[col_idx].value = False

            time.sleep(speed)

        # Bounce back if enabled
        if bounce:
            for wall in reversed(walls[:-1]):  # Skip the last wall as it's already displayed
                turn_everything_off()

                # Turn on all layers
                for layer in layers:
                    layer.value = True

                # Turn on the columns for this wall
                for col_idx in wall:
                    columns[col_idx].value = False

                time.sleep(speed)


def vertical_scan(repeats=3, speed=0.15):
    """
    Vertical scanning patterns across the cube.
    """
    # Define different vertical scan patterns
    patterns = [
        # Horizontal scans (rows of columns)
        [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]],

        # Vertical scans (columns in same position across rows)
        [[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]]
    ]

    for _ in range(repeats):
        for pattern_set in patterns:
            for pattern in pattern_set:
                turn_everything_off()

                # Turn on all layers
                for layer in layers:
                    layer.value = True

                # Turn on the columns for this pattern
                for col_idx in pattern:
                    columns[col_idx].value = False

                time.sleep(speed)


def wiper_effect(repeats=5, speed=0.2):
    """
    Creates a windshield wiper effect using vertical columns.
    """
    # Define the wiper positions
    wiper_positions = [
        [0, 1, 4, 5],  # Left corner
        [1, 2, 5, 6],  # Left-center
        [2, 3, 6, 7],  # Right-center
        [3, 7, 11, 15],  # Right corner
        [2, 3, 6, 7],  # Right-center again
        [1, 2, 5, 6],  # Left-center again
    ]

    for _ in range(repeats):
        for position in wiper_positions:
            turn_everything_off()

            # Turn on all layers
            for layer in layers:
                layer.value = True

            # Turn on the columns for this position
            for col_idx in position:
                columns[col_idx].value = False

            time.sleep(speed)


def rain_drop(repeats=8, speed=0.15):
    """
    Rain drops falling from top to bottom.
    """
    for _ in range(repeats):
        # Pick a random column for this drop
        col_idx = random.randint(0, 15)

        # Rain drop falling from top to bottom
        for z in range(3, -1, -1):
            turn_everything_off()

            # Light only this layer
            layers[z].value = True

            # Light only this column
            columns[col_idx].value = False

            time.sleep(speed)


def plane_wave(repeats=3, speed=0.2):
    """
    A wave of horizontal planes moving up and down.
    """
    for _ in range(repeats):
        # Wave going up
        for z in range(4):
            turn_everything_off()

            # Turn on this layer
            layers[z].value = True

            # Turn on all columns
            for col in columns:
                col.value = False

            time.sleep(speed)

        # Wave going down
        for z in range(2, -1, -1):
            turn_everything_off()

            # Turn on this layer
            layers[z].value = True

            # Turn on all columns
            for col in columns:
                col.value = False

            time.sleep(speed)


def elevator(repeats=5, speed=0.2):
    """
    An elevator-like pattern moving up and down the cube.
    """
    # Define the elevator floors (each floor is a layer)
    for _ in range(repeats):
        # Elevator going up
        for z in range(4):
            turn_everything_off()

            # Turn on this layer
            layers[z].value = True

            # Light up the elevator columns (center 2x2 area)
            for y in range(1, 3):
                for x in range(1, 3):
                    col_idx = x + (y * 4)
                    columns[col_idx].value = False

            time.sleep(speed)

        # Pause at the top
        time.sleep(speed * 1.5)

        # Elevator going down
        for z in range(2, -1, -1):
            turn_everything_off()

            # Turn on this layer
            layers[z].value = True

            # Light up the elevator columns (center 2x2 area)
            for y in range(1, 3):
                for x in range(1, 3):
                    col_idx = x + (y * 4)
                    columns[col_idx].value = False

            time.sleep(speed)

        # Pause at the bottom
        time.sleep(speed * 1.5)


def elevator_expanded(repeats=5, speed=0.2):
    """
    An elevator-like pattern moving up and down the cube.
    On the way up: lights the middle 2x2 area
    On the way down: lights everything EXCEPT the middle 2x2 area
    """
    # Define the middle 2x2 area indices
    middle_indices = [5, 6, 9, 10]

    # Define the non-middle indices (everything except the 2x2 center)
    outer_indices = [0, 1, 2, 3, 4, 7, 8, 11, 12, 13, 14, 15]

    for _ in range(repeats):
        # Elevator going up - middle 2x2 lit
        for z in range(4):
            turn_everything_off()

            # Turn on this layer
            layers[z].value = True

            # Light up the elevator columns (center 2x2 area)
            for col_idx in middle_indices:
                columns[col_idx].value = False

            time.sleep(speed)

        # Pause at the top
        # time.sleep(speed * 1.5)

        # Elevator going down - everything EXCEPT middle 2x2 lit
        for z in range(3, -1, -1):
            turn_everything_off()

            # Turn on this layer
            layers[z].value = True

            # Light up everything EXCEPT the center 2x2 area
            for col_idx in outer_indices:
                columns[col_idx].value = False

            time.sleep(speed)

        # Pause at the bottom
        # time.sleep(speed * 1.5)



def spinning_plane(repeats=4, speed=0.15):
    """
    A plane that spins around the vertical axis.
    """
    # Define the plane positions
    planes = [
        # Vertical planes along the x-axis
        [0, 4, 8, 12],
        [0, 5, 10, 15],
        [0, 1, 2, 3],
        [3, 6, 9, 12],
        [3, 7, 11, 15],
        [0, 5, 10, 15],
        [12, 13, 14, 15],
        [3, 6, 9, 12]
    ]

    for _ in range(repeats):
        for plane in planes:
            turn_everything_off()

            # Turn on all layers
            for layer in layers:
                layer.value = True

            # Turn on the columns for this plane
            for col_idx in plane:
                columns[col_idx].value = False

            time.sleep(speed)


def checkerboard(repeats=6, speed=0.5):
    """
    Toggle between two checkerboard patterns.
    """
    # Define the two checkerboard patterns
    pattern1 = [0, 2, 5, 7, 8, 10, 13, 15]  # Alternating columns
    pattern2 = [1, 3, 4, 6, 9, 11, 12, 14]  # Opposite alternating columns

    for _ in range(repeats):
        # First pattern
        turn_everything_off()

        # Turn on all layers
        for layer in layers:
            layer.value = True

        # Turn on the columns for pattern 1
        for col_idx in pattern1:
            columns[col_idx].value = False

        time.sleep(speed)

        # Second pattern
        turn_everything_off()

        # Turn on all layers
        for layer in layers:
            layer.value = True

        # Turn on the columns for pattern 2
        for col_idx in pattern2:
            columns[col_idx].value = False

        time.sleep(speed)


def propeller(repeats=10, speed=0.15):
    """
    A propeller-like rotating animation with different configurations.
    """
    # Define different propeller patterns
    patterns = [
        # '+' propeller
        [1, 5, 6, 7, 9, 13, 14, 15],
        # 'x' propeller
        [0, 3, 5, 6, 9, 10, 12, 15]
    ]

    # Define rotations for each pattern
    rotations = [
        # Rotations for '+' pattern
        [[1, 5, 6, 7, 9, 13, 14, 15],
         [4, 5, 6, 7, 8, 9, 10, 11]],

        # Rotations for 'x' pattern
        [[0, 3, 5, 6, 9, 10, 12, 15],
         [2, 1, 4, 7, 8, 11, 13, 14]]
    ]

    for _ in range(repeats):
        # Cycle through each propeller type
        for pattern_rotations in rotations:
            # Cycle through each rotation of this pattern
            for pattern in pattern_rotations:
                turn_everything_off()

                # Turn on all layers
                for layer in layers:
                    layer.value = True

                # Turn on the columns for this propeller pattern
                for col_idx in pattern:
                    columns[col_idx].value = False

                time.sleep(speed)

def diagonal_movement(repeats=10, speed=0.2):
    patterns = [[0], [1, 4], [2, 5, 8], [3, 6, 9, 12], [7, 10, 13], [11, 14], [15]]

    for _ in range(repeats):
        for pattern_range in patterns:
            turn_everything_off()

            # turn on all layers
            for layer in layers:
                layer.value = True

            for col_idx in pattern_range:
                columns[col_idx].value = False

            time.sleep(speed)

def random_pixels(repeats=50, speed=0.2):
    horizontal = random.randint(0,3)
    vertical = random.randint(0, 15)

    turn_everything_off()

    columns[vertical].value = False
    layers[horizontal].value = True

    time.sleep(speed)

def move_squares(repeats=4, speed=0.5):
    squares = [
        [0, 1, 4, 5],
        [2, 3, 6, 7],
        [10, 11, 14, 15],
        [8, 9, 12, 13]
    ]

    horizontals = [
        [0, 1],
        [2, 3],
        [0, 1],
        [2, 3]
    ]

    for _ in range(repeats):
        for i in range(len(squares)):
            turn_everything_off()

            square = squares[i]
            level = horizontals[i]

            for idx in level:
                layers[idx].value = True

            for idx in square:
                columns[idx].value = False

            time.sleep(speed)

# Main loop that alternates between all animations
while True:

    #elevator expansion
    elevator_expanded(4, 0.2)
    time.sleep(0.5)

    turn_on_all_one_by_one()

    move_squares(repeats=4, speed=0.2)
    time.sleep(0.5)

    for i in range(15):
        random_pixels()
        time.sleep(0.02)

    diagonal_movement(repeats=4)
    time.sleep(0.5)

    # Moving wall
    moving_wall(repeats=3, speed=0.2, bounce=True)

    # Rotating fan
    rotating_fan(repeats=5, speed=0.15)
    time.sleep(0.5)

    # # Vertical scan
    # vertical_scan(repeats=2, speed=0.2)
    # time.sleep(0.5)

    # # Wiper effect
    # wiper_effect(repeats=3, speed=0.25)
    # time.sleep(0.5)

    # Rain drops
    for _ in range(10):
        rain_drop(repeats=1, speed=0.15)
    time.sleep(0.5)

    # Plane wave
    plane_wave(repeats=4, speed=0.18)
    time.sleep(0.5)

    # Spinning plane
    spinning_plane(repeats=2, speed=0.20)
    time.sleep(0.5)

    # Checkerboard
    checkerboard(repeats=6, speed=0.4)
    time.sleep(0.5)

    # Propeller
    propeller(repeats=4, speed=0.15)
    time.sleep(0.5)