#!/usr/bin/env bash
set -euo pipefail

SHARE_DIR="/usr/local/share/sg-da7212"
PROFILE_DIR="$SHARE_DIR/profiles"
MAX_DAC_VOLUME=100

# Detect the active DA7212/IQaudio/Codec Zero ALSA card.
# Different boards may appear as:
#   Zero [RPi Codec Zero]
#   IQaudIOCODEC [IQaudIOCODEC]
#   HAT [SG DA7212 HAT]
# These are valid for the DA7212 Audio Codec HAT family.
find_card_line() {
    awk '
      /^[[:space:]]*[0-9]+[[:space:]]+\[/ {
        line=$0; low=tolower(line);
        if (low ~ /zero|iqaudio|da721|codec|sg da7212|sg.*hat/) { print line; exit }
      }
    ' /proc/asound/cards 2>/dev/null || true
}

find_card_id() {
    local line id
    line="$(find_card_line)"
    [[ -n "$line" ]] || return 1
    id="$(printf '%s\n' "$line" | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | sed 's/[[:space:]]*$//')"
    [[ -n "$id" ]] && printf '%s\n' "$id"
}

find_card_index() {
    local line
    line="$(find_card_line)"
    [[ -n "$line" ]] || return 1
    printf '%s\n' "$line" | awk '{gsub(":", "", $1); print $1; exit}'
}

require_card() {
    CARD_ID="$(find_card_id || true)"
    CARD_INDEX="$(find_card_index || true)"
    if [[ -z "${CARD_ID:-}" || -z "${CARD_INDEX:-}" ]]; then
        echo "ERROR: DA7212/IQaudio/Codec Zero ALSA card not found." >&2
        echo "Check that /boot/firmware/config.txt enables dtoverlay=iqaudio-codec, then reboot." >&2
        echo "Current cards:" >&2
        cat /proc/asound/cards >&2 2>/dev/null || true
        exit 1
    fi
}

pcm_device() {
    require_card
    echo "plughw:CARD=${CARD_ID},DEV=0"
}

profile_file() {
    case "$1" in
        all) echo "All-input-output.state" ;;
        aux|aux-low) echo "Aux-input.state" ;;
        hp|headphone|headset-mic|headset-mic-ref) echo "Headphone-input-output.state" ;;
        mic1|onboard-mic1) echo "OnBoard-MIC1-input.state" ;;
        mic2|onboard-mic|onboard-mic2|onboard-mic-p|onboard-mic-n|onboard-mic-diff) echo "OnBoard-MIC2-input.state" ;;
        spk|speaker) echo "Single-speaker-output.state" ;;
        speakers|dual-speaker) echo "Dual-speaker-output.state" ;;
        *) return 1 ;;
    esac
}

make_temp_state() {
    local src="$1" tmp
    tmp="$(mktemp /tmp/sg-da7212-state.XXXXXX)"
    cp "$src" "$tmp"
    sed -i -E "1s/^state\.[^[:space:]]+[[:space:]]*\{/state.${CARD_ID} {/" "$tmp"
    echo "$tmp"
}

restore_state() {
    local tmp="$1"
    # Try by ALSA card ID first. If that fails, fall back to numeric index.
    if sudo /usr/sbin/alsactl -U restore -f "$tmp" "$CARD_ID" 2>/tmp/sg-da7212-alsactl.err; then
        return 0
    fi
    if sudo /usr/sbin/alsactl -U restore -f "$tmp" "$CARD_INDEX" 2>/tmp/sg-da7212-alsactl.err; then
        return 0
    fi
    cat /tmp/sg-da7212-alsactl.err >&2 || true
    return 1
}

amix() { amixer -c "$CARD_INDEX" cset name="$1" "$2" >/dev/null 2>&1 || true; }

# Keep this function minimal. It is used after the original working reference profile.
# It only reinforces the headset MIC input and does not rewrite unrelated routes.
fix_headset_mic_route() {
    amix 'Mic 1 Amp Source MUX' 'MIC_P'
    amix 'Mic 1 Switch' on
    amix 'Mic 1 Volume' 7
    amix 'Mic 2 Switch' off
    amix 'Aux Switch' off,off
    amix 'ADC Switch' on,on
    amix 'ADC Volume' 114,114
    amix 'Mixin PGA Switch' on,on
    amix 'Mixin PGA Volume' 0,0
    amix 'Mixin Left Mic 1 Switch' on
    amix 'Mixin Right Mic 1 Switch' on
    amix 'Mixin Left Mic 2 Switch' off
    amix 'Mixin Right Mic 2 Switch' off
    amix 'Mixin Left Aux Left Switch' off
    amix 'Mixin Right Aux Right Switch' off
}

fix_hp_playback_route() {
    amix 'DAC Left Source MUX' 'DAI Input Left'
    amix 'DAC Right Source MUX' 'DAI Input Right'
    amix 'DAC Switch' on,on
    amix 'DAC Volume' ${MAX_DAC_VOLUME},${MAX_DAC_VOLUME}
    amix 'Headphone Switch' on,on
    amix 'Headphone Volume' 58,58
    amix 'Mixout Left DAC Left Switch' on
    amix 'Mixout Right DAC Right Switch' on
    amix 'Lineout Switch' off
}

fix_speaker_playback_route() {
    # V34: keep playback DAC output at or below 100 to avoid DA7212/APA2068 overdrive.
    amix 'DAC Left Source MUX' 'DAI Input Left'
    amix 'DAC Right Source MUX' 'DAI Input Right'
    amix 'DAC Switch' on,on
    amix 'DAC Volume' ${MAX_DAC_VOLUME},${MAX_DAC_VOLUME}
}

fix_aux_capture_route() {
    amix 'Mic 1 Switch' off
    amix 'Mic 2 Switch' off
    amix 'Aux Switch' on,on
    amix 'Aux Volume' 55,55
    amix 'Mixin PGA Switch' on,on
    amix 'Mixin PGA Volume' 0,0
    amix 'ADC Switch' on,on
    amix 'ADC Volume' 112,112
    amix 'Mixin Left Aux Left Switch' on
    amix 'Mixin Right Aux Right Switch' on
    amix 'Mixin Left Mic 1 Switch' off
    amix 'Mixin Right Mic 1 Switch' off
    amix 'Mixin Left Mic 2 Switch' off
    amix 'Mixin Right Mic 2 Switch' off
    amix 'Headphone Switch' off,off
    amix 'Lineout Switch' off
}

fix_onboard_mic2_route() {
    # V32: schematic-based onboard MIC route.
    # U1 -> MIC2_P and U2 -> MIC2_N are two single-ended analog MEMS microphones,
    # not two independent stereo channels on DA7212.
    # V32 two-MIC default: onboard-mic uses Differential so U1/MIC2_P and U2/MIC2_N
    # participate together in one combined MIC2 recording path.
    # Use onboard-mic-p / onboard-mic-n for separate single-MIC tests.
    local source="${1:-MIC_P}"
    amix 'Mic 2 Amp Source MUX' "$source"
    amix 'Mic 1 Switch' off
    amix 'Mic 2 Switch' on
    amix 'Mic 2 Volume' 7
    amix 'Aux Switch' off,off
    amix 'ADC Switch' on,on
    amix 'ADC Volume' 114,114
    amix 'Mixin PGA Switch' on,on
    amix 'Mixin PGA Volume' 0,0
    amix 'Mixin Left Mic 1 Switch' off
    amix 'Mixin Right Mic 1 Switch' off
    amix 'Mixin Left Mic 2 Switch' on
    amix 'Mixin Right Mic 2 Switch' on
    amix 'Mixin Left Aux Left Switch' off
    amix 'Mixin Right Aux Right Switch' off
}

fix_onboard_mic1_route() {
    # Mono MIC1 test route copied to both left and right ADC paths.
    amix 'Mic 1 Amp Source MUX' 'MIC_P'
    amix 'Mic 1 Switch' on
    amix 'Mic 1 Volume' 7
    amix 'Mic 2 Switch' off
    amix 'Aux Switch' off,off
    amix 'ADC Switch' on,on
    amix 'ADC Volume' 127,127
    amix 'Mixin PGA Switch' on,on
    amix 'Mixin PGA Volume' 10,10
    amix 'Mixin Left Mic 1 Switch' on
    amix 'Mixin Right Mic 1 Switch' on
    amix 'Mixin Left Mic 2 Switch' off
    amix 'Mixin Right Mic 2 Switch' off
    amix 'Mixin Left Aux Left Switch' off
    amix 'Mixin Right Aux Right Switch' off
}

apply_profile() {
    require_card
    local name="$1" file src tmp
    file="$(profile_file "$name")" || {
        echo "ERROR: unknown profile: $name" >&2
        echo "Available: hp, headset-mic, headset-mic-ref, aux, onboard-mic, onboard-mic-p, onboard-mic-n, onboard-mic-diff, onboard-mic1, spk, speakers, all" >&2
        exit 2
    }
    src="$PROFILE_DIR/$file"
    [[ -f "$src" ]] || { echo "ERROR: missing profile: $src" >&2; exit 1; }
    tmp="$(make_temp_state "$src")"
    restore_state "$tmp" || { rm -f "$tmp"; echo "ERROR: failed to restore profile '$name'." >&2; exit 1; }
    rm -f "$tmp"

    case "$name" in
        hp|headphone) fix_hp_playback_route ;;
        headset-mic) fix_headset_mic_route ;;
        aux|aux-low) fix_aux_capture_route ;;
        onboard-mic|onboard-mic2|mic2|onboard-mic-diff) fix_onboard_mic2_route Differential ;;
        onboard-mic-p) fix_onboard_mic2_route MIC_P ;;
        onboard-mic-n) fix_onboard_mic2_route MIC_N ;;
        onboard-mic1|mic1) fix_onboard_mic1_route ;;
        spk|speaker|speakers|dual-speaker) fix_speaker_playback_route ;;
        headset-mic-ref) : ;; # Raw reference profile for comparison.
    esac

    echo "Applied profile '$name' to card $CARD_INDEX ($CARD_ID)."
    echo "PCM=$(pcm_device)"
}


# GPIO definitions from DA7212 Audio Codec HAT schematic.
# Two controllable LEDs are active-high through S8050 transistors:
#   green LED: BCM23 / signal GLED
#   blue LED : BCM24 / signal BLED
# Button is active-low:
#   user button: BCM27 / signal D27, pulled up to 3.3V and pressed to GND
GPIO_GREEN=23
GPIO_BLUE=24
GPIO_BUTTON=27

need_gpio_tool() {
    if command -v pinctrl >/dev/null 2>&1; then
        echo pinctrl
    elif command -v raspi-gpio >/dev/null 2>&1; then
        echo raspi-gpio
    else
        echo "ERROR: neither pinctrl nor raspi-gpio was found. Install Raspberry Pi GPIO tools or use Raspberry Pi OS Bookworm." >&2
        exit 1
    fi
}

gpio_set_output() {
    local gpio="$1" value="$2" tool
    tool="$(need_gpio_tool)"
    if [[ "$tool" == "pinctrl" ]]; then
        if [[ "$value" == "1" ]]; then
            pinctrl set "$gpio" op dh
        else
            pinctrl set "$gpio" op dl
        fi
    else
        if [[ "$value" == "1" ]]; then
            raspi-gpio set "$gpio" op dh
        else
            raspi-gpio set "$gpio" op dl
        fi
    fi
}

gpio_set_input_pullup() {
    local gpio="$1" tool
    tool="$(need_gpio_tool)"
    if [[ "$tool" == "pinctrl" ]]; then
        pinctrl set "$gpio" ip pu
    else
        raspi-gpio set "$gpio" ip pu
    fi
}

gpio_read_level() {
    local gpio="$1" tool out
    tool="$(need_gpio_tool)"
    if [[ "$tool" == "pinctrl" ]]; then
        out="$(pinctrl get "$gpio" 2>/dev/null || true)"
        if echo "$out" | grep -qi '\blo\b'; then echo 0; return 0; fi
        if echo "$out" | grep -qi '\bhi\b'; then echo 1; return 0; fi
    else
        out="$(raspi-gpio get "$gpio" 2>/dev/null || true)"
        if echo "$out" | grep -q 'level=0'; then echo 0; return 0; fi
        if echo "$out" | grep -q 'level=1'; then echo 1; return 0; fi
    fi
    echo "ERROR: failed to read GPIO$gpio." >&2
    return 1
}

led_gpio() {
    case "$1" in
        green|g|gled) echo "$GPIO_GREEN" ;;
        blue|b|bled) echo "$GPIO_BLUE" ;;
        all|both) echo "${GPIO_GREEN} ${GPIO_BLUE}" ;;
        *) echo "ERROR: unknown LED '$1'. Use: green, blue, or all." >&2; exit 2 ;;
    esac
}

led_command() {
    local led="${1:-}" action="${2:-}" count delay gpio
    [[ -n "$led" && -n "$action" ]] || {
        echo "Usage: sg-audio led <green|blue|all> <on|off|blink|toggle> [count]" >&2
        exit 2
    }
    case "$action" in
        on)
            for gpio in $(led_gpio "$led"); do gpio_set_output "$gpio" 1; done
            echo "LED $led: on" ;;
        off)
            for gpio in $(led_gpio "$led"); do gpio_set_output "$gpio" 0; done
            echo "LED $led: off" ;;
        toggle)
            for gpio in $(led_gpio "$led"); do
                if [[ "$(gpio_read_level "$gpio")" == "1" ]]; then gpio_set_output "$gpio" 0; else gpio_set_output "$gpio" 1; fi
            done
            echo "LED $led: toggled" ;;
        blink)
            count="${3:-5}"; delay="${SG_AUDIO_LED_DELAY:-0.25}"
            for ((i=0; i<count; i++)); do
                for gpio in $(led_gpio "$led"); do gpio_set_output "$gpio" 1; done
                sleep "$delay"
                for gpio in $(led_gpio "$led"); do gpio_set_output "$gpio" 0; done
                sleep "$delay"
            done
            echo "LED $led: blinked $count time(s)" ;;
        *)
            echo "Usage: sg-audio led <green|blue|all> <on|off|blink|toggle> [count]" >&2
            exit 2 ;;
    esac
}

button_command() {
    local action="${1:-status}" timeout start now level
    gpio_set_input_pullup "$GPIO_BUTTON"
    case "$action" in
        status|read)
            level="$(gpio_read_level "$GPIO_BUTTON")"
            if [[ "$level" == "0" ]]; then
                echo "Button D27 / GPIO27: pressed (LOW)"
            else
                echo "Button D27 / GPIO27: released (HIGH)"
            fi ;;
        wait)
            timeout="${2:-10}"
            echo "Waiting for button press on D27 / GPIO27 for ${timeout}s..."
            start="$(date +%s)"
            while true; do
                level="$(gpio_read_level "$GPIO_BUTTON")"
                if [[ "$level" == "0" ]]; then
                    echo "Button press detected."
                    return 0
                fi
                now="$(date +%s)"
                if (( now - start >= timeout )); then
                    echo "Timeout: no button press detected."
                    return 1
                fi
                sleep 0.05
            done ;;
        *)
            echo "Usage: sg-audio button [status|wait <seconds>]" >&2
            exit 2 ;;
    esac
}

gpio_status() {
    local g b btn
    gpio_set_input_pullup "$GPIO_BUTTON" >/dev/null 2>&1 || true
    g="$(gpio_read_level "$GPIO_GREEN" 2>/dev/null || echo '?')"
    b="$(gpio_read_level "$GPIO_BLUE" 2>/dev/null || echo '?')"
    btn="$(gpio_read_level "$GPIO_BUTTON" 2>/dev/null || echo '?')"
    echo "GPIO green LED: BCM${GPIO_GREEN}, level=${g} (active-high)"
    echo "GPIO blue LED : BCM${GPIO_BLUE}, level=${b} (active-high)"
    if [[ "$btn" == "0" ]]; then
        echo "GPIO button   : BCM${GPIO_BUTTON}, pressed (LOW, active-low)"
    elif [[ "$btn" == "1" ]]; then
        echo "GPIO button   : BCM${GPIO_BUTTON}, released (HIGH, active-low)"
    else
        echo "GPIO button   : BCM${GPIO_BUTTON}, level unknown"
    fi
}

set_default_alsa() {
    require_card
    local conf=/etc/asound.conf backup="${conf}.pre-sg-da7212-v34"
    [[ -f "$conf" && ! -f "$backup" ]] && sudo cp "$conf" "$backup"
    sudo tee "$conf" >/dev/null <<EOF2
# Seengreat DA7212 Audio Codec HAT default ALSA device
# Remove with: sudo sg-audio unset-default
pcm.!default {
    type plug
    slave.pcm "plughw:CARD=${CARD_ID},DEV=0"
}
ctl.!default {
    type hw
    card "${CARD_ID}"
}
EOF2
    echo "Default ALSA device set to CARD=${CARD_ID}."
    echo "Run: sg-audio hp"
    echo "Then: mpg123 -o alsa xxx.mp3"
}

unset_default_alsa() {
    local conf=/etc/asound.conf b
    for b in "${conf}.pre-sg-da7212-v34" "${conf}.pre-sg-da7212-v30" "${conf}.pre-sg-da7212-v29" "${conf}.pre-sg-da7212-v28" "${conf}.pre-sg-da7212-v27" "${conf}.pre-sg-da7212-v26" "${conf}.pre-sg-da7212-v25" "${conf}.pre-sg-da7212-v24" "${conf}.pre-sg-da7212-v23"; do
        if [[ -f "$b" ]]; then
            sudo cp "$b" "$conf"
            echo "Restored previous ALSA config from $b."
            return 0
        fi
    done
    if [[ -f "$conf" ]] && grep -q 'Seengreat DA7212\|sg-da7212\|plughw:CARD=.*DEV=0' "$conf" 2>/dev/null; then
        sudo rm -f "$conf"
        echo "Removed $conf. ALSA will fall back to system defaults."
    else
        echo "No Seengreat DA7212 default ALSA config found. Nothing changed."
    fi
}

volume_usage() {
    cat <<'EOF2'
Usage:
  sg-audio volume hp <0-63>       # headphone analog volume
  sg-audio volume spk <0-63>      # DA7212 SPK / Lineout output volume
  sg-audio volume dac <0-100>     # DAC playback volume, capped at 100
  sg-audio volume mic1 <0-7>      # headset MIC / MIC1 gain
  sg-audio volume mic2 <0-7>      # onboard MIC2 gain
  sg-audio volume aux <0-63>      # AUX input gain
  sg-audio volume adc <0-127>     # capture ADC volume
EOF2
}

set_volume() {
    require_card
    local target="${1:-}" val="${2:-}"
    [[ -n "$target" && -n "$val" ]] || { volume_usage; exit 2; }
    if [[ ! "$val" =~ ^[0-9]+$ ]]; then
        echo "ERROR: volume value must be an integer." >&2
        exit 2
    fi
    case "$target" in
        hp|headphone) amix 'Headphone Volume' "$val,$val" ;;
        spk|speaker|lineout) amix 'Lineout Volume' "$val" ;;
        dac)
            if (( val > MAX_DAC_VOLUME )); then
                echo "NOTE: requested DAC volume $val is above the V34 limit. Using ${MAX_DAC_VOLUME}." >&2
                val="$MAX_DAC_VOLUME"
            fi
            amix 'DAC Volume' "$val,$val" ;;
        mic1|headset-mic) amix 'Mic 1 Volume' "$val" ;;
        mic2|onboard-mic) amix 'Mic 2 Volume' "$val" ;;
        aux) amix 'Aux Volume' "$val,$val" ;;
        adc) amix 'ADC Volume' "$val,$val" ;;
        *) volume_usage; exit 2 ;;
    esac
    echo "Set $target volume to $val on card $CARD_INDEX ($CARD_ID)."
}

status() {
    echo "=== ALSA cards ==="
    cat /proc/asound/cards 2>/dev/null || true
    echo
    echo "=== detected DA7212 card ==="
    if CARD_ID="$(find_card_id || true)" && CARD_INDEX="$(find_card_index || true)" && [[ -n "${CARD_ID:-}" ]]; then
        echo "CARD_ID=$CARD_ID"
        echo "CARD_INDEX=$CARD_INDEX"
        echo "PCM=plughw:CARD=$CARD_ID,DEV=0"
    else
        echo "Not found"
    fi
    echo
    echo "=== playback devices ==="
    aplay -l || true
    echo
    echo "=== capture devices ==="
    arecord -l || true
    echo
    echo "=== GPIO status ==="
    gpio_status || true
    echo
    echo "=== config overlays ==="
    grep -nE 'dtparam=i2c_arm|dtparam=i2s|dtoverlay=.*(iqaudio|codec|da7212|seengreat|rpi-codeczero)' /boot/firmware/config.txt 2>/dev/null || true
    echo
    echo "=== current default ALSA config ==="
    cat /etc/asound.conf 2>/dev/null || echo "No /etc/asound.conf"
}

usage() {
    cat <<'EOF2'
Usage:
  sg-audio status
  sg-audio led <green|blue|all> <on|off|blink|toggle> [count]
  sg-audio button [status|wait <seconds>]
  sg-audio card             # print numeric card index
  sg-audio card-id          # print ALSA card ID, for example Zero or IQaudIOCODEC
  sg-audio pcm              # print plughw:CARD=<id>,DEV=0
  sg-audio hp | headset-mic | headset-mic-ref | aux | onboard-mic | onboard-mic-p | onboard-mic-n | onboard-mic-diff | onboard-mic1 | spk | speakers | all
  sg-audio controls
  sg-audio volume hp|spk|dac|mic1|mic2|aux|adc <value>  # dac is capped at 100
  sg-audio set-default
  sg-audio unset-default
  sg-audio save-profile <name>
EOF2
}

case "${1:-}" in
    status) status ;;
    card) require_card; echo "$CARD_INDEX" ;;
    card-id) require_card; echo "$CARD_ID" ;;
    pcm) pcm_device ;;
    hp|headphone|headset-mic|headset-mic-ref|aux|aux-low|onboard-mic|onboard-mic-p|onboard-mic-n|onboard-mic-diff|onboard-mic1|onboard-mic2|mic1|mic2|spk|speaker|speakers|dual-speaker|all)
        apply_profile "$1" ;;
    controls) require_card; amixer -c "$CARD_INDEX" scontrols ;;
    set-default) set_default_alsa ;;
    unset-default|restore-default|clear-default) unset_default_alsa ;;
    volume) set_volume "${2:-}" "${3:-}" ;;
    led) shift; led_command "$@" ;;
    button) shift; button_command "$@" ;;
    save-profile)
        require_card
        name="${2:-}"
        [[ -n "$name" ]] || { echo "ERROR: profile name required" >&2; exit 2; }
        [[ "$name" == *.state ]] || name="$name.state"
        sudo /usr/sbin/alsactl -U store -f "$PROFILE_DIR/$name" "$CARD_ID"
        echo "Saved: $PROFILE_DIR/$name" ;;
    *) usage; exit 2 ;;
esac
