#!/bin/bash
#
# Tab completion for voxl-elrs. The binary works out the candidates
# ("voxl-elrs __complete <words...>") from its own command table, the preset
# directory, the firmware catalog and the per-boot parameter cache, so this
# script only passes the words in and the candidates out. It never talks to a
# device.

# Removes shell quoting from a word: "a b", 'a b' and a\ b all become a b
_voxl_elrs_dequote()
{
    local in=$1 out="" quote="" c i
    for (( i = 0; i < ${#in}; i++ )); do
        c=${in:i:1}
        if [[ -n $quote ]]; then
            if [[ $c == "$quote" ]]; then
                quote=""
                continue
            fi
            if [[ $quote == '"' && $c == '\' && $(( i + 1 )) -lt ${#in} ]]; then
                (( i++ ))
                c=${in:i:1}
            fi
            out+=$c
        elif [[ $c == "'" || $c == '"' ]]; then
            quote=$c
        elif [[ $c == '\' && $(( i + 1 )) -lt ${#in} ]]; then
            (( i++ ))
            out+=${in:i:1}
        else
            out+=$c
        fi
    done
    printf '%s' "$out"
}

# Directories and firmware files (.bin, .mrc, .mrcb, .mrcbundle) for a word
_voxl_elrs_firmware_files()
{
    local had_extglob=0
    shopt -q extglob && had_extglob=1
    shopt -s extglob
    compgen -d -- "$1"
    compgen -f -X '!*.@(bin|mrc|mrcb|mrcbundle)' -- "$1"
    (( had_extglob )) || shopt -u extglob
}

_voxl_elrs()
{
    local cur cword i
    local -a words
    if declare -F _get_comp_words_by_ref >/dev/null 2>&1; then
        # Keep "mpa:crsf_raw" and "--opt=value" in one word
        _get_comp_words_by_ref -n := cur words cword
    else
        # Rejoin the pieces readline split at ':' and '=', e.g. "mpa" ":" "crsf"
        local w
        words=()
        for (( i = 0; i <= COMP_CWORD; i++ )); do
            w=${COMP_WORDS[i]}
            if (( i > 0 )) && [[ $w == [:=] || ${words[-1]} == *[:=] ]]; then
                words[-1]+=$w
            else
                words+=("$w")
            fi
        done
        cword=$(( ${#words[@]} - 1 ))
        cur=${words[cword]}
    fi

    local -a args=()
    for (( i = 1; i < cword; i++ )); do
        args+=("$(_voxl_elrs_dequote "${words[i]}")")
    done
    args+=("$(_voxl_elrs_dequote "$cur")")

    local prog=${words[0]}
    command -v "$prog" >/dev/null 2>&1 || prog=voxl-elrs

    local line files=0 firmware_files=0 filenames=0 nospace=0
    COMPREPLY=()
    while IFS= read -r line; do
        case $line in
            @files) files=1 ;;
            @firmware-files) firmware_files=1 ;;
            @filenames) filenames=1 ;;
            @nospace) nospace=1 ;;
            *) COMPREPLY+=("$line") ;;
        esac
    done < <("$prog" __complete "${args[@]}" 2>/dev/null)

    if (( files )); then
        filenames=1
        while IFS= read -r line; do
            COMPREPLY+=("$line")
        done < <(compgen -f -- "$(_voxl_elrs_dequote "$cur")")
    fi
    if (( firmware_files )); then
        filenames=1
        while IFS= read -r line; do
            COMPREPLY+=("$line")
        done < <(_voxl_elrs_firmware_files "$(_voxl_elrs_dequote "$cur")")
    fi
    # File name handling quotes names with spaces, e.g. "Output Mapping/"
    (( filenames )) && compopt -o filenames 2>/dev/null
    (( nospace )) && compopt -o nospace 2>/dev/null

    # Readline completes only the text after a ':', so drop what comes before it
    if [[ $cur == *:* && $COMP_WORDBREAKS == *:* ]]; then
        local prefix=${cur%"${cur##*:}"}
        for i in "${!COMPREPLY[@]}"; do
            COMPREPLY[i]=${COMPREPLY[i]#"$prefix"}
        done
    fi
    return 0
}

complete -F _voxl_elrs voxl-elrs
