import board, time, os, math, random
import neopixel
from audiopwmio import PWMAudioOut as AudioOut
from audiocore import WaveFile
import pulseio, adafruit_irremote

# -----------------------------
# Pintout
# Audio - GP14
# Neopicel - GP9
# IR - GP16
# -----------------------------

# -----------------------------
# AUDIO SETUP
# -----------------------------
audio = AudioOut(board.GP14)

path = "sounds/"
audios = [
    "Rainbow.wav",
    "Clear.wav",
    "Clouds.wav",
    "Rain.wav",
    "Thunderstorm.wav"
]

current_file = None

def play_sound(index):
    global current_file

    audio.stop()

    if current_file:
        current_file.close()

    current_file = open(path + audios[index], "rb")
    audio.play(WaveFile(current_file))

# -----------------------------
# LED SETUP
# -----------------------------
pixels = neopixel.NeoPixel(board.GP9, 30, auto_write=False)
pixels.brightness = 1

# animation state
mode = -1
curr_mode = -1
# rainbow
rainbow_offset = 0

# breathing
breath = 0
breath_dir = 1

# ripple
ripple_pos = 0

# strobe
strobe = 0

# -----------------------------
# IR receiver setup
# -----------------------------
ir_receiver = pulseio.PulseIn(board.GP16, maxlen=120, idle_state=True)
decoder = adafruit_irremote.GenericDecode()

def decode_ir_signals(p):
    codes = decoder.decode_bits(p)
    return codes

# IR Map
BUTTON_MAP = {
    "00FFA25D": "-1",
    "00FF6897": "0",
    "00FF30CF": "1",
    "00FF18E7": "2",
    "00FF7A85": "3",
    "00FF10EF": "4",
    "00FF38C7": "5", # not being used
    "00FF5AA5": "6" # not being used 
}

# -----------------------------
# AUDIO UPDATE (NON-BLOCKING)
# -----------------------------
def update_audio():
    if mode == -1 or mode > 4:
        return
    else:
        print("MODE CHANGED: should play audio")
        play_sound(mode)

# -----------------------------
# LED UPDATE (NON-BLOCKING)
# -----------------------------
def hsv_to_rgb(h, s, v):
    i = int(h * 6)
    f = (h * 6) - i
    p = int(255 * v * (1 - s))
    q = int(255 * v * (1 - f * s))
    t = int(255 * v * (1 - (1 - f) * s))
    v = int(255 * v)

    i = i % 6

    if i == 0:
        return (v, t, p)
    if i == 1:
        return (q, v, p)
    if i == 2:
        return (p, v, t)
    if i == 3:
        return (p, q, v)
    if i == 4:
        return (t, p, v)
    if i == 5:
        return (v, p, q)

def update_led():
    global rainbow_offset, breath, breath_dir, ripple_pos, strobe

    if mode == 0:
        # 🌈 TRUE rainbow
        print("LED should change to Rainbow")

        for i in range(len(pixels)):
            h = (rainbow_offset + i * 8) % 256
            h = h / 256.0

            pixels[i] = hsv_to_rgb(h, 1, 1)

        rainbow_offset = (rainbow_offset + 1) % 256

    elif mode == 1:
        # 💛 breathing yellow
        breath += breath_dir * 0.03
        if breath >= 1:
            breath = 1
            breath_dir = -1
        elif breath <= 0:
            breath = 0
            breath_dir = 1

        r = int(255 * breath)
        g = int(200 * breath)
        pixels.fill((r, g, 0))

    elif mode == 2:
        # ☁️ CLOUD EFFECT (smooth gray waves)
        t = time.monotonic()

        for i in range(len(pixels)):
            v = (
                0.5
                + 0.25 * math.sin(i * 0.3 + t * 0.8)
                + 0.25 * math.sin(i * 0.1 - t * 0.5)
            )

            v = max(0, min(1, v))

            c = int(180 * v)
            pixels[i] = (c, c, int(c * 1.1))  # slight blue tint

        # optional lightning flash
        if random.random() < 0.01:
            pixels.fill((255, 255, 255))

    elif mode == 3:
        # 🌧 ripple
        pixels.fill((0, 0, 0))
        for i in range(0, len(pixels), 3):
            idx = (i + ripple_pos) % len(pixels)
            pixels[idx] = (0, 0, 255)
        ripple_pos = (ripple_pos + 1) % len(pixels)

    elif mode == 4:
        # ⚡ strobe
        # strobe += 1
        pixels.fill((255, 255, 255))
        time.sleep(0.5)
        pixels.fill((0, 0, 0))
        time.sleep(0.5)
        # if strobe % 6 < 3:
        # if strobe % 2 == 0:
        #     pixels.fill((0, 0, 0))
        # else:
        #     pixels.fill((255, 255, 255))


    pixels.show()

# -----------------------------
# MAIN LOOP (ENGINE)
# -----------------------------

while True:
    pulses = decoder.read_pulses(ir_receiver)
    try:
        # Attempt to decode the received pulses
        received_code = decode_ir_signals(pulses)
        if received_code:
            hex_code = ''.join(["%02X" % x for x in received_code])
        curr_mode = BUTTON_MAP.get(hex_code, "ERROR")
        print(f"Received: {hex_code}", f"Mode: {curr_mode}")
    except adafruit_irremote.IRNECRepeatException:  # Signal was repeated, ignore
        pass
    except adafruit_irremote.IRDecodeException:  # Failed to decode signal
        print("Error decoding")

    if curr_mode == "ERROR":
        print("Decoding error")
        pass
    elif mode != curr_mode:
        mode = int(curr_mode)
        update_audio()
        update_led()

    time.sleep(0.03)