# nmcli(1) completion                                      -*- shell-script -*-
# Originally based on
# https://github.com/GArik/bash-completion/blob/master/completions/nmcli

_nmcli_list()
{
    COMPREPLY=( $( compgen -W '$1' -- $cur ) )
}

_nmcli_list_nl()
{
    local IFS=$'\n'
    COMPREPLY=( $( compgen -W '$1' -- $cur ) )

    # Now escape special characters (spaces, single and double quotes),
    # so that the argument is really regarded a single argument by bash.
    # See http://stackoverflow.com/questions/1146098/properly-handling-spaces-and-quotes-in-bash-completion
    local escaped_single_quote="'\''"
    local i=0
    local entry
    for entry in ${COMPREPLY[*]}
    do
        if [[ "${cur:0:1}" == "'" ]]; then
            # started with single quote, escaping only other single quotes
            # [']bla'bla"bla\bla bla --> [']bla'\''bla"bla\bla bla
            COMPREPLY[$i]="${entry//\'/${escaped_single_quote}}"
        elif [[ "${cur:0:1}" == '"' ]]; then
            # started with double quote, escaping all double quotes and all backslashes
            # ["]bla'bla"bla\bla bla --> ["]bla'bla\"bla\\bla bla
            entry="${entry//\\/\\\\}"
            entry="${entry//\"/\\\"}"
            COMPREPLY[$i]="$entry"
        else
            # no quotes in front, escaping _everything_
            # [ ]bla'bla"bla\bla bla --> [ ]bla\'bla\"bla\\bla\ bla
            entry="${entry//\\/\\\\}"
            entry="${entry//\'/\'}"
            entry="${entry//\"/\\\"}"
            entry="${entry// /\\ }"
            COMPREPLY[$i]="$entry"
        fi
        (( i++ ))
    done

    # Work-around bash_completion issue where bash interprets a colon
    # as a separator.
    # Colon is escaped here. Change "\\:" back to ":".
    # See also:
    # http://stackoverflow.com/questions/28479216/how-to-give-correct-suggestions-to-tab-complete-when-my-words-contains-colons
    # http://stackoverflow.com/questions/2805412/bash-completion-for-maven-escapes-colon/12495727
    i=0
    for entry in ${COMPREPLY[*]}
    do
        entry="${entry//\\\\:/:}"
        COMPREPLY[$i]=${entry}
        (( i++ ))
    done
}

_nmcli_con_show()
{
    nmcli -t -f "$1" connection show $2 2> /dev/null
}

_nmcli_wifi_list()
{
    nmcli -t -f "$1" device wifi list 2>/dev/null
}

_nmcli_dev_status()
{
    nmcli -t -f "$1" device status 2>/dev/null
}

_nmcli_array_has_value() {
    # expects the name of an array as first parameter and
    # returns true if if one of the remaining arguments is
    # contained in the array ${$1[@]}
    eval "local ARRAY=(\"\${$1[@]}\")"
    local arg a
    shift
    for arg; do
        for a in "${ARRAY[@]}"; do
            if [[ "$a" = "$arg" ]]; then
                return 0
            fi
        done
    done
    return 1
}

_nmcli_array_delete_at()
{
    eval "local ARRAY=(\"\${$1[@]}\")"
    local i
    local tmp=()
    local lower=$2
    local upper=${3:-$lower}

    # for some reason the following fails. So this clumsy workaround...
    #   A=(a "")
    #   echo " >> ${#A[@]}"
    #    >> 2
    #   A=("${A[@]:1}")
    #   echo " >> ${#A[@]}"
    #    >> 0
    # ... seriously???

    for i in "${!ARRAY[@]}"; do
        if [[ "$i" -lt "$2" || "$i" -gt "${3-$2}" ]]; then
            tmp=("${tmp[@]}" "${ARRAY[$i]}")
        fi
    done
    eval "$1=(\"\${tmp[@]}\")"
}

_nmcli_compl_match_option()
{
    local S="$1"
    local V
    shift
    if [[ "${S:0:2}" == "--" ]]; then
        S="${S:2}"
    elif [[ "${S:0:1}" == "-" ]]; then
        S="${S:1}"
    fi
    for V; do
        case "$V" in
            "$S"*)
                printf "%s" "$V"
                return 0
                ;;
        esac
    done
    return 1
}

# OPTIONS appear first at the command line (before the OBJECT).
# This iterates over the argument list and tries to complete
# the options. If there are options that are to be completed,
# zero is returned and completion will be performed.
# Otherwise it will remove all the option parameters from the ${words[@]}
# array and return with zero (so that completion of OBJECT can continue).
_nmcli_compl_OPTIONS()
{
    local i W

    for (( ; ; )); do
        if [[ "${#words[@]}" -le 1 ]]; then
            return 1
        fi
        W="$(_nmcli_compl_match_option "${words[0]}" "${LONG_OPTIONS[@]}")"
        if [[ $? != 0 ]]; then
            return 2
        fi

        # remove the options already seen.
        for i in ${!LONG_OPTIONS[@]}; do
            if [[ "${LONG_OPTIONS[$i]}" == "$W" ]]; then
                _nmcli_array_delete_at LONG_OPTIONS $i
                break
            fi
        done

        if [[ "$HELP_ONLY_AS_FIRST" == '1' ]]; then
            for i in ${!LONG_OPTIONS[@]}; do
                if [[ "${LONG_OPTIONS[$i]}" == "help" ]]; then
                    _nmcli_array_delete_at LONG_OPTIONS $i
                    break
                fi
            done
        fi

        case "$W" in
            terse)
                _nmcli_array_delete_at words 0
                ;;
            pretty)
                _nmcli_array_delete_at words 0
                ;;
            ask)
                _nmcli_array_delete_at words 0
                ;;
            show-secrets)
                _nmcli_array_delete_at words 0
                ;;
            order)
                if [[ "${#words[@]}" -eq 2 ]]; then
                   local ord="${words[1]}"
                   local ord_sta=""
                   local i
                   local c=()

                   # FIXME: currently the completion considers colon as separator
                   # for words. Hence the following doesn't work as $ord will
                   # not contain any colons at this point.
                   # See https://bugzilla.gnome.org/show_bug.cgi?id=745157

                   if [[ $ord = *":"* ]]; then
                       ord_sta="${ord%:*}:"
                       ord="${ord##*:}"
                   fi
                   if [[ $ord = [-+]* ]]; then
                       ord_sta="$ord_sta${ord:0:1}"
                   fi
                   for i in active name type path; do
                       c=("${c[@]}" "$ord_sta$i")
                   done
                   _nmcli_list "${c[*]}"
                   return 0
                fi
                _nmcli_array_delete_at words 0 1
                ;;
            active)
                _nmcli_array_delete_at words 0
                ;;
            version)
                _nmcli_array_delete_at words 0
                ;;
            help)
                _nmcli_array_delete_at words 0
                if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                    HELP_ONLY_AS_FIRST=0
                    return 0
                fi
                HELP_ONLY_AS_FIRST=0
                ;;
            temporary)
                _nmcli_array_delete_at words 0
                ;;
            mode)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "tabular multiline"
                   return 0
                fi
                _nmcli_array_delete_at words 0 1
                ;;
            colors)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "yes no auto"
                   return 0
                fi
                _nmcli_array_delete_at words 0 1
                ;;
            fields)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "all common
                        NAME UUID TYPE TIMESTAMP TIMESTAMP-REAL AUTOCONNECT READONLY DBUS-PATH ACTIVE DEVICE STATE ACTIVE-PATH
                        connection 802-3-ethernet 802-1x 802-11-wireless 802-11-wireless-security ipv4 ipv6 serial ppp pppoe gsm cdma bluetooth 802-11-olpc-mesh vpn wimax infiniband bond vlan adsl bridge bridge-port team team-port dcb tun ip-tunnel macvlan vxlan
                        GENERAL IP4 DHCP4 IP6 DHCP6 VPN
                        profile active"
                   return 0
                fi
                _nmcli_array_delete_at words 0 1
                ;;
            escape)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "no yes"
                   return 0
                fi
                _nmcli_array_delete_at words 0 1
                ;;
            wait)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list ""
                   return 0
                fi
                _nmcli_array_delete_at words 0 1
                ;;
            *)
                # something unexpected. We are finished with parsing the OPTIONS.
                return 2
                ;;
        esac
    done
}

# after the OPTIONS, the OBJECT, the COMMAND and possible the COMMAND_CONNECTION, the syntax for nmcli
# expects several options with parameters. This function can parse them and remove them from the words array.
_nmcli_compl_ARGS()
{
    local aliases=${@}
    local OPTIONS_ALL N_REMOVE_WORDS REMOVE_OPTIONS OPTIONS_HAS_MANDATORY i
    OPTIONS_ALL=("${OPTIONS[@]}")
    OPTIONS_UNKNOWN_OPTION=

    OPTIONS_HAS_MANDATORY=0
    if [[ "${#OPTIONS_MANDATORY[@]}" -ge 1 ]]; then
        OPTIONS_HAS_MANDATORY=1
    fi

    for (( ; ; )); do
        if [[ "${#words[@]}" -le 1 ]]; then
            # we have no more words left...
            if [[ ${#OPTIONS[@]} -eq 0 ]]; then
                return 1;
            fi
            if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then
                _nmcli_list "$(echo "${OPTIONS[@]}")"
                return 0
            fi
            COMMAND_ARGS_WAIT_OPTIONS=0
            return 1
        fi
        if ! _nmcli_array_has_value OPTIONS_ALL "${words[0]}"; then
            # This is an entirely unknown option.
            OPTIONS_UNKNOWN_OPTION="?${words[0]}"
            return 1
        fi
        if [[ "$OPTIONS_HAS_MANDATORY" -eq 1 && "${#OPTIONS_MANDATORY[@]}" -eq 0 ]]; then
            # we had some mandatory options, but they are all satisfied... stop here...
            # This means, that we can continue with more additional options from the NEXT_GROUP.
            return 1
        fi

        N_REMOVE_WORDS=2
        REMOVE_OPTIONS=("${words[0]}")

        # change option name to alias
        WORD0="${words[0]}"
        for alias in "${aliases[@]}" ; do
            if [[ "${WORD0}" == ${alias%%:*} ]]; then
                WORD0=${alias#*:}
                break
            fi
        done

        case "${WORD0}" in
            level)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "OFF ERR WARN INFO DEBUG TRACE"
                   return 0
                fi
                ;;
            domains)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    local OPTIONS_DOM=(ALL DEFAULT PLATFORM RFKILL ETHER WIFI BT MB DHCP4 DHCP6 PPP WIFI_SCAN IP4 IP6 AUTOIP4 DNS VPN SHARING SUPPLICANT AGENTS SETTINGS SUSPEND CORE DEVICE OLPC WIMAX INFINIBAND FIREWALL ADSL BOND VLAN BRIDGE DBUS_PROPS TEAM CONCHECK DCB DISPATCH)
                    if [[ "${words[1]}" != "" ]]; then

                        # split the comma separaeted domain string into its parts LOGD
                        local oIFS="$IFS"
                        IFS=","
                        local LOGD=($(printf '%s' "${words[1]}" | sed 's/\(^\|,\)/,#/g'))
                        IFS="$oIFS"
                        unset oIFS

                        local LOGDLAST LOGDLAST_IS_OPTION LOGDI i
                        # first we iterate over all present domains and remove them from OPTIONS_DOM
                        for LOGDI in ${LOGD[@]}; do
                            LOGDI="${LOGDI:1}"
                            LOGDLAST="$LOGDI"
                            LOGDLAST_IS_OPTION=0
                            for i in ${!OPTIONS_DOM[*]}; do
                                if [[ "${OPTIONS_DOM[$i]}" = "$LOGDI" ]]; then
                                    LOGDLAST_IS_OPTION=1
                                    unset OPTIONS_DOM[$i]
                                fi
                            done
                        done

                        local OPTIONS_DOM2=()
                        if [[ "$LOGDLAST" = "" ]]; then
                            # we have a word that ends with ','. Just append all remaining options.
                            for i in ${!OPTIONS_DOM[*]}; do
                                OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]}${OPTIONS_DOM[$i]}"
                            done
                        else
                            # if the last option is not "" we keep only those option with the same prefix
                            # as the last domain (LOGDLAST)
                            for i in ${!OPTIONS_DOM[*]}; do
                                if [[ "${OPTIONS_DOM[$i]:0:${#LOGDLAST}}" == "$LOGDLAST" ]]; then
                                    # modify the option with the present prefix
                                    OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]}${OPTIONS_DOM[$i]:${#LOGDLAST}}"
                                fi
                            done

                            if [[ $LOGDLAST_IS_OPTION -eq 1 ]]; then
                                # if the last logd itself was a valid iption, ${words[1]} is itself a valid match
                                OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]}"

                                # also, add all remaining options by comma separated to the word.
                                for i in ${!OPTIONS_DOM[*]}; do
                                    OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]},${OPTIONS_DOM[$i]}"
                                done
                            fi
                            if [[ ${#OPTIONS_DOM2[@]} -eq 1 ]]; then
                                for i in ${!OPTIONS_DOM[*]}; do
                                    if [[ "$LOGDLAST" != "${OPTIONS_DOM[$i]:0:${#LOGDLAST}}" ]]; then
                                        OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${OPTIONS_DOM2[0]},${OPTIONS_DOM[$i]}"
                                    fi
                                done
                            fi

                        fi
                        OPTIONS_DOM=(${OPTIONS_DOM2[@]})
                    fi

                    _nmcli_list "$(echo "${OPTIONS_DOM[@]}")"
                   return 0
                fi
                ;;
            type)
                if [[ "$OPTIONS_TYPE" != "" ]]; then
                    return 1
                fi
                if [[ "${#words[@]}" -eq 2 ]]; then
                    if [[ "${words[1]:0:1}" = "8" ]]; then
                        # usually we don't want to show the 802-x types (because the shorter aliases are more
                        # user friendly. Only complete them, if the current word already starts with an "8".
                        _nmcli_list "802-3-ethernet 802-11-wireless 802-11-olpc-mesh"
                    else
                        _nmcli_list "ethernet wifi wimax gsm cdma infiniband bluetooth vpn olpc-mesh vlan bond bridge team pppoe adsl tun ip-tunnel macvlan vxlan"
                    fi
                    return 0
                fi
                OPTIONS_TYPE="${words[1]}"

                if [[ "x$OPTIONS_MANDATORY_IFNAME" != x ]]; then
                    # the ifname is not a mandatory option for a few connection types...
                    # Check, if we have such a 'type' and remove the 'ifname' from the list
                    # of mandatory options.
                    case "$OPTIONS_TYPE" in
                        vl|vla|vlan| \
                        bond| \
                        team| \
                        bridge)
                            for i in ${!OPTIONS_MANDATORY[*]}; do
                                if [[ "${OPTIONS_MANDATORY[$i]}" = "ifname" ]]; then
                                    unset OPTIONS_MANDATORY[$i]
                                fi
                            done
                            ;;
                        *)
                            ;;

                    esac
                    OPTIONS_MANDATORY_IFNAME=
                fi
                ;;
            master)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    if [[ "${words[1]}" = "" ]]; then
                        _nmcli_list_nl "$(_nmcli_dev_status DEVICE)"
                    else
                        _nmcli_list_nl "$(printf "%s\n%s\n%s" "$(_nmcli_dev_status DEVICE)" "$(_nmcli_con_show UUID)")"
                   fi
                   return 0
                fi
                ;;
            dev)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    if [[ "${words[1]}" = "" ]]; then
                        _nmcli_list_nl "$(_nmcli_dev_status DEVICE)"
                    else
                        _nmcli_list_nl "$(printf "%s\n%s\n%s" "$(_nmcli_dev_status DEVICE)" "$(_nmcli_wifi_list BSSID)" "$(_nmcli_con_show UUID)")"
                   fi
                   return 0
                fi
                ;;
            primary| \
            ifname)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list_nl "$(_nmcli_dev_status DEVICE)"
                   return 0
                fi
                ;;
            mode)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    case "$OPTIONS_TYPE" in
                        "wifi")
                            _nmcli_list "infrastructure ap adhoc"
                            ;;
                        "tun")
                            _nmcli_list "tun tap"
                            ;;
                        "ip-tunnel")
                            _nmcli_list "ipip gre sit isatap vti ip6ip6 ipip6 ip6gre vti6"
                            ;;
                        "macvlan")
                            _nmcli_list "vepa bridge private passthru source"
                            ;;
                        "bond"| \
                        *)
                        _nmcli_list "balance-rr active-backup balance-xor broadcast 802.3ad balance-tlb balance-alb"
                    esac
                    return 0
                fi
                ;;
            transport-mode)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "datagram connected"
                    return 0
                fi
                ;;
            vpn-type)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "vpnc openvpn pptp openconnect openswan libreswan ssh l2tp iodine"
                    return 0
                fi
                ;;
            slave-type)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "bond team bridge"
                    return 0
                fi
                ;;
            lacp-rate)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "slow fast"
                    return 0
                fi
                ;;
            bt-type)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "panu dun-gsm dun-cdma"
                    return 0
                fi
                ;;
            wep-key-type)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "key phrase"
                    return 0
                fi
                ;;
            managed| \
            autoconnect| \
            stp| \
            hairpin| \
            save| \
            hidden| \
            private| \
            pi| \
            vnet-hdr| \
            multi-queue|\
            tap)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "yes no"
                    return 0
                fi
                ;;
            config)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    compopt -o default
                    COMPREPLY=()
                    return 0
                fi
                ;;
            ip4| \
            ip6| \
            gw4| \
            gw6| \
            priority| \
            forward-delay| \
            hello-time| \
            max-age| \
            ageing-time| \
            nsp| \
            path-cost| \
            name| \
            mtu| \
            cloned-mac| \
            addr| \
            parent| \
            miimon| \
            arp-interval| \
            arp-ip-target| \
            downdelay| \
            updelay| \
            p-key| \
            mac| \
            id| \
            flags| \
            ingress| \
            dhcp-anycast| \
            channel| \
            egress| \
            apn| \
            con-name| \
            user| \
            username| \
            service| \
            password| \
            passwd-file| \
            file)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    return 0
                fi
                ;;
            ssid)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list_nl "$(_nmcli_wifi_list SSID)"
                    return 0
                fi
                ;;
            ap| \
            bssid)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list_nl "$(_nmcli_wifi_list BSSID)"
                    return 0
                fi
                ;;
            encapsulation)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "vcmux llc"
                    return 0
                fi
                ;;
            protocol)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "pppoa pppoe ipoatm"
                    return 0
                fi
                ;;
            band)
                if [[ "${#words[@]}" -eq 2 ]]; then
                    _nmcli_list "a bg"
                    return 0
                fi
                ;;
            *)
                return 1
                ;;
        esac


        if [[ "${#OPTIONS_NEXT_GROUP[@]}" -gt 0 ]]; then
            if _nmcli_array_has_value OPTIONS_NEXT_GROUP "${words[0]}"; then
                # the current value is from the next group...
                # We back off, because the current group is complete.
                return 1
            fi
        fi

        _nmcli_array_delete_at words 0 $((N_REMOVE_WORDS-1))
        # remove the options already seen.
        for i in ${!OPTIONS[*]}; do
            if [[ "${OPTIONS[$i]}" = "${REMOVE_OPTIONS[0]}" || "${OPTIONS[$i]}" = "${REMOVE_OPTIONS[1]}" ]]; then
                if ! _nmcli_array_has_value OPTIONS_REPEATABLE "${OPTIONS[$i]}" ; then
                    unset OPTIONS[$i]
                fi
            fi
        done
        for i in ${!OPTIONS_MANDATORY[*]}; do
            if [[ "${OPTIONS_MANDATORY[$i]}" = "${REMOVE_OPTIONS[0]}" || "${OPTIONS_MANDATORY[$i]}" = "${REMOVE_OPTIONS[1]}" ]]; then
                unset OPTIONS_MANDATORY[$i]
            fi
        done
    done
}

# some commands expect a connection as parameter. This connection can either be given
# as id|uuid|path|apath. Parse that connection parameter.
# Actually, it can also ask for a device name, like `nmcli device set [ifname] <ifname>`
_nmcli_compl_ARGS_CONNECTION()
{
    if ! _nmcli_array_has_value OPTIONS "${words[0]}"; then
        COMMAND_CONNECTION_TYPE=
        COMMAND_CONNECTION_ID="${words[0]}"
        _nmcli_array_delete_at words 0
        return 1
    fi
    COMMAND_CONNECTION_TYPE="${words[0]}"
    COMMAND_CONNECTION_ID="${words[1]}"
    local CON_TYPE=
    if [[ "x$COMMAND_CONNECTION_ACTIVE" != x ]]; then
        CON_TYPE=--active
    fi
    case "${words[0]}" in
        id)
            if [[ ${#words[@]} -le 2 ]]; then
                _nmcli_list_nl "$(_nmcli_con_show NAME $CON_TYPE)"
                return 0
            fi
            _nmcli_array_delete_at words 0 1
            ;;
        uuid)
            if [[ ${#words[@]} -le 2 ]]; then
                _nmcli_list_nl "$(_nmcli_con_show UUID $CON_TYPE)"
                return 0
            fi
            _nmcli_array_delete_at words 0 1
            ;;
        path)
            if [[ ${#words[@]} -le 2 ]]; then
                _nmcli_list_nl "$(_nmcli_con_show DBUS-PATH $CON_TYPE)"
                return 0
            fi
            _nmcli_array_delete_at words 0 1
            ;;
        apath)
            if [[ ${#words[@]} -le 2 ]]; then
                _nmcli_list_nl "$(_nmcli_con_show ACTIVE-PATH --active)"
                return 0
            fi
            _nmcli_array_delete_at words 0 1
            ;;
        ifname)
            if [[ ${#words[@]} -le 2 ]]; then
                _nmcli_list_nl "$(_nmcli_dev_status DEVICE)"
                return 0
            fi
            _nmcli_array_delete_at words 0 1
            ;;
        *)
            COMMAND_CONNECTION_TYPE=
            COMMAND_CONNECTION_ID="${words[0]}"
            _nmcli_array_delete_at words 0
            ;;
    esac
    return 1
}

_nmcli_compl_COMMAND() {
    local command="$1"
    shift
    local V=("$@")
    local H=
    if [[ "${command[0]:0:1}" != '-' ]]; then
        H=help
    elif [[ "${command[0]:1:1}" == '-' ||  "${command[0]}" == "-" ]]; then
        H=--help
    else
        H=-help
    fi
    if [[ "x$COMPL_COMMAND_NO_HELP" == x ]]; then
        V=("${V[@]}" "$H")
    fi
    _nmcli_list "${V[*]}"
}

_nmcli_compl_COMMAND_nl() {
    local command="$1"
    local a="$2"
    shift
    shift
    local V=("$@")
    local H=
    if [[ "${command[0]:0:1}" != '-' ]]; then
        V=("${V[@]/#/--}")
        H=help
    elif [[ "${command[0]:1:1}" == '-' ||  "${command[0]}" == "-" ]]; then
        V=("${V[@]/#/--}")
        H=--help
    else
        V=("${V[@]/#/-}")
        H=-help
    fi
    if [[ "x$COMPL_COMMAND_NO_HELP" == x ]]; then
        V=("${V[@]}" "$H")
    fi
    local IFS=$'\n'
    V="${V[*]}"
    _nmcli_list_nl "$(printf "%s%s\n%s" "" "$V" "$a")"
}

_nmcli_compl_PROPERTIES()
{
    while [[ "${#words[@]}" -gt 0 ]]; do
        if [[ ${#words[@]} -le 1 ]]; then
            local PREFIX=""

            if [[ "${words[0]:0:1}" == [+-] ]]; then
                PREFIX="${words[0]:0:1}"
            fi
            _nmcli_list_nl "$(echo -e 'print\nquit\nyes' |nmcli c edit "$@" 2>/dev/null |awk -F: '/\..*:/ {print "'$PREFIX'"$1}')"
            return 0
        elif [[ ${#words[@]} -le 2 ]]; then
            return 0
        fi
        _nmcli_array_delete_at words 0 1
    done
    _nmcli_list_nl "$(echo -e 'print\nquit\nyes' |nmcli c edit "$@" 2>/dev/null |awk -F: '/\..*:/ {print $1}')"
}

_nmcli()
{
    local cur prev words cword i
    _init_completion || return

    # we don't care about any arguments after the current cursor position
    # because we only parse from left to right. So, if there are some arguments
    # right of the cursor, just ignore them. Also don't care about ${words[0]}.
    _nmcli_array_delete_at words $((cword+1)) ${#words[@]}
    _nmcli_array_delete_at words 0

    # _init_completion returns the words with all the quotes and escaping
    # characters. We don't care about them, drop them at first.
    for i in ${!words[@]}; do
        words[i]="$(printf '%s' "${words[i]}" | xargs printf '%s\n' 2>/dev/null || true)"
    done

    # In case the cursor is not at the end of the line,
    # $cur consists of spaces that we want do remove.
    # For example: `nmcli connection modify id  <TAB>  lo`
    if [[ "$cur" =~ ^[[:space:]]+ ]]; then
        cur=''
    fi

    local OPTIONS_UNKNOWN_OPTION OPTIONS_TYPE OPTIONS_TYPED OPTIONS OPTIONS_MANDATORY COMMAND_ARGS_WAIT_OPTIONS OPTIONS_IP OPTIONS_MANDATORY OPTIONS_NEXT_GROUP OPTIONS_SEP OPTIONS_REPEATABLE
    local COMMAND_CONNECTION_TYPE COMMAND_CONNECTION_ID OPTIONS_MANDATORY_IFNAME HELP_ONLY_AS_FIRST
    local COMMAND_CONNECTION_ACTIVE=""

    HELP_ONLY_AS_FIRST=
    local LONG_OPTIONS=(terse pretty mode fields colors escape ask show-secrets wait version help)
    _nmcli_compl_OPTIONS
    i=$?

    if [[ "$HELP_ONLY_AS_FIRST" == '0' ]]; then
        # got a --help. No more completion.
        return 0
    fi

    case $i in
        0)
            return 0
            ;;
        1)
            # we show for completion either the (remaining) OPTIONS
            # (if the current word starts with a dash) or the OBJECT list
            # otherwise.
            if [[ "${words[0]:0:1}" != '-' ]]; then
                OPTIONS=(help general networking radio connection device agent monitor)
            elif [[ "${words[0]:1:1}" == '-' ||  "${words[0]}" == "-" ]]; then
                OPTIONS=("${LONG_OPTIONS[@]/#/--}")
            else
                OPTIONS=("${LONG_OPTIONS[@]/#/-}")
            fi
            _nmcli_list "${OPTIONS[*]}"
            return 0
            ;;
    esac

    local command="${words[1]}"
    case "${words[0]}" in
        h|he|hel|help)
            ;;
        g|ge|gen|gene|gener|genera|general)
            if [[ ${#words[@]} -eq 2 ]]; then
                _nmcli_compl_COMMAND "$command" status permissions logging hostname
            elif [[ ${#words[@]} -gt 2 ]]; then
                case "$command" in
                    ho|hos|host|hostn|hostna|hostnam|hostname)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" \
                                        "$(printf '%s\n%s\n%s\n' \
                                            "$(nmcli general hostname 2>/dev/null)" \
                                            "$(cat /etc/hostname 2>/dev/null)" \
                                            "$(hostnamectl status 2>/dev/null | sed -n '1s/^.\+hostname: \(.\+\)$/\1/p')" \
                                            "$HOSTNAME")"
                        fi
                        ;;
                    l|lo|log|logg|loggi|loggin|logging)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}" level domains
                        else
                            _nmcli_array_delete_at words 0 1
                            OPTIONS=(level domains)
                            _nmcli_compl_ARGS
                        fi
                        ;;
                    s|st|sta|stat|statu|status| \
                    p|pe|per|perm|permi|permis|permiss|permissi|permissio|permission|permissions)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}"
                        fi
                        ;;
                esac
            fi
            ;;
        n|ne|net|netw|netwo|networ|network|networki|networkin|networking)
            if [[ ${#words[@]} -eq 2 ]]; then
                _nmcli_compl_COMMAND "$command" on off connectivity
            elif [[ ${#words[@]} -eq 3 ]]; then
                case "$command" in
                    c|co|con|conn|conne|connec|connect|connecti|connectiv|connectivi|connectivit|connectivity)
                        _nmcli_compl_COMMAND "${words[2]}" "check"
                        ;;
                esac
            fi
            ;;
        r|ra|rad|radi|radio)
            if [[ ${#words[@]} -eq 2 ]]; then
                _nmcli_compl_COMMAND "$command" all wifi wwan
            elif [[ ${#words[@]} -eq 3 ]]; then
                case "$command" in
                    a|al|all | w|wi|wif|wifi | ww|wwa|wwan)
                        _nmcli_compl_COMMAND "${words[2]}" "on off"
                        ;;
                esac
            fi
            ;;
        c|co|con|conn|conne|connec|connect|connecti|connectio|connection)
            if [[ ${#words[@]} -eq 2 ]]; then
                _nmcli_compl_COMMAND "$command" show up down add modify clone edit delete monitor reload load import export
            elif [[ ${#words[@]} -gt 2 ]]; then
                case "$command" in
                    s|sh|sho|show)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME)")" active order
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help active order)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                            i=$?

                            if ! _nmcli_array_has_value LONG_OPTIONS active; then
                                COMMAND_CONNECTION_ACTIVE=1
                            fi

                            case $i in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        if [[ "x$COMMAND_CONNECTION_ACTIVE" = x ]]; then
                                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"

                                        else
                                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")" "${LONG_OPTIONS[@]}"
                                        fi
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS=(id uuid path apath)
                            while [[ ${#words[@]} -gt 0 ]]; do
                                _nmcli_compl_ARGS_CONNECTION && return 0
                            done
                            if [[ "x$COMMAND_CONNECTION_ACTIVE" = x ]]; then
                                _nmcli_list_nl "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME)")"
                            else
                                _nmcli_list_nl "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")"
                            fi
                        fi
                        ;;
                    u|up)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "ifname\nid\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")"
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS

                           case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "ifname\nid\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
                                    fi
                                    return 0
                                    ;;
                            esac

                            local COMMAND_CONNECTION_TYPE=''
                            OPTIONS=(ifname id uuid path)
                            _nmcli_compl_ARGS_CONNECTION && return 0

                            if [[ "$COMMAND_CONNECTION_TYPE" = "ifname" ]]; then
                                OPTIONS=(ap nsp passwd-file)
                            else
                                OPTIONS=(ifname ap nsp passwd-file)
                            fi
                            _nmcli_compl_ARGS
                        fi
                        ;;
                    d|do|dow|down)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")"
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                           case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")" "${LONG_OPTIONS[@]}"
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS=(id uuid path apath)
                            COMMAND_CONNECTION_ACTIVE=1
                            _nmcli_compl_ARGS_CONNECTION && return 0
                        fi
                        ;;
                    a|ad|add)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}" type ifname con-name autoconnect master slave-type
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                           case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND "${words[2]}" type ifname con-name autoconnect master slave-type
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS_TYPE=
                            OPTIONS=(type ifname con-name autoconnect save master slave-type)
                            OPTIONS_MANDATORY=(type ifname)
                            COMMAND_ARGS_WAIT_OPTIONS=1
                            OPTIONS_MANDATORY_IFNAME=1
                            _nmcli_compl_ARGS && return 0

                            OPTIONS_MANDATORY_IFNAME=
                            if _nmcli_array_has_value OPTIONS "${OPTIONS_MANDATORY[@]}"; then
                                # we still have some missing mandatory options...
                                if [[ "$OPTIONS_UNKNOWN_OPTION" != '' ]]; then
                                    if ! _nmcli_array_has_value OPTIONS "${OPTIONS_UNKNOWN_OPTION:1}"; then
                                        # if we encountered an unknown option while having mandatory
                                        # options, just return.
                                        return 0
                                    fi
                                fi
                                _nmcli_list "$(echo "${OPTIONS[@]}")"
                                return 0
                            fi

                            OPTIONS_IP=(ip4 ip6 gw4 gw6)
                            OPTIONS_SEP=(--)
                            OPTIONS_MANDATORY=()
                            case "$OPTIONS_TYPE" in
                                802-3|802-3-|802-3-e|802-3-et|802-3-eth|802-3-ethe|802-3-ether|802-3-ethern|802-3-etherne|802-3-ethernet| \
                                e|et|eth|ethe|ether|ethern|etherne|ethernet)
                                    OPTIONS_TYPE=ethernet
                                    OPTIONS_TYPED=(mac cloned-mac mtu)
                                    ;;
                                802-11-w|802-11-wi|802-11-wir|802-11-wire|802-11-wirel|802-11-wirele|802-11-wireles|802-11-wireless| \
                                wif|wifi)
                                    OPTIONS_TYPE=wifi
                                    OPTIONS_TYPED=(ssid mac cloned-mac mtu mode)
                                    OPTIONS_MANDATORY=(ssid)
                                    ;;
                                wim|wima|wimax)
                                    OPTIONS_TYPE=wimax
                                    OPTIONS_TYPED=(mac nsp)
                                    ;;
                                g|gs|gsm)
                                    OPTIONS_TYPE=gsm
                                    OPTIONS_TYPED=(apn user password)
                                    OPTIONS_MANDATORY=(apn)
                                    ;;
                                c|cd|cdm|cdma)
                                    OPTIONS_TYPE=cdma
                                    OPTIONS_TYPED=(user password)
                                    ;;
                                i|in|inf|infi|infin|infini|infinib|infiniba|infiniban|infiniband)
                                    OPTIONS_TYPE=infiniband
                                    OPTIONS_TYPED=(mac mtu transport-mode parent p-key)
                                    ;;
                                bl|blu|blue|bluet|blueto|bluetoo|bluetoot|bluetooth)
                                    OPTIONS_TYPE=bluetooth
                                    OPTIONS_TYPED=(addr bt-type)
                                    ;;
                                vl|vla|vlan)
                                    OPTIONS_TYPE=vlan
                                    OPTIONS_TYPED=(dev id flags ingress egress mtu)
                                    OPTIONS_MANDATORY=(dev)
                                    ;;
                                bond)
                                    OPTIONS_TYPE=bond
                                    OPTIONS_TYPED=(mode miimon downdelay updelay arp-interval arp-ip-target primary lacp-rate)
                                    ;;
                                team)
                                    OPTIONS_TYPE=team
                                    OPTIONS_TYPED=(config)
                                    ;;
                                bridge)
                                    OPTIONS_TYPE=bridge
                                    OPTIONS_TYPED=(stp priority forward-delay hello-time max-age ageing-time mac)
                                    ;;
                                vp|vpn)
                                    OPTIONS_TYPE=vpn
                                    OPTIONS_TYPED=(vpn-type user)
                                    OPTIONS_MANDATORY=(vpn-type)
                                    ;;
                                802-11-o|802-11-ol|802-11-olp|802-11-olpc|802-11-olpc-|802-11-olpc-m|802-11-olpc-me|802-11-olpc-mes|802-11-olpc-mesh| \
                                o|ol|olp|olpc|olpc-|olpc-m|olpc-me|olpc-mes|olpc-mesh)
                                    OPTIONS_TYPE=olpc-mesh
                                    OPTIONS_TYPED=(ssid channel dhcp-anycast)
                                    OPTIONS_MANDATORY=(ssid)
                                    ;;
                                p|pp|ppp|pppo|pppoe)
                                    OPTIONS_TYPE=pppoe
                                    OPTIONS_TYPED=(username password service mtu mac)
                                    OPTIONS_MANDATORY=(username)
                                    ;;
                                a|ad|ads|adsl)
                                    OPTIONS_TYPE=adsl
                                    OPTIONS_TYPED=(username password protocol encapsulation)
                                    OPTIONS_MANDATORY=(username protocol)
                                    ;;
                                tu|tun)
                                    OPTIONS_TYPE=tun
                                    OPTIONS_TYPED=(mode owner group pi vnet-hdr multi-queue)
                                    OPTIONS_MANDATORY=(mode)
                                    ;;
                                ip|ip-|ip-t|ip-tu|ip-tun|ip-tunn|ip-tunne|ip-tunnel)
                                    OPTIONS_TYPE=ip-tunnel
                                    OPTIONS_TYPED=(mode remote local dev)
                                    OPTIONS_MANDATORY=(mode remote)
                                    ;;
                                m|ma|mac|macv|macvl|macvla|macvlan)
                                    OPTIONS_TYPE=macvlan
                                    OPTIONS_TYPED=(dev mode tap)
                                    OPTIONS_MANDATORY=(dev mode)
                                    ;;
                                vx|vxl|vxla|vxlan)
                                    OPTIONS_TYPE=vxlan
                                    OPTIONS_TYPED=(id remote local dev source-port-min source-port-max destination-port)
                                    OPTIONS_MANDATORY=(id remote)
                                    ;;
                                *)
                                    # for an unknown connection type, we stop completion here
                                    return 0
                                    ;;
                            esac
                            if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then
                                # means, we are at the end of options. Nothing more to parse, just show
                                # what are the options now.
                                if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then
                                    _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}")"
                                else
                                    _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}") $(echo "${OPTIONS_IP[@]}") $(echo "${OPTIONS_SEP[@]}")"
                                fi
                                return 0
                            fi
                            if [[ "${#OPTIONS[@]}" -gt 0 ]]; then
                                # we still have some options from before, but no mandatory ones. Mix them with OPTIONS_TYPED
                                # and continue parsing the options...
                                OPTIONS=("${OPTIONS[@]}" "${OPTIONS_TYPED[@]}")
                                OPTIONS_NEXT_GROUP=("${OPTIONS_TYPED[@]}")
                                _nmcli_compl_ARGS && return 0

                                if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then
                                    # means, we are at the end of options. Nothing more to parse, just show
                                    # what are the options now.
                                    if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then
                                        _nmcli_list "$(echo "${OPTIONS[@]}")"
                                    else
                                        _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_IP[@]}") $(echo "${OPTIONS_SEP[@]}")"
                                    fi
                                    return 0
                                fi

                                if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then
                                    # there was an unknown option specified. Maybe we have to stop with the completion.
                                    if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then
                                        # we have an unknown option, but still mandatory ones that must be fullfiled first.
                                        return 0
                                    fi
                                    if ! (_nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}" ||
                                          _nmcli_array_has_value OPTIONS_SEP "${OPTIONS_UNKNOWN_OPTION:1}"); then
                                        # the unknown option is neither an IP option nor a separator.
                                        return 0
                                    fi
                                    # The unknown option is an IP option or a separator, which is fine... continue...
                                fi

                            fi
                            OPTIONS=("${OPTIONS_TYPED[@]}")
                            OPTIONS_NEXT_GROUP=()

                            if [[ "${#OPTIONS_MANDATORY[@]}" -ge 1 ]]; then
                                # we have some mandatory options... don't check for IP options yet...
                                _nmcli_compl_ARGS && return 0

                                if _nmcli_array_has_value OPTIONS "${OPTIONS_MANDATORY[@]}"; then
                                    _nmcli_list "$(echo "${OPTIONS[@]}")"
                                    return 0
                                fi

                                if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then
                                    if ! (_nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}" ||
                                          _nmcli_array_has_value OPTIONS_SEP "${OPTIONS_UNKNOWN_OPTION:1}"); then
                                        # the unknown option is neither an IP option nor a separator.
                                        return 0
                                    fi
                                fi
                            fi


                            # no mandatory options... do final completion including IP options
                            OPTIONS=("${OPTIONS[@]}" "${OPTIONS_IP[@]}" "${OPTIONS_SEP[@]}")
                            OPTIONS_NEXT_GROUP=("${OPTIONS_IP[@]}" "${OPTIONS_SEP[@]}")
                            _nmcli_compl_ARGS && return 0

                            if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then
                                return 0
                            fi

                            if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then
                                # means, we are at the end of options. Nothing more to parse, just show
                                # what are the options now.
                                _nmcli_list "$(echo "${OPTIONS[@]}")"
                                return 0
                            fi


                            # process the last group of options, as the OPTIONS_TYPED are already handled...
                            OPTIONS=("${OPTIONS_IP[@]}" "${OPTIONS_SEP[@]}")
                            OPTIONS_NEXT_GROUP=("${OPTIONS_SEP[@]}")
                            COMMAND_ARGS_WAIT_OPTIONS=0
                            _nmcli_compl_ARGS && return 0

                            _nmcli_array_delete_at words 0
                            _nmcli_compl_PROPERTIES type "$OPTIONS_TYPE"

                            return 0

                        fi
                        ;;
                    e|ed|edi|edit)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\ntype\ncon-name\n%s" "$(_nmcli_con_show NAME)")"
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS

                           case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\ntype\ncon-name\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
                                    fi
                                    return 0
                                    ;;
                            esac

                            if [[ "${words[0]}" = 'type' || "${words[0]}" = 'con-name' ]]; then
                                OPTIONS=(type con-name)
                                _nmcli_compl_ARGS
                            else
                                OPTIONS=(id uuid path apath)
                                _nmcli_compl_ARGS_CONNECTION
                            fi
                        fi
                        ;;
                    m|mo|mod|modi|modif|modify)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" temporary
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help temporary)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                            case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS=(id uuid path)
                            _nmcli_compl_ARGS_CONNECTION && return 0

                            _nmcli_compl_PROPERTIES ${COMMAND_CONNECTION_TYPE} "$COMMAND_CONNECTION_ID"

                            return 0
                        fi
                        ;;
                    c|cl|clo|clon|clone)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" temporary
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help temporary)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                            case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS=(id uuid path)
                            _nmcli_compl_ARGS_CONNECTION && return 0

                            return 0
                        fi
                        ;;

                    de|del|dele|delet|delete| \
                    mon|moni|monit|monito|monitor)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")"
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help)
                            _nmcli_compl_OPTIONS
                            case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if ! _nmcli_array_has_value LONG_OPTIONS "help"; then
                                        return 0
                                    fi
                                    ;;
                            esac

                            OPTIONS=(id uuid path apath)
                            while [[ ${#words[@]} -gt 0 ]]; do
                                _nmcli_compl_ARGS_CONNECTION && return 0
                            done
                            _nmcli_list_nl "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")"
                        fi
                        ;;
                    l|lo|loa|load)
                        if [[ ${#words[@]} -gt 2 ]]; then
                            # we should also complete for help/--help, but who to mix that
                            # with file name completion?
                            compopt -o default
                            COMPREPLY=()
                        fi
                        ;;
                    i|im|imp|impo|impor|import)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}" type file --temporary
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help temporary)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                            case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND "${words[2]}" type file
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS=(type file)
                            OPTIONS_MANDATORY=(type file)
                            ALIASES=("type:vpn-type")
                            _nmcli_compl_ARGS ${ALIASES[@]}
                            return 0
                        fi
                        ;;
                    e|ex|exp|expo|expor|export)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")"
                        elif [[ ${#words[@]} -gt 3 ]]; then
                            _nmcli_array_delete_at words 0 1

                            LONG_OPTIONS=(help)
                            HELP_ONLY_AS_FIRST=1
                            _nmcli_compl_OPTIONS
                            case $? in
                                0)
                                    return 0
                                    ;;
                                1)
                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
                                        _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
                                    fi
                                    return 0
                                    ;;
                            esac

                            OPTIONS=(id uuid path)
                            _nmcli_compl_ARGS_CONNECTION && return 0
                            return 0
                        fi
                        ;;

                esac
            fi
            ;;
        d|de|dev|devi|devic|device)
            if [[ ${#words[@]} -eq 2 ]]; then
                _nmcli_compl_COMMAND "$command" status show connect reapply disconnect delete monitor wifi set lldp
            elif [[ ${#words[@]} -gt 2 ]]; then
                case "$command" in
                    s|st|sta|stat|statu|status)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}"
                        fi
                        ;;
                    sh|sho|show| \
                    r|re|rea|reap|reapp|reappl|reapply| \
                    c|co|con|conn|conne|connec|connect)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(_nmcli_dev_status DEVICE)"
                        fi
                        ;;
                    d|di|dis|disc|disco|discon|disconn|disconne|disconnec|disconnect| \
                    de|del|dele|delet|delete| \
                    m|mo|mon|moni|monit|monito|monitor)
                        if [[ ${#words[@]} -ge 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(_nmcli_dev_status DEVICE)"
                        fi
                        ;;
                    se|set)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "ifname\n%s" "$(_nmcli_dev_status DEVICE)")"
                        else
                            _nmcli_array_delete_at words 0 1
                            OPTIONS=(ifname)
                            _nmcli_compl_ARGS_CONNECTION && return 0
                            OPTIONS=(autoconnect managed)
                            _nmcli_compl_ARGS
                        fi
                        ;;
                    w|wi|wif|wifi)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}" list connect hotspot rescan
                        else
                            case "${words[2]}" in
                                l|li|lis|list)
                                    _nmcli_array_delete_at words 0 2
                                    OPTIONS=(ifname bssid)
                                    _nmcli_compl_ARGS
                                    ;;
                                c|co|con|conn|conne|connec|connect)
                                    if [[ ${#words[@]} -eq 4 ]]; then
                                        if [[ "${words[3]}" = "" ]]; then
                                            _nmcli_list_nl "$(_nmcli_wifi_list SSID)"
                                        else
                                            _nmcli_list_nl "$(printf "%s\n%s" "$(_nmcli_wifi_list SSID)" "$(_nmcli_wifi_list BSSID)")"
                                        fi
                                    else
                                        _nmcli_array_delete_at words 0 3
                                        local OPTIONS=(password wep-key-type ifname bssid name private hidden)
                                        _nmcli_compl_ARGS
                                    fi
                                    ;;
                                h|ho|hot|hots|hotsp|hotspo|hotspot)
                                    _nmcli_array_delete_at words 0 2
                                    OPTIONS=(ifname con-name ssid band channel password)
                                    _nmcli_compl_ARGS
                                    ;;
                                r|re|res|resc|resca|rescan)
                                    _nmcli_array_delete_at words 0 2
                                    OPTIONS_REPEATABLE=(ssid)
                                    OPTIONS=(ifname ssid)
                                    _nmcli_compl_ARGS
                                    ;;
                            esac
                        fi
                        ;;
                    l|ll|lld|lldp)
                        if [[ ${#words[@]} -eq 3 ]]; then
                            _nmcli_compl_COMMAND "${words[2]}" list
                        else
                            case "${words[2]}" in
                                l|li|lis|list)
                                    _nmcli_array_delete_at words 0 2
                                    OPTIONS=(ifname)
                                    _nmcli_compl_ARGS
                                    ;;
                            esac
                        fi
                        ;;
                esac
            fi
            ;;
        a|ag|age|agen|agent)
            if [[ ${#words[@]} -eq 2 ]]; then
                _nmcli_compl_COMMAND "$command" secret polkit all
            fi
            ;;
        m|mo|mon|moni|monit|monito|monitor)
            ;;
    esac

    return 0
} &&
complete -F _nmcli nmcli

# ex: ts=4 sw=4 et filetype=sh
