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

# Physical USB ports on the RDKx5 carrier.
RADAR_PORT="1-1.4"
IMU_PORT="1-1.1"

# RADAR -> usb_ch343, IMU -> cdc_acm.
RADAR_DRIVER="usb_ch343"
IMU_DRIVER="cdc_acm"

DRIVER_ROOT="/sys/bus/usb/drivers"
MAX_WAIT_SECONDS="${MAX_WAIT_SECONDS:-30}"
WAIT_INTERVAL_SECONDS="${WAIT_INTERVAL_SECONDS:-1}"
INTERFACES=()

log() {
    local message="[radar-driver-switch] $*"
    if [ -w /dev/kmsg ]; then
        printf '%s\n' "$message" > /dev/kmsg
    else
        printf '%s\n' "$message"
    fi
}

load_driver() {
    local driver="$1"
    case "$driver" in
        cdc_acm)
            modprobe cdc_acm 2>/dev/null || true
            ;;
        usb_ch343)
            modprobe ch343 2>/dev/null || true
            ;;
    esac
}

driver_path() {
    printf '%s/%s\n' "$DRIVER_ROOT" "$1"
}

require_driver() {
    local driver="$1"
    local path
    path="$(driver_path "$driver")"
    if [ ! -d "$path" ]; then
        log "error: driver path missing: $path"
        exit 2
    fi
}

collect_interfaces() {
    local port="$1"
    shopt -s nullglob
    INTERFACES=(/sys/bus/usb/devices/"${port}":*)
    shopt -u nullglob
    [ "${#INTERFACES[@]}" -gt 0 ]
}

wait_for_interfaces() {
    local label="$1"
    local port="$2"
    local attempt=0
    while [ "$attempt" -le "$MAX_WAIT_SECONDS" ]; do
        if collect_interfaces "$port"; then
            return 0
        fi
        log "waiting for $label port $port interfaces (${attempt}s)"
        sleep "$WAIT_INTERVAL_SECONDS"
        attempt=$((attempt + WAIT_INTERVAL_SECONDS))
    done
    log "error: timed out waiting for $label port $port interfaces"
    exit 1
}

current_driver() {
    local iface="$1"
    local target
    target="$(readlink -f "$iface/driver" 2>/dev/null || true)"
    if [ -n "$target" ]; then
        basename "$target"
    fi
}

unbind_from_current_driver() {
    local iface="$1"
    local iface_name
    local driver
    iface_name="$(basename "$iface")"
    driver="$(current_driver "$iface")"
    if [ -z "$driver" ]; then
        return 0
    fi
    if [ ! -w "$(driver_path "$driver")/unbind" ]; then
        log "error: cannot unbind $iface_name from $driver"
        exit 5
    fi
    printf '%s\n' "$iface_name" > "$(driver_path "$driver")/unbind"
    log "unbound $iface_name from $driver"
}

bind_to_driver() {
    local iface="$1"
    local driver="$2"
    local iface_name
    local bind_path
    iface_name="$(basename "$iface")"
    bind_path="$(driver_path "$driver")/bind"
    if [ ! -w "$bind_path" ]; then
        log "error: cannot bind $iface_name to $driver"
        exit 6
    fi
    if printf '%s\n' "$iface_name" > "$bind_path" 2>/dev/null; then
        log "bound $iface_name to $driver"
    else
        log "warning: $iface_name did not bind to $driver"
    fi
}

ensure_port_bound() {
    local label="$1"
    local port="$2"
    local desired_driver="$3"
    local iface
    local iface_name
    local driver
    local bound_count=0

    wait_for_interfaces "$label" "$port"

    for iface in "${INTERFACES[@]}"; do
        iface_name="$(basename "$iface")"
        driver="$(current_driver "$iface")"
        if [ "$driver" = "$desired_driver" ]; then
            log "$label $iface_name already uses $desired_driver"
            bound_count=$((bound_count + 1))
            continue
        fi

        if [ -n "$driver" ]; then
            unbind_from_current_driver "$iface"
            sleep 0.2
        fi

        bind_to_driver "$iface" "$desired_driver"
        sleep 0.2
        driver="$(current_driver "$iface")"
        if [ "$driver" = "$desired_driver" ]; then
            bound_count=$((bound_count + 1))
        fi
    done

    if [ "$bound_count" -eq 0 ]; then
        log "error: no $label interface on $port is bound to $desired_driver"
        exit 7
    fi
}

main() {
    load_driver "$RADAR_DRIVER"
    load_driver "$IMU_DRIVER"
    require_driver "$RADAR_DRIVER"
    require_driver "$IMU_DRIVER"

    ensure_port_bound "RADAR" "$RADAR_PORT" "$RADAR_DRIVER"
    ensure_port_bound "IMU" "$IMU_PORT" "$IMU_DRIVER"

    udevadm settle --timeout=5 2>/dev/null || true
    log "done; serial devices: $(ls /dev/ttyACM* /dev/ttyCH343USB* 2>/dev/null | tr '\n' ' ')"
}

main "$@"
