# shellcheck shell=bash
# udevadm(8) completion                                   -*- shell-script -*-
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# Copyright © 2010 Ran Benita
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# systemd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with systemd; If not, see <https://www.gnu.org/licenses/>.

__contains_word () {
    local w word=$1; shift
    for w in "$@"; do
        [[ $w = "$word" ]] && return
    done
}

__get_all_sysdevs() {
    local -a devs=(/sys/bus/*/devices/*/ /sys/class/*/*/)
    printf '%s\n' "${devs[@]%/}"
}

__get_all_device_nodes() {
    find /dev -xtype b -o -xtype c
}

__get_all_device_units() {
    systemctl list-units -t device --full --no-legend --no-pager --plain 2>/dev/null |
        { while read -r a b; do echo "$a"; done; }
}

__get_all_devices() {
    __get_all_sysdevs
    __get_all_device_nodes
    __get_all_device_units
}

__get_root() {
    local i

    for ((i=0; i < COMP_CWORD; i++)); do
        if [[ "${COMP_WORDS[i]}" = --root=* ]]; then
            echo "${COMP_WORDS[i]#--root=}"
            break
        fi
        if (( i > 0 )) && [[ "${COMP_WORDS[i-1]}" == "--root" ]]; then
            echo "${COMP_WORDS[i]}"
            break
        fi
    done
}

__get_udev_rules_files() {
    local root=$( __get_root )

    ls "$root"/usr/lib/udev/rules.d/*.rules \
       "$root"/usr/local/lib/udev/rules.d/*.rules \
       "$root"/run/udev/rules.d/*.rules \
       "$root"/etc/udev/rules.d/*.rules 2>/dev/null
}

__get_udev_rules_names() {
    local -a rules=( $( __get_udev_rules_files ) )
    printf '%s\n' "${rules[@]##*/}"
}

_udevadm() {
    local i verb comps builtin
    local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
    local -A OPTS=(
        [COMMON]='-h --help -V --version'
        [DEBUG]='-d --debug'
        [INFO_STANDALONE]='-r --root -a --attribute-walk -t --tree -x --export -e --export-db -c --cleanup-db
                           -w --wait-for-initialization --value --no-pager --initialized-match --initialized-nomatch'
        [INFO_ARG]='-q --query -p --path -n --name -P --export-prefix -d --device-id-of-file --property
                    --json --subsystem-match --subsystem-nomatch --attr-match --attr-nomatch --property-match
                    --tag-match --sysname-match --name-match --parent-match'
        [TRIGGER_STANDALONE]='-v --verbose -n --dry-run -q --quiet -w --settle --wait-daemon --uuid
                              --initialized-match --initialized-nomatch --include-parents'
        [TRIGGER_ARG]='-t --type -c --action -s --subsystem-match -S --subsystem-nomatch
                       -a --attr-match -A --attr-nomatch -p --property-match
                       -g --tag-match -y --sysname-match --name-match -b --parent-match
                       --prioritized-subsystem'
        [SETTLE]='-t --timeout -E --exit-if-exists'
        [CONTROL_STANDALONE]='-e --exit -s --stop-exec-queue -S --start-exec-queue -R --reload --ping --revert
                              --load-credentials'
        [CONTROL_ARG]='-l --log-priority -p --property -m --children-max -t --timeout --trace'
        [MONITOR_STANDALONE]='-k --kernel -u --udev -p --property'
        [MONITOR_ARG]='-s --subsystem-match -t --tag-match'
        [TEST_STANDALONE]='-v --verbose'
        [TEST_ARG]='-a --action -N --resolve-names -D --extra-rules-dir --json'
        [TEST_BUILTIN]='-a --action'
        [VERIFY_STANDALONE]='--no-summary --no-style'
        [VERIFY_ARG]='-N --resolve-names --root'
        [CAT_STANDALONE]='--tldr --config'
        [CAT_ARG]='--root'
        [WAIT]='-t --timeout --initialized=no --removed --settle'
        [LOCK]='-t --timeout -d --device -b --backing -p --print'
    )

    local verbs=(info trigger settle control monitor test-builtin test verify cat wait lock)
    local builtins=(blkid btrfs dissect_image factory_reset hwdb input_id keyboard kmod net_driver net_id net_setup_link path_id uaccess usb_id)

    for ((i=0; i < COMP_CWORD; i++)); do
        if __contains_word "${COMP_WORDS[i]}" "${verbs[@]}"; then
            verb=${COMP_WORDS[i]}
            break
        fi
    done

    if [[ -z ${verb-} ]]; then
        if [[ "$cur" = -* ]]; then
            COMPREPLY=( $(compgen -W '${OPTS[COMMON]} ${OPTS[DEBUG]}' -- "$cur") )
        else
            COMPREPLY=( $(compgen -W '${verbs[*]}' -- "$cur") )
        fi
        return 0
    fi

    case $verb in
        'info')
            if __contains_word "$prev" ${OPTS[INFO_ARG]}; then
                case $prev in
                    -q|--query)
                        comps='name symlink path property all'
                        ;;
                    -p|--path)
                        comps=$( __get_all_sysdevs )
                        local IFS=$'\n'
                        ;;
                    -n|--name)
                        comps=$( __get_all_device_nodes )
                        ;;
                    --json)
                        comps=$( udevadm info --json=help )
                        ;;
                    --parent-match)
                        comps=$( __get_all_sysdevs )
                        local IFS=$'\n'
                        ;;
                    --name-match)
                        comps=$( __get_all_device_nodes )
                        ;;
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            if [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[INFO_STANDALONE]} ${OPTS[INFO_ARG]}"
            else
                comps=$( __get_all_devices )
                local IFS=$'\n'
            fi
            ;;

        'trigger')
            if __contains_word "$prev" ${OPTS[TRIGGER_ARG]}; then
                case $prev in
                    -t|--type)
                        comps='all devices subsystems'
                        ;;
                    -c|--action)
                        comps=$( udevadm trigger --action help )
                        ;;
                    -y|--sysname-match|-b|--parent-match)
                        comps=$( __get_all_sysdevs )
                        local IFS=$'\n'
                        ;;
                    --name-match)
                        comps=$( __get_all_device_nodes )
                        ;;
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            if [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[TRIGGER_STANDALONE]} ${OPTS[TRIGGER_ARG]}"
            else
                comps=$( __get_all_devices )
                local IFS=$'\n'
            fi
            ;;

        'settle')
            if __contains_word "$prev" ${OPTS[SETTLE]}; then
                case $prev in
                    -E|--exit-if-exists)
                        comps=$( compgen -A file -- "$cur" )
                        ;;
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            comps="${OPTS[COMMON]} ${OPTS[SETTLE]}"
            ;;

        'control')
            if __contains_word "$prev" ${OPTS[CONTROL_ARG]}; then
                case $prev in
                    -l|--log-priority)
                        comps='alert crit debug emerg err info notice warning'
                        ;;
                    --trace)
                        comps='yes no'
                        ;;
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            comps="${OPTS[COMMON]} ${OPTS[CONTROL_STANDALONE]} ${OPTS[CONTROL_ARG]}"
            ;;

        'monitor')
            if __contains_word "$prev" ${OPTS[MONITOR_ARG]}; then
                case $prev in
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            comps="${OPTS[COMMON]} ${OPTS[MONITOR_STANDALONE]} ${OPTS[MONITOR_ARG]}"
            ;;

        'test')
            if __contains_word "$prev" ${OPTS[TEST_ARG]}; then
                case $prev in
                    -a|--action)
                        comps=$( udevadm test --action help )
                        ;;
                    -N|--resolve-names)
                        comps=$( udevadm test --resolve-names help )
                        ;;
                    -D|--extra-rules-dir)
                        comps=''
                        compopt -o dirnames
                        ;;
                    --json)
                        comps=$( udevadm test --json help )
                        ;;
                esac
            elif [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[TEST_ARG]} ${OPTS[TEST_STANDALONE]}"
            else
                comps=$( __get_all_devices )
                local IFS=$'\n'
            fi
            ;;

        'test-builtin')
            if __contains_word "$prev" ${OPTS[TEST_BUILTIN]}; then
                case $prev in
                    -a|--action)
                        comps=$( udevadm test-builtin --action help )
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            for ((i=0; i < COMP_CWORD; i++)); do
                if __contains_word "${COMP_WORDS[i]}" "${builtins[@]}"; then
                    builtin=${COMP_WORDS[i]}
                    break
                fi
            done

            if [[ -z $builtin ]]; then
                comps="${builtins[@]}"
            elif [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[TEST_BUILTIN]}"
            else
                comps=$( __get_all_devices )
                local IFS=$'\n'
            fi
            ;;

        'verify')
            if __contains_word "$prev" ${OPTS[VERIFY_ARG]}; then
                case $prev in
                    -N|--resolve-names)
                        comps=$( udevadm test --resolve-names help )
                        ;;
                    --root)
                        comps=''
                        compopt -o dirnames
                        ;;
                    *)
                        comps=''
                        ;;
                esac
            elif [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[VERIFY_ARG]} ${OPTS[VERIFY_STANDALONE]}"
            elif [[ $cur = */* ]]; then
                comps=$( __get_udev_rules_files )
                compopt -o dirnames
            else
                comps=$( __get_udev_rules_names )
                compopt -o default
            fi
            ;;

        'cat')
            if __contains_word "$prev" ${OPTS[CAT_ARG]}; then
                case $prev in
                    --root)
                        comps=''
                        compopt -o dirnames
                        ;;
                    *)
                        comps=''
                        ;;
                esac
            elif [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[CAT_ARG]} ${OPTS[CAT_STANDALONE]}"
            elif __contains_word "--config" ${COMP_WORDS[*]}; then
                comps="${OPTS[COMMON]} ${OPTS[CAT_ARG]} ${OPTS[CAT_STANDALONE]}"
            elif [[ $cur = */* ]]; then
                comps=$( __get_udev_rules_files )
                compopt -o dirnames
            else
                comps=$( __get_udev_rules_names )
                compopt -o default
            fi
            ;;

        'wait')
            if __contains_word "$prev" ${OPTS[WAIT]}; then
                case $prev in
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            if [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[WAIT]}"
            else
                comps=$( __get_all_devices )
                local IFS=$'\n'
            fi
            ;;

        'lock')
            if __contains_word "$prev" ${OPTS[LOCK]}; then
                case $prev in
                    *)
                        comps=''
                        ;;
                esac
                COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
                return 0
            fi

            if [[ $cur = -* ]]; then
                comps="${OPTS[COMMON]} ${OPTS[LOCK]}"
            else
                comps=''
            fi
            ;;

        *)
            comps=${VERBS[*]}
            ;;
    esac

    COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
    return 0
}

complete -F _udevadm udevadm
