"""
GUI_controller.py

One-line LCD animation selector.

Controls:
    Joystick left/up   -> Previous mode
    Joystick right/down -> Next mode
    Joystick button    -> Play selected mode

The selected mode is sent to main.py through the on_mode_selected callback.
"""

from time import sleep

from RPLCD.i2c import CharLCD

from ADC import ADS7830


# ============================================================
# HARDWARE SETUP
# ============================================================

# Joystick push-button GPIO.
# This pin may be left electrically disconnected from the joystick,
# but the menu button obviously will not work until it is connected.

# ADS7830 I2C address.
ADC_ADDRESS = 0x4B

# LCD I2C address.
LCD_ADDRESS = 0x27

# Joystick analog channels.
# These match the channels from your current working code.
JOYSTICK_Y_CHANNEL = 0
JOYSTICK_X_CHANNEL = 4




adc = ADS7830(address=ADC_ADDRESS)

lcd = CharLCD(
    i2c_expander="PCF8574",
    address=LCD_ADDRESS,
    cols=16,
    rows=2
)


# ============================================================
# JOYSTICK SETTINGS
# ============================================================

CENTER = 128
DEADZONE = 35

# How often the joystick is checked.
POLL_DELAY = 0.05

# Prevents one joystick movement from scrolling through many modes.
MOVE_REPEAT_DELAY = 0.25


# ============================================================
# AVAILABLE LED MODES
# ============================================================

MODES = [
    "Ocean",
    "Aurora",
    "Peppermint",
    "Lava",
    "Candle",
    "Nebula",
    "Thunderstorm",
    "Rainbow",
    "Breathe",
    "Chase",
    "Sparkle",
]


# ============================================================
# LCD FUNCTIONS
# ============================================================

def fit_to_lcd(text):
    """
    Make text exactly 16 characters long so old characters
    do not remain visible on the LCD.
    """

    return str(text)[:16].ljust(16)


def display_mode(mode_name, is_playing=False):
    """
    Display a mode using only the first LCD row.

    Examples:
        > Nebula
        * Nebula

    > means the mode is highlighted.
    * means the mode was selected and is playing.
    """

    marker = "*" if is_playing else ">"

    lcd.cursor_pos = (0, 0)
    lcd.write_string(
        fit_to_lcd(f"{marker} {mode_name}")
    )

    # Keep the second row blank.
    lcd.cursor_pos = (1, 0)
    lcd.write_string(" " * 16)


def display_message(message):
    """
    Show a temporary message on the first LCD row.
    """

    lcd.cursor_pos = (0, 0)
    lcd.write_string(fit_to_lcd(message))

    lcd.cursor_pos = (1, 0)
    lcd.write_string(" " * 16)


def clear_display():
    """
    Clear the LCD.
    """

    lcd.clear()


# ============================================================
# JOYSTICK FUNCTIONS
# ============================================================

def read_joystick():
    """
    Read the joystick X and Y positions.

    Returns:
        tuple: (x, y)
    """

    x = adc.read(JOYSTICK_X_CHANNEL)
    y = adc.read(JOYSTICK_Y_CHANNEL)

    return x, y


def get_direction(x, y):
    """
    Convert joystick analog readings into a menu direction.

    Returns:
        -1 for previous mode
         1 for next mode
         0 when centered
    """

    # Left or up selects the previous mode.
    if x < CENTER - DEADZONE:
        return -1

    if y < CENTER - DEADZONE:
        return -1

    # Right or down selects the next mode.
    if x > CENTER + DEADZONE:
        return 1

    if y > CENTER + DEADZONE:
        return 1

    return 0


def wait_for_joystick_center():
    """
    Wait until the joystick returns to its center position.

    This prevents one movement from skipping through many modes.
    """

    while True:
        x, y = read_joystick()

        if get_direction(x, y) == 0:
            return

        sleep(POLL_DELAY)


def wait_for_button_release():
    """
    Wait until the joystick button is released.
    """

    while button.is_pressed:
        sleep(POLL_DELAY)


# ============================================================
# MENU LOOP
# ============================================================

def run_menu(on_mode_selected, initial_mode="Ocean"):
    """
    Move the joystick to change modes.

    The highlighted mode begins playing immediately.
    No joystick push button is required.
    """

    if not callable(on_mode_selected):
        raise TypeError(
            "on_mode_selected must be a function that accepts a mode name"
        )

    if initial_mode in MODES:
        selected_index = MODES.index(initial_mode)
    else:
        selected_index = 0

    active_mode = MODES[selected_index]

    clear_display()
    display_mode(active_mode, is_playing=True)

    on_mode_selected(active_mode)
    print(f"Initial LED mode: {active_mode}")

    try:
        while True:
            x, y = read_joystick()
            direction = get_direction(x, y)

            if direction != 0:
                selected_index = (
                    selected_index + direction
                ) % len(MODES)

                active_mode = MODES[selected_index]

                display_mode(active_mode, is_playing=True)

                print(f"Playing mode: {active_mode}")
                on_mode_selected(active_mode)

                sleep(MOVE_REPEAT_DELAY)
                wait_for_joystick_center()

            sleep(POLL_DELAY)

    except KeyboardInterrupt:
        raise

    except Exception:
        display_message("Menu error")
        raise

# ============================================================
# CLEANUP
# ============================================================

def cleanup():
    """
    Release LCD and button resources.
    """

    try:
        display_message("Shutting down")
        sleep(0.5)
        lcd.clear()
        lcd.close(clear=True)
    except Exception:
        pass




# ============================================================
# STANDALONE MENU TEST
# ============================================================

if __name__ == "__main__":

    def print_selected_mode(mode_name):
        """
        Test callback used only when this file is run directly.
        """

        print(f"SELECTED: {mode_name}")

    print("LCD menu test starting...")

    try:
        run_menu(
            on_mode_selected=print_selected_mode,
            initial_mode="Ocean"
        )

    except KeyboardInterrupt:
        print("\nLCD menu stopped")

    finally:
        cleanup()