"""
eyes.py — Control de les matrius LED 8x8 RGB WS2811 (ulls del robot).
Equivalent a eyes.cpp del codi Arduino/ESP32.

FastLED → pigpio waveforms (GPIO26, qualsevol pin).

REQUISIT: iniciar el dimoni pigpio amb resolució d'1µs:
    sudo pigpiod -s 1
Si s'inicia sense -s 1 (defecte 5µs), els LEDs no funcionaran correctament.

Si en el futur es fa una modificació hardware (GPIO26 → GPIO18 o GPIO21),
es pot substituir _send_ws2811() per rpi_ws281x sense canviar cap altra funció.
"""

import time
import threading
import pigpio

from pins import LED_DATA

# ==================
# Constants
# ==================

ROW_NUM  = 8
COL_NUM  = 8
NUM_LEDS = ROW_NUM * COL_NUM * 2   # 128 LEDs (2 matrius 8x8)

MAX_BR = 170   # Brillantor màxima del parpelleig (0–255)
MIN_BR = 80    # Brillantor mínima del parpelleig

# Colors predefinits (R, G, B)
WHITE  = (255, 255, 255)
RED    = (255,   0,   0)
GREEN  = (  0, 255,   0)
BLUE   = (  0,   0, 255)
YELLOW = (255, 200,   0)
ORANGE = (255,  80,   0)
PURPLE = (180,   0, 255)
CYAN   = (  0, 255, 255)   # Color del mode gestos
BLACK  = (  0,   0,   0)

# ==================
# Formes dels ulls (índexs dels LEDs actius)
# ==================

class EyeShape:
    """Conjunt de LEDs que formen una expressió dels ulls."""
    def __init__(self, leds: list):
        self.leds = leds
        self.len  = len(leds)


EYES_OPEN = EyeShape([
    102, 89, 38, 25, 106, 101, 90, 85, 42, 37, 26, 21,
    107, 100, 91, 84, 43, 36, 27, 20, 108, 99, 92, 83,
    44, 35, 28, 19, 109, 98, 93, 82, 45, 34, 29, 18,
    97, 94, 33, 30
])

EYES_FW = EyeShape([
    103, 88, 39, 24, 105, 102, 89, 86, 41, 38, 25, 22,
    117, 106, 101, 90, 85, 74, 53, 42, 37, 26, 21, 10,
    123, 116, 100, 91, 75, 68, 59, 52, 36, 27, 11, 4,
    99, 92, 35, 28, 98, 93, 34, 29, 97, 94, 33, 30, 96,
    95, 32, 31
])

EYES_DOWN = EyeShape([97, 94, 33, 30])

# Nova forma per al mode gestos: marc extern dels dos ulls (expressió "atenta")
EYES_GESTURE = EyeShape([
    96, 97, 98, 99, 100, 101, 102, 103,
    104, 111, 112, 119, 120, 127,
    31, 32, 33, 34, 35, 36, 37, 38, 39,
    0, 7, 8, 15, 16, 23
])

# ==================
# Estat global
# ==================

_pi: pigpio.pi = None
_leds          = [[0, 0, 0] for _ in range(NUM_LEDS)]
_brightness    = MAX_BR
_leds_lock     = threading.Lock()

_update_stop   = threading.Event()
_update_thread: threading.Thread = None

# Màscara GPIO per a les waveforms de pigpio
_GPIO_MASK: int = 0


# ==================
# WS2811 via pigpio waveforms
# ==================

def _send_ws2811(data: bytes):
    """
    Envia dades RGB als LEDs WS2811 via pigpio waveforms.
    Ordre de color: GRB (igual que FastLED amb WS2811, GRB).
    Timing a 1µs de resolució (requereix sudo pigpiod -s 1):
      - Bit 0: 1µs HIGH + 2µs LOW  (spec: 0.5µs + 2.0µs)
      - Bit 1: 2µs HIGH + 1µs LOW  (spec: 1.2µs + 1.3µs)
      - Reset: 80µs LOW
    """
    pulses = []
    for byte_val in data:
        for bit in range(7, -1, -1):
            if byte_val & (1 << bit):
                pulses.append(pigpio.pulse(_GPIO_MASK, 0,            2))
                pulses.append(pigpio.pulse(0,          _GPIO_MASK,   1))
            else:
                pulses.append(pigpio.pulse(_GPIO_MASK, 0,            1))
                pulses.append(pigpio.pulse(0,          _GPIO_MASK,   2))
    pulses.append(pigpio.pulse(0, _GPIO_MASK, 80))   # reset

    _pi.wave_add_new()
    _pi.wave_add_generic(pulses)
    wid = _pi.wave_create()
    if wid >= 0:
        _pi.wave_send_once(wid)
        while _pi.wave_tx_busy():
            pass
        _pi.wave_delete(wid)


def _eyes_show(brightness: int):
    """Renderitza l'estat actual de _leds amb la brillantor indicada."""
    data = bytearray(NUM_LEDS * 3)
    scale = brightness / 255
    for i, (r, g, b) in enumerate(_leds):
        data[i * 3 + 0] = int(g * scale)   # WS2811 GRB: primer G
        data[i * 3 + 1] = int(r * scale)
        data[i * 3 + 2] = int(b * scale)
    _send_ws2811(bytes(data))


# ==================
# Setup i cleanup
# ==================

def eyes_setup(pi: pigpio.pi):
    """Inicialitza el GPIO i arrenca el thread de parpelleig."""
    global _pi, _GPIO_MASK, _update_thread

    _pi        = pi
    _GPIO_MASK = 1 << LED_DATA

    pi.set_mode(LED_DATA, pigpio.OUTPUT)
    pi.write(LED_DATA, 0)

    _update_stop.clear()
    _update_thread = threading.Thread(
        target=_task_update_leds, daemon=True, name="eyes"
    )
    _update_thread.start()


def eyes_cleanup():
    """Atura el thread de parpelleig i apaga els LEDs."""
    _update_stop.set()
    if _update_thread:
        _update_thread.join(timeout=1.0)
    with _leds_lock:
        for i in range(NUM_LEDS):
            _leds[i] = [0, 0, 0]
        _eyes_show(255)


# ==================
# Thread de parpelleig (equivalent a task_update_leds del FreeRTOS)
# ==================

def _task_update_leds():
    """
    Bucle continu que fa respirar la brillantor dels ulls.
    Equivalent a task_update_leds() del FreeRTOS.
    """
    global _brightness
    going_up = False   # Al C++ comença a MAX_BR i baixa
    _brightness = MAX_BR

    while not _update_stop.is_set():
        if going_up:
            if _brightness < MAX_BR:
                _brightness += 2
            else:
                going_up = False
        else:
            if _brightness > MIN_BR:
                _brightness -= 2
            else:
                going_up = True

        with _leds_lock:
            _eyes_show(_brightness)
        time.sleep(0.05)


# ==================
# Animacions (equivalent a les funcions de eyes.cpp)
# ==================

def eyes_turn_off():
    """Apaga tots els LEDs amb un fos progressiu."""
    for _ in range(50):
        with _leds_lock:
            for i in range(NUM_LEDS):
                r, g, b = _leds[i]
                _leds[i] = [int(r * 245 / 255),
                             int(g * 245 / 255),
                             int(b * 245 / 255)]
        time.sleep(0.01)
    with _leds_lock:
        for i in range(NUM_LEDS):
            _leds[i] = [0, 0, 0]


def eyes_turn_on(shape: EyeShape, color: tuple,
                 repeat: int = 1, forward: bool = True):
    """
    Encén els LEDs d'una forma un per un, amb animació.
    shape:   forma a dibuixar (EYES_OPEN, EYES_FW, EYES_DOWN…)
    color:   color RGB com a tupla (r, g, b)
    repeat:  nombre de vegades que es repeteix l'animació
    forward: True = ordre normal, False = ordre invers
    """
    r, g, b = color
    for rep in range(repeat):
        eyes_turn_off()
        for i in range(shape.len):
            idx = shape.leds[i if forward else (shape.len - 1 - i)]
            with _leds_lock:
                _leds[idx] = [r, g, b]
            time.sleep(0.008)

        if rep < repeat - 1:
            for i in range(shape.len):
                idx = shape.leds[i if forward else (shape.len - 1 - i)]
                with _leds_lock:
                    _leds[idx] = [0, 0, 0]
                time.sleep(0.008)


# ==================
# Animacions noves — mode gestos (TFG)
# ==================

def eyes_gesture_mode_on():
    """
    Animació d'activació del mode gestos.
    Parpelleig doble en cian per indicar que el robot escolta gestos.
    """
    eyes_turn_on(EYES_OPEN, CYAN, repeat=2)


def eyes_gesture_mode_off():
    """
    Animació de desactivació del mode gestos.
    Torna als ulls oberts en blanc.
    """
    eyes_turn_off()
    eyes_turn_on(EYES_OPEN, WHITE)


def eyes_listening():
    """
    Expressió "escoltant": marc extern dels ulls en cian.
    Es mostra mentre el robot espera un gest.
    """
    eyes_turn_on(EYES_GESTURE, CYAN)
