#!/usr/bin/env bash

set -uo pipefail

usage() {
    echo "usage: peripheraldiag --fixture DIR --serial SERIAL [--repair]" >&2
}

fixture=
serial=
repair=false

while (($#)); do
    case $1 in
        --fixture)
            (($# >= 2)) || { usage; exit 64; }
            fixture=$2
            shift 2
            ;;
        --serial)
            (($# >= 2)) || { usage; exit 64; }
            serial=$2
            shift 2
            ;;
        --repair)
            repair=true
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            usage
            exit 64
            ;;
    esac
done

[[ -n $fixture && -n $serial ]] || { usage; exit 64; }

table=$fixture/devices.tsv
if [[ ! -r $table ]]; then
    echo "fixture: cannot read $table" >&2
    exit 66
fi

find_device() {
    awk -F '\t' -v wanted="$serial" '
        !/^#/ && $6 == wanted {
            print
            found = 1
            exit
        }
        END {
            if (!found) {
                exit 1
            }
        }
    ' "$table"
}

load_device() {
    local row
    if ! row=$(find_device); then
        echo "enumeration: missing (serial=$serial)"
        echo "diagnosis: enumeration"
        return 2
    fi

    IFS=$'\t' read -r port bus device vendor product found_serial authorized power_ma driver node mode group claim <<<"$row"
}

diagnose_loaded_device() {
    local power_ok=true
    local driver_ok=true
    local permissions_ok=true
    local claim_ok=true

    echo "enumeration: ok (port=$port bus=$bus device=$device id=$vendor:$product serial=$found_serial)"

    if [[ $authorized == 1 && $power_ma =~ ^[0-9]+$ ]] && ((power_ma <= 500)); then
        echo "power: ok (authorized=$authorized draw=${power_ma}mA)"
    else
        echo "power: blocked (authorized=$authorized draw=${power_ma}mA limit=500mA)"
        power_ok=false
    fi

    if [[ -n $driver && $driver != - ]]; then
        echo "driver: ok ($driver)"
    else
        echo "driver: blocked (unbound)"
        driver_ok=false
    fi

    if [[ $mode == 0660 && $group == plugdev ]]; then
        echo "permissions: ok (node=$node mode=$mode group=$group)"
    else
        echo "permissions: blocked (node=$node mode=$mode group=$group; need mode=0660 group=plugdev)"
        permissions_ok=false
    fi

    if [[ $claim != free ]]; then
        echo "application-claim: busy ($claim)"
        claim_ok=false
    elif [[ $permissions_ok == false ]]; then
        echo "application-claim: blocked (permissions)"
        claim_ok=false
    else
        echo "application-claim: ok (available)"
    fi

    if [[ $power_ok == false ]]; then
        echo "diagnosis: power"
    elif [[ $driver_ok == false ]]; then
        echo "diagnosis: driver"
    elif [[ $permissions_ok == false ]]; then
        echo "diagnosis: permissions"
    elif [[ $claim_ok == false ]]; then
        echo "diagnosis: application-claim"
    else
        echo "diagnosis: healthy"
        return 0
    fi

    return 1
}

repair_permissions() {
    local tmp_file=

    if [[ $authorized != 1 || ! $power_ma =~ ^[0-9]+$ ]] || ((power_ma > 500)); then
        echo "repair: refused while power is unhealthy" >&2
        return 3
    fi
    if [[ -z $driver || $driver == - ]]; then
        echo "repair: refused while the driver is unbound" >&2
        return 3
    fi
    if [[ $claim != free ]]; then
        echo "repair: refused while the device is claimed" >&2
        return 3
    fi
    if [[ $group != plugdev ]]; then
        echo "repair: refused; group remediation is outside this repair" >&2
        return 3
    fi
    if [[ $mode == 0660 ]]; then
        echo "repair: no permission change needed"
        return 0
    fi

    tmp_file=$(mktemp "${table}.tmp.XXXXXX") || return 74
    if ! awk -F '\t' -v OFS='\t' \
        -v vendor="$vendor" -v product="$product" -v serial="$serial" '
        /^#/ {
            print
            next
        }
        {
            if ($4 == vendor && $5 == product) {
                $11 = "0660"
            }
            print
        }
    ' "$table" >"$tmp_file"; then
        rm -f -- "$tmp_file"
        return 74
    fi

    if ! mv -- "$tmp_file" "$table"; then
        rm -f -- "$tmp_file"
        return 74
    fi
    echo "repair: set $node mode to 0660 for serial $serial"
}

load_device
load_status=$?
if ((load_status != 0)); then
    exit "$load_status"
fi

if [[ $repair == true ]]; then
    repair_permissions || exit $?
    load_device
    load_status=$?
    if ((load_status != 0)); then
        exit "$load_status"
    fi
fi

diagnose_loaded_device
