# Copyright 2026 PostalPortal LLC
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that
# the following conditions are met:
#   1. Redistributions of source code must retain
#      the above copyright notice, this list of conditions
#      and the following disclaimer.
#   2. Redistributions in binary form must reproduce
#      the above copyright notice, this list of conditions
#      and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   3. Neither the name of the copyright holder
#       nor the names of its contributors may be used
#       to endorse or promote products derived from
#       this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.

from machine import Pin, PWM
import machine
from time import sleep_us, sleep_ms
import config
from oledscreen import iconDisplay, centerText
import neopixel
import _thread

if config.HAS_WS2812:
    onboardLED = Pin(config.WS2812_PIN, Pin.OUT)
    neopixelLED = neopixel.NeoPixel(onboardLED, 1)
else:
    onboardLED = Pin(config.LED_PIN, Pin.OUT)
buzzerPin = PWM(Pin(config.BUZZER_PWM_PIN))
ledSequence = []
ledLock = _thread.allocate_lock()

# Run LED on the second core so it doesn't slow down the main process

def ledThreadFn():
    global feedbackType, ledSequence
    while True:
        ledLock.acquire()
        if not ledSequence:
            ledLock.release()
            sleep_ms(1)
            continue
        seq = ledSequence
        ledSequence = []
        ledLock.release()
        if config.HAS_WS2812:
            for item in seq:
                pulseRGBLED(item[0], item[1])
        else:
            for speed in seq:
                pulseLED(speed)


ledThread = None

# Run in main.py during boot
def createLEDThread():
    global ledThread
    ledThread = _thread.start_new_thread(ledThreadFn, ())

def doLEDFeedback(mode):
    global ledSequence
    if config.HAS_WS2812:
        ledLock.acquire()
        if mode == "SCAN":
            ledSequence = [["green", "fast"]]
        elif mode == "OK":
            ledSequence = [["green", "fast"]]
        elif mode == "ERR":
            ledSequence = [["red", "fast"],["red", "fast"],["red", "fast"]]
        elif mode == "POW":
            ledSequence = [["blue", "fast"],["green", "fast"],]
        elif mode == "NAF":
            ledSequence = [["orange", "fast"], ["blue", "fast"], ["orange", "fast"]]
        ledLock.release()
    else:
        ledLock.acquire()
        if mode == "SCAN":
            ledSequence = ["fast"]
        elif mode == "OK":
            ledSequence = ["fast"]
        elif mode == "ERR":
            ledSequence = ["fast", "fast", "fast"]
        elif mode == "POW":
            ledSequence = ["normal"]
        elif mode == "NAF":
            ledSequence = ["normal", "fast", "fast", "normal"]
        ledLock.release()

def doBuzzerFeedback(mode):
    if mode == "SCAN":
        passiveBuzzer(1047, 100)
    elif mode == "OK":
        passiveBuzzer(1175, 100)
    elif mode == "ERR":
        passiveBuzzer(440, 100)
        sleep_ms(50)
        passiveBuzzer(440, 100)
        sleep_ms(50)
        passiveBuzzer(440, 100)
    elif mode == "POW":
        passiveBuzzer(880, 100)
        passiveBuzzer(988, 100)
        passiveBuzzer(1047, 100)
    elif mode == "NAF":
        passiveBuzzer(440, 100)
        passiveBuzzer(494, 100)
        passiveBuzzer(440, 200)

def doFeedback(mode):
    doLEDFeedback(mode)
    doBuzzerFeedback(mode)
    

# Pulse the LED on and off with brightness fade
def pulseLED(speed):
    r = 600
    if speed == "fast":
        r = 300
    elif speed == "normal":
        r = 600
    elif speed == "slow":
        r = 1000
    
    for i in range(0,r):
        onboardLED.on()
        sleep_us(i)
        onboardLED.off()
        sleep_us(r-i)
    for i in range(0,r):
        onboardLED.on()
        sleep_us(r-i)
        onboardLED.off()
        sleep_us(i)
        
def pulseRGBLED(color, speed):
    rgb = (255, 255, 255)
    if color == "red":
        rgb = (255, 0, 0)
    elif color == "green":
        rgb = (0, 255, 0)
    elif color == "blue":
        rgb = (0, 0, 255)
    elif color == "orange":
        rgb = (255, 150, 0)
    elif color == "white":
        rgb = (255, 255, 255)
    
    r = 600
    if speed == "fast":
        r = 300
    elif speed == "normal":
        r = 600
    elif speed == "slow":
        r = 1000
    
    neopixelLED.fill(rgb)
    neopixelLED.write()
    sleep_ms(r)
    neopixelLED.fill((0,0,0))
    neopixelLED.write()

def passiveBuzzer(frequency, duration):
    global buzzerPin
    buzzerPin.duty_u16(int(65536*0.2))
    buzzerPin.freq(frequency)
    sleep_ms(duration)
    buzzerPin.duty_u16(int(0))

def firmwareUpdateMessage():
    centerText("Firmware Update", False, True)

def feedbackBuzzer(feedback, onMain = False):
    if feedback == "POW":
        iconDisplay("POW")
    doFeedback(feedback)
