SAVE_IFS=$IFS
IFS="
"
_OVSDB_SERVER_LOCATION=""

# Run ovs-vsctl and make sure that ovs-vsctl is always called with
# the correct --db argument.
_ovs_vsctl () {
    local _db

    if [ -n "$_OVSDB_SERVER_LOCATION" ]; then
        _db="--db=$_OVSDB_SERVER_LOCATION"
    fi
    ovs-vsctl ${_db} "$@"
}

# ovs-vsctl --commands outputs in this format:
#
# main = <localopts>,<name>,<options>
# localopts = ([<localopt>] )*
# localopt = --[^]]*
# name = [^,]*
# arguments = ((!argument|?argument|*argument|+argument) )*
# argument = ([^ ]*|argument\|argument)
#
# The [] characters in local options are just delimiters.  The
# argument prefixes mean:
#   !argument :: The argument is required
#   ?argument :: The argument is optional
#   *argument :: The argument may appear any number (0 or more) times
#   +argument :: The argument may appear one or more times
# A bar (|) character in an argument means thing before bar OR thing
# after bar; for example, del-port can take a port or an interface.

_OVS_VSCTL_COMMANDS=
_OVS_VSCTL_OPTIONS=
if command -v ovs-vsctl > /dev/null; then
    _OVS_VSCTL_COMMANDS="$(_ovs_vsctl --commands)"

    # This doesn't complete on short arguments, so it filters them out.
    _OVS_VSCTL_OPTIONS="$(_ovs_vsctl --options | awk '/^--/ { print $0 }' \
                          | sed -e 's/\(.*\)=ARG/\1=/')"
fi
IFS=$SAVE_IFS

declare -A _OVS_VSCTL_PARSED_ARGS
declare -A _OVS_VSCTL_NEW_RECORDS

# This is a convenience function to make sure that user input is
# looked at as a fixed string when being compared to something.  $1 is
# the input; this behaves like 'grep "^$1"' but deals with regex
# metacharacters in $1.
_ovs_vsctl_check_startswith_string () {
    awk 'thearg == "" || index($0, thearg)==1' thearg="$1"
}

# $1 = word to complete on.
# Complete on global options.
_ovs_vsctl_bashcomp_globalopt () {
    local options result

    options=""
    result=$(printf "%s\n" "${_OVS_VSCTL_OPTIONS}" \
             | _ovs_vsctl_check_startswith_string "${1%=*}")
    if [[ $result =~ "=" ]]; then
        options="NOSPACE"
    fi
    printf -- "${options}\nEO\n${result}"
}

# $1 = word to complete on.
# Complete on local options.
_ovs_vsctl_bashcomp_localopt () {
    local options result possible_opts

    possible_opts=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}" | cut -f1 -d',')
    # This finds all options that could go together with the
    # already-seen ones
    for prefix_arg in $1; do
        possible_opts=$(printf "%s\n" "$possible_opts" \
                        | grep -- "\[${prefix_arg%%=*}=\?\]")
    done
    result=$(printf "%s\n" "${possible_opts}" \
             | tr ' ' '\n' | tr -s '\n' | sort | uniq)
    # This removes the already-seen options from the list so that
    # users aren't completed for the same option twice.
    for prefix_arg in $1; do
        result=$(printf "%s\n" "${result}" \
                 | grep -v -- "\[${prefix_arg%%=*}=\?\]")
    done
    result=$(printf "%s\n" "${result}" | sed -ne 's/\[\(.*\)\]/\1/p' \
             | _ovs_vsctl_check_startswith_string "$2")
    if [[ $result =~ "=" ]]; then
        options="NOSPACE"
    fi
    printf -- "${options}\nEO\n${result}"
}

# $1 = given local options.
# $2 = word to complete on.
# Complete on command that could contain the given local options.
_ovs_vsctl_bashcomp_command () {
    local result possible_cmds

    possible_cmds=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}")
    for prefix_arg in $1; do
        possible_cmds=$(printf "%s\n" "$possible_cmds" \
                        | grep -- "\[$prefix_arg=\?\]")
    done
    result=$(printf "%s\n" "${possible_cmds}" \
             | cut -f2 -d',' \
             | _ovs_vsctl_check_startswith_string "$2")
    printf -- "${result}"
}

# $1 = completion result to check.
# Return 0 if the completion result is non-empty, otherwise return 1.
_ovs_vsctl_detect_nonzero_completions () {
    local tmp newarg

    newarg=${1#*EO}
    readarray tmp <<< "$newarg"
    if [ "${#tmp[@]}" -eq 1 ] && [ "${#newarg}" -eq 0 ]; then
        return 1
    fi
    return 0
}

# $1 = argument format to expand.
# Expand '+ARGUMENT' in argument format to '!ARGUMENT *ARGUMENT'.
_ovs_vsctl_expand_command () {
    result=$(printf "%s\n" "${_OVS_VSCTL_COMMANDS}" \
             | grep -- ",$1," | cut -f3 -d',' | tr ' ' '\n' \
             | awk '/\+.*/ { name=substr($0,2);
                             print "!"name; print "*"name; next; }
                    1')
    printf -- "${result}\n!--"
}

# $1 = word to complete on.
# Complete on table.
_ovs_vsctl_complete_table () {
    local result

    result=$(ovsdb-client --no-heading list-tables $_OVSDB_SERVER_LOCATION Open_vSwitch \
        | _ovs_vsctl_check_startswith_string "$1")
    printf -- "EO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on record.  Provide both the name and uuid.
_ovs_vsctl_complete_record () {
    local table uuids names new_record

    table="${_OVS_VSCTL_PARSED_ARGS[TABLE]}"
    new_record="${_OVS_VSCTL_NEW_RECORDS[${table^^}]}"
    # Tables should always have an _uuid column
    uuids=$(_ovs_vsctl --no-heading -f table -d bare --columns=_uuid \
                      list $table | _ovs_vsctl_check_startswith_string "$1")
    # Names don't always exist, silently ignore if the name column is
    # unavailable.
    names=$(_ovs_vsctl --no-heading -f table -d bare \
                      --columns=name list $table \
                      2>/dev/null \
            | _ovs_vsctl_check_startswith_string "$1")
    printf -- "EO\n%s\n%s\n%s\n" "${uuids}" "${names}" "${new_record}"
}

# $1 = word to complete on.
# Complete on bridge.
_ovs_vsctl_complete_bridge () {
    local result

    result=$(_ovs_vsctl list-br | _ovs_vsctl_check_startswith_string "$1")
    printf -- "EO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on port.  If a bridge has already been specified,
# just complete for that bridge.
_ovs_vsctl_complete_port () {
    local ports result

    if [ -n "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}" ]; then
        ports=$(_ovs_vsctl list-ports "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}")
    else
        local all_ports
        all_ports=$(_ovs_vsctl --format=table \
                              --no-headings \
                              --columns=name \
                              list Port)
        ports=$(printf "$all_ports" | tr -d '" ' | sort -u)
    fi
    result=$(_ovs_vsctl_check_startswith_string "$1" <<< "$ports")
    printf -- "EO\n%s\n" "${result}"
}

# $1:  Atom to complete (as usual)
# $2:  Table to complete the key in
# $3:  Column to find keys in
# $4:  Prefix for each completion
# Complete on key based on given table and column info.
_ovs_vsctl_complete_key_given_table_column () {
    local keys

    keys=$(_ovs_vsctl --no-heading --columns="$3" list \
                     "$2" \
           | tr -d '{\"}' | tr -s ', ' '\n' | cut -d'=' -f1 \
           | xargs printf "$4%s\n" | _ovs_vsctl_check_startswith_string "$4$1")
    result="${keys}"
    printf -- "%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on key.
__complete_key () {
    # KEY is used in both br-set-external-id/br-get-external id (in
    # which case it is implicitly a key in the external-id column) and
    # in remove, where it is a table key.  This checks to see if table
    # is set (the remove scenario), and then decides what to do.
    local result

    if [ -n "${_OVS_VSCTL_PARSED_ARGS[TABLE]}" ]; then
        local column=$(tr -d '\n' <<< ${_OVS_VSCTL_PARSED_ARGS["COLUMN"]})
        result=$(_ovs_vsctl_complete_key_given_table_column \
                     "$1" \
                     ${_OVS_VSCTL_PARSED_ARGS["TABLE"]} \
                     $column \
                     "")
    else
        result=$(_ovs_vsctl br-get-external-id \
                           ${_OVS_VSCTL_PARSED_ARGS["BRIDGE"]} \
                 | cut -d'=' -f1 | _ovs_vsctl_check_startswith_string "$1")
    fi
    printf -- "%s" "${result}"
}

# $1 = word to complete on.
# Complete on key.
_ovs_vsctl_complete_key () {
    # KEY is used in both br-set-external-id/br-get-external id (in
    # which case it is implicitly a key in the external-id column) and
    # in remove, where it is a table key.  This checks to see if table
    # is set (the remove scenario), and then decides what to do.
    local result

    result="$(__complete_key $1)"
    # If result is empty, just use user input as result.
    if [ -z "$result" ]; then
        result=$1
    fi
    printf -- "EO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on value.
_ovs_vsctl_complete_value () {
    local result

    # Just use user input as result.
    result=$1

    printf -- "EO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on key=value.
_ovs_vsctl_complete_key_value () {
    local orig_completions new_completions

    orig_completions=$(__complete_key "$1")
    for completion in ${orig_completions#*EO}; do
        new_completions="${new_completions} ${completion}="
    done
    # If 'new_completions' is empty, just use user input as result.
    if [ -z "$new_completions" ]; then
        new_completions=$1
    fi
    printf -- "NOSPACE\nEO\n%s" "${new_completions}"
}

# $1 = word to complete on.
# Complete on column.
_ovs_vsctl_complete_column () {
    local columns result

    columns=$(ovsdb-client --no-headings list-columns $_OVSDB_SERVER_LOCATION \
        Open_vSwitch ${_OVS_VSCTL_PARSED_ARGS["TABLE"]})
    result=$(printf "%s\n" "${columns}" \
             | tr -d ':' | cut -d' ' -f1 \
             | _ovs_vsctl_check_startswith_string "$1" | sort | uniq)
    printf -- "EO\n%s\n" "${result}"
}

# Extract all system interfaces.
_ovs_vsctl_get_sys_intf () {
    local result

    case "$(uname -o)" in
        *Linux*)
            result=$(ip -o link 2>/dev/null | cut -d':' -f2 \
                     | sed -e 's/^ \(.*\)/\1/')
            ;;
        *)
            result=$(ifconfig -a -s 2>/dev/null | cut -f1 -d' ' | tail -n +2)
            ;;
    esac
    printf "%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on system interface.
_ovs_vsctl_complete_sysiface () {
    local result

    result=$(_ovs_vsctl_get_sys_intf | _ovs_vsctl_check_startswith_string "$1")
    printf -- "EO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on interface.  If a bridge has already been specified,
# just complete for that bridge.
_ovs_vsctl_complete_iface () {
    local result

    if [ -n "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}" ]; then
        result=$(_ovs_vsctl list-ifaces "${_OVS_VSCTL_PARSED_ARGS[BRIDGE]}")
    else
        for bridge in $(_ovs_vsctl list-br); do
            local ifaces

            ifaces=$(_ovs_vsctl list-ifaces "${bridge}")
            result="${result} ${ifaces}"
        done
    fi
    printf "EO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on COLUMN?:KEY=VALUE.
_ovs_vsctl_complete_column_optkey_value () {
    local result column key value completion

    column=$(printf "%s\n" "$1" | cut -d '=' -f1 | cut -d':' -f1)
    key=$(printf "%s\n" "$1" | cut -d '=' -f1 | cut -s -d':' -f2)
    # The tr -d '\n' <<< makes sure that there are no leading or
    # trailing accidental newlines.
    table=$(tr -d '\n' <<< ${_OVS_VSCTL_PARSED_ARGS["TABLE"]})
    # This might also be called after add-port or add-bond; in those
    # cases, the table should implicitly be assumed to be "Port".
    # This is done by checking if a NEW- parameter has been
    # encountered and, if it has, using that type without the NEW- as
    # the table.
    if [ -z "$table" ]; then
        if [ -n ${_OVS_VSCTL_PARSED_ARGS["NEW-PORT"]} ] \
           || [ -n ${_OVS_VSCTL_PARSED_ARGS["NEW-BOND-PORT"]} ]; then
            table="Port"
        fi
    fi
    if [ -z "$key" ]; then
        local columns=$(ovsdb-client --no-headings list-columns \
            $_OVSDB_SERVER_LOCATION Open_vSwitch $table)

        result=$(printf "%s\n" "${columns}" \
                 | awk '/key.*value/ { print $1":"; next }
                                     { print $1; next }' \
                 | _ovs_vsctl_check_startswith_string "$1" | sort | uniq)
    fi
    if [[ $1 =~ ":" ]]; then
        result=$(_ovs_vsctl_complete_key_given_table_column \
                     "$key" "$table" "$column" "$column:")
    fi
    # If result is empty, just use user input as result.
    if [ -z "$result" ]; then
        result=$1
    fi
    printf -- "NOSPACE\nEO\n%s\n" "${result}"
}

# $1 = word to complete on.
# Complete on filename.
_ovs_vsctl_complete_filename () {
    local result

    result=$(compgen -o filenames -A file "$1")
    printf -- "EO\n%s\n" "${result}"
}

_ovs_vsctl_complete_bridge_fail_mode () {
    printf -- "EO\nstandalone\nsecure"
}

# $1 = word to complete on.
# Complete on target.
_ovs_vsctl_complete_target () {
    local result

    if [[ "$1" =~ ^p?u ]]; then
        local protocol pathname expansion_base result

        protocol=$(cut -d':' -f1 <<< "$1")
        pathname=$(cut -s -d':' -f2 <<< "$1")
        expansion_base=$(compgen -W "unix punix" "$protocol")
        expansion_base="$expansion_base:"
        result=$(compgen -o filenames -A file \
                         -P $expansion_base "${pathname}")
        printf -- "NOSPACE\nEO\n%s\n" "${result}"
    else
        printf -- "NOSPACE\nEO\nssl:\ntcp:\nunix:\npssl:\nptcp:\npunix:"
    fi
}

# Extract PS1 prompt.
_ovs_vsctl_get_PS1 () {
    if [ "$test" = "true" ]; then
        printf -- "> "
        return;
    fi

    # On Bash 4.4+ just use the @P expansion
    if ((BASH_VERSINFO[0] > 4 ||
        (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4))); then
        printf '%s\n' "${PS1@P}"
        return
    fi

    # Original inspiration from
    # http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2,
    # but changed quite a lot to make it more robust.

    # Make sure the PS1 used doesn't include any of the special
    # strings used to identify the prompt
    myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")"
    # Export the current environment in case the prompt uses any
    vars="$(env | cut -d'=' -f1)"
    for var in $vars; do export $var; done
    funcs="$(declare -F | cut -d' ' -f3)"
    for func in $funcs; do export -f $func; done
    # Get the prompt
    v="$(bash --norc --noprofile -i 2>&1 <<< $'PS1=\"'"$myPS1"$'\" \n# Begin prompt\n# End prompt')"
    v="${v##*# Begin prompt}"
    printf -- "$(tail -n +2 <<< "${v%# End prompt*}" | sed 's/\\Begin prompt/Begin prompt/; s/\\End prompt/End prompt/')"

}

# Request a new value from user.  Nothing to complete on.
_ovs_vsctl_complete_new () {
    local two_word_type message result

    if [ ! "$1" = "--" ]; then
        two_word_type="${2/-/ }"
        message="\nEnter a ${two_word_type,,}:\n$(_ovs_vsctl_get_PS1)$COMP_LINE"
        if [ -n "$1" ]; then
            result="$1"
        fi
        printf -- "NOCOMP\nBM%sEM\nEO\n%s\n" "${message}" "${result}"
    fi
}

_ovs_vsctl_complete_dashdash () {
    printf -- "EO\n%s\n" "--"
}


# These functions are given two arguments:
#
# $1 is the word being completed
#
# $2 is the type of completion --- only currently useful for the
# NEW-* functions.
#
# Note that the NEW-* functions actually are ``completed''; currently
# the completions are just used to save the fact that they have
# appeared for later use (i.e. implicit table calculation).
#
# The output is of the form <options>EO<completions>, where EO stands
# for end options.  Currently available options are:
#  - NOSPACE: Do not add a space at the end of each completion
#  - NOCOMP: Do not complete, but store the output of the completion
#    func in _OVS_VSCTL_PARSED_ARGS for later usage.
#  - BM<message>EM: Print the <message>
declare -A _OVS_VSCTL_ARG_COMPLETION_FUNCS=(
    ["TABLE"]=_ovs_vsctl_complete_table
    ["RECORD"]=_ovs_vsctl_complete_record
    ["BRIDGE"]=_ovs_vsctl_complete_bridge
    ["PARENT"]=_ovs_vsctl_complete_bridge
    ["PORT"]=_ovs_vsctl_complete_port
    ["KEY"]=_ovs_vsctl_complete_key
    ["VALUE"]=_ovs_vsctl_complete_value
    ["ARG"]=_ovs_vsctl_complete_value
    ["IFACE"]=_ovs_vsctl_complete_iface
    ["SYSIFACE"]=_ovs_vsctl_complete_sysiface
    ["COLUMN"]=_ovs_vsctl_complete_column
    ["COLUMN?:KEY"]=_ovs_vsctl_complete_column_optkey_value
    ["COLUMN?:KEY=VALUE"]=_ovs_vsctl_complete_column_optkey_value
    ["KEY=VALUE"]=_ovs_vsctl_complete_key_value
    ["?KEY=VALUE"]=_ovs_vsctl_complete_key_value
    ["PRIVATE-KEY"]=_ovs_vsctl_complete_filename
    ["CERTIFICATE"]=_ovs_vsctl_complete_filename
    ["CA-CERT"]=_ovs_vsctl_complete_filename
    ["MODE"]=_ovs_vsctl_complete_bridge_fail_mode
    ["TARGET"]=_ovs_vsctl_complete_target
    ["NEW-BRIDGE"]=_ovs_vsctl_complete_new
    ["NEW-PORT"]=_ovs_vsctl_complete_new
    ["NEW-BOND-PORT"]=_ovs_vsctl_complete_new
    ["NEW-VLAN"]=_ovs_vsctl_complete_new
    ["--"]=_ovs_vsctl_complete_dashdash
)

# $1: Argument type, may include vertical bars to mean OR
# $2: Beginning of completion
#
# Note that this checks for existance in
# _OVS_VSCTL_ARG_COMPLETION_FUNCS; if the argument type ($1) is not
# there it will fail gracefully.
_ovs_vsctl_possible_completions_of_argument () {
    local possible_types completions tmp

    completions="EO"

    possible_types=$(printf "%s\n" "$1" | tr '|' '\n')
    for type in $possible_types; do
        if [ ${_OVS_VSCTL_ARG_COMPLETION_FUNCS["${type^^}"]} ]; then
            tmp=$(${_OVS_VSCTL_ARG_COMPLETION_FUNCS["${type^^}"]} \
                      "$2" "${type^^}")
            tmp_noEO="${tmp#*EO}"
            tmp_EO="${tmp%%EO*}"
            completions=$(printf "%s%s\n%s" "${tmp_EO}" \
                                 "${completions}" "${tmp_noEO}")
        fi
    done
    printf "%s\n" "${completions}"
}

# $1 = List of argument types
# $2 = current pointer into said list
# $3 = word to complete on
# Outputs list of possible completions
# The return value is the index in the cmd_args($1) list that should
# next be matched, if only one of them did, or 254 if there are no
# matches, so it doesn't know what comes next.
_ovs_vsctl_complete_argument() {
    local cmd_args arg expansion index

    new=$(printf "%s\n" "$1" | grep -- '.\+')
    readarray -t cmd_args <<< "$new";
    arg=${cmd_args[$2]}
    case ${arg:0:1} in
        !)
            expansion=$(_ovs_vsctl_possible_completions_of_argument \
                            "${arg:1}" $3)
            index=$(($2+1))
            ;;
        \?|\*)
            local tmp1 tmp2 arg2_index tmp2_noEO tmp2_EO
            tmp1=$(_ovs_vsctl_possible_completions_of_argument "${arg:1}" $3)
            tmp2=$(_ovs_vsctl_complete_argument "$1" "$(($2+1))" "$3")
            arg2_index=$?
            if _ovs_vsctl_detect_nonzero_completions "$tmp1" \
               && _ovs_vsctl_detect_nonzero_completions "$tmp2"; then
                if [ "${arg:0:1}" = "*" ]; then
                    index=$2;
                else
                    index=$(($2+1));
                fi
            fi
            if _ovs_vsctl_detect_nonzero_completions "$tmp1" \
               && (! _ovs_vsctl_detect_nonzero_completions "$tmp2"); then
                if [ "${arg:0:1}" = "*" ]; then
                    index=$2;
                else
                    index=$(($2+1));
                fi
            fi
            if (! _ovs_vsctl_detect_nonzero_completions "$tmp1") \
               && _ovs_vsctl_detect_nonzero_completions "$tmp2"; then
                index=$arg2_index
            fi
            if (! _ovs_vsctl_detect_nonzero_completions "$tmp1") \
               && (! _ovs_vsctl_detect_nonzero_completions "$tmp2"); then
                index=254
            fi
            # Don't allow secondary completions to inhibit primary
            # completions:
            if [[ $tmp2 =~ ^([^E]|E[^O])*NOCOMP ]]; then
                tmp2=""
            fi
            tmp2_noEO="${tmp2#*EO}"
            tmp2_EO="${tmp2%%EO*}"
            expansion=$(printf "%s%s\n%s" "${tmp2_EO}" \
                               "${tmp1}" "${tmp2_noEO}")
            ;;
    esac
    printf "%s\n" "$expansion"
    return $index
}

_ovs_vsctl_detect_nospace () {
    if [[ $1 =~ ^([^E]|E[^O])*NOSPACE ]]; then
        _OVS_VSCTL_COMP_NOSPACE=true
    fi
}

_ovs_vsctl_process_messages () {
    local message

    message="${1#*BM}"
    message="${message%%EM*}"
    if [ "$test" = "true" ]; then
        printf -- "--- BEGIN MESSAGE"
    fi
    printf "${message}"
    if [ "$test" = "true" ]; then
        printf -- "--- END MESSAGE"
    fi
}

# colon, equal sign will mess up the completion output, just
# removes the colon-word and equal-word prefix from COMPREPLY items.
#
# Implementation of this function refers to the __ltrim_colon_completions
# function defined in bash_completion module.
#
# $1:  Current argument
# $2:  $COMP_WORDBREAKS
# $3:  ${COMPREPLY[@]}
_ovs_vsctl_trim_compreply() {
    local cur comp_wordbreaks
    local compreply

    cur=$1 && shift
    comp_wordbreaks=$1 && shift
    compreply=( $@ )

    if [[ "$cur" == *:* && "$comp_wordbreaks" == *:* ]]; then
        local colon_word=${cur%${cur##*:}}
        local i=${#compreply[*]}
        cur=${cur##*:}
        while [ $((--i)) -ge 0 ]; do
            compreply[$i]=${compreply[$i]#"$colon_word"}
        done
    fi

    if [[ "$cur" == *=* && "$comp_wordbreaks" == *=* ]]; then
        local equal_word=${cur%${cur##*=}}
        local i=${#compreply[*]}
        while [ $((--i)) -ge 0 ]; do
            compreply[$i]=${compreply[$i]#"$equal_word"}
        done
    fi

    printf "%s " "${compreply[@]}"
}

# The general strategy here is that the same functions that decide
# completions can also capture the necessary context for later
# completions.  This means that there is no distinction between the
# processing for words that are not the current word and words that
# are the current word.
#
# Parsing up until the command word happens starts with everything
# valid; as the syntax order of ovs-vsctl is fairly strict, when types
# of words that preclude other words from happending can turn them
# off; this is controlled by valid_globals, valid_opts, and
# valid_commands.  given_opts is used to narrow down which commands
# are valid based on the previously given options.
#
# After the command has been detected, the parsing becomes more
# complicated.  The cmd_pos variable is set to 0 when the command is
# detected; it is used as a pointer into an array of the argument
# types for that given command.  The argument types are stored in both
# cmd_args and raw_cmd as the main loop uses properties of arrays to
# detect certain conditions, but arrays cannot be passed to functions.
# To be able to deal with optional or repeatable arguments, the exit
# status of the function _ovs_vsctl_complete_argument represents where
# it has determined that the next argument will be.
_ovs_vsctl_bashcomp () {
    local words cword valid_globals cmd_args raw_cmd cmd_pos valid_globals valid_opts
    local test="false"

    # Does not support BASH_VERSION < 4.0
    if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
        return 0
    fi

    # Prepare the COMP_* variables based on input.
    if [ "$1" = "test" ]; then
        test="true"
        export COMP_LINE="ovs-vsctl $2"
        tmp="ovs-vsctl"$'\n'"$(tr ' ' '\n' <<< "${COMP_LINE}x")"
        tmp="${tmp%x}"
        readarray -t COMP_WORDS \
                  <<< "$tmp"
        export COMP_WORDS
        export COMP_CWORD="$((${#COMP_WORDS[@]}-1))"
    else
        # If not in test mode, reassembles the COMP_WORDS and COMP_CWORD
        # using just space as word break.
        _get_comp_words_by_ref -n "\"'><=;|&(:" -w words -i cword
        COMP_WORDS=( "${words[@]}" )
        COMP_CWORD=${cword}
    fi

    # Extract the conf.db path.
    db=$(sed -n 's/.*--db=\([^ ]*\).*/\1/p' <<< "$COMP_LINE")
    if [ -n "$db" ]; then
        _OVSDB_SERVER_LOCATION="$db"
    fi

    # If having trouble accessing the database, return.
    if ! _ovs_vsctl get-manager 1>/dev/null 2>/dev/null; then
        return 1;
    fi

    _OVS_VSCTL_PARSED_ARGS=()
    _OVS_VSCTL_NEW_RECORDS=()
    cmd_pos=-1
    valid_globals=true
    valid_opts=true
    valid_commands=true
    given_opts=""
    index=1
    for word in "${COMP_WORDS[@]:1:${COMP_CWORD}} "; do
        _OVS_VSCTL_COMP_NOSPACE=false
        local completion
        completion=""
        if [ $cmd_pos -gt -1 ]; then
            local tmp tmp_noop arg possible_newindex
            tmp=$(_ovs_vsctl_complete_argument "$raw_cmd" "$cmd_pos" "$word")
            possible_newindex=$?
            # Check for nospace.
            _ovs_vsctl_detect_nospace $tmp
            # Remove all options.
            tmp_noop="${tmp#*EO}"

            # Allow commands to specify that they should not be
            # completed
            if ! [[ $tmp =~ ^([^E]|E[^O])*NOCOMP ]]; then
                # Directly assignment, since 'completion' is guaranteed to
                # to be empty.
                completion="$tmp_noop"
                # If intermediate completion is empty, it means that the current
                # argument is invalid.  And we should not continue.
                if [ $index -lt $COMP_CWORD ] \
                    && (! _ovs_vsctl_detect_nonzero_completions "$completion"); then
                    _ovs_vsctl_process_messages "BM\nCannot complete \'${COMP_WORDS[$index]}\' at index ${index}:\n$(_ovs_vsctl_get_PS1)${COMP_LINE}EM\nEO\n"
                    return 1
                fi
            else
                # Only allow messages when there is no completion
                # printout and when on the current word.
                if [ $index -eq $COMP_CWORD ]; then
                    _ovs_vsctl_process_messages "${tmp}"
                fi
                # Append the new record to _OVS_VSCTL_NEW_RECORDS.
                _OVS_VSCTL_NEW_RECORDS["${cmd_args[$cmd_pos]##*-}"]="${_OVS_VSCTL_NEW_RECORDS["${cmd_args[$cmd_pos]##*-}"]} $tmp_noop"
            fi
            if [[ $cmd_pos -lt ${#cmd_args} ]]; then
                _OVS_VSCTL_PARSED_ARGS["${cmd_args[$cmd_pos]:1}"]=$word
            fi
            if [ $possible_newindex -lt 254 ]; then
                cmd_pos=$possible_newindex
            fi
        fi

        if [ $valid_globals == true ]; then
            tmp=$(_ovs_vsctl_bashcomp_globalopt $word)
            _ovs_vsctl_detect_nospace $tmp
            completion="${completion} ${tmp#*EO}"
        fi
        if [ $valid_opts == true ]; then
            tmp=$(_ovs_vsctl_bashcomp_localopt "$given_opts" $word)
            _ovs_vsctl_detect_nospace $tmp
            completion="${completion} ${tmp#*EO}"
            if [ $index -lt $COMP_CWORD ] \
               && _ovs_vsctl_detect_nonzero_completions "$tmp"; then
                valid_globals=false
                given_opts="${given_opts} ${word}"
            fi
        fi
        if [ $valid_commands = true ]; then
            tmp=$(_ovs_vsctl_bashcomp_command "$given_opts" $word)
            _ovs_vsctl_detect_nospace $tmp
            completion="${completion} ${tmp#*EO}"
            if [ $index -lt $COMP_CWORD ] \
               && _ovs_vsctl_detect_nonzero_completions "$tmp"; then
                valid_globals=false
                valid_opts=false
                valid_commands=false
                cmd_pos=0
                raw_cmd=$(_ovs_vsctl_expand_command "$word")
                readarray -t cmd_args <<< "$raw_cmd"
            fi
        fi
        if [ "$word" = "--" ] && [ $index -lt $COMP_CWORD ]; then
            # Empty the parsed args array.
            _OVS_VSCTL_PARSED_AGS=()
            cmd_pos=-1
            # No longer allow global options after '--'.
            valid_globals=false
            valid_opts=true
            valid_commands=true
            given_opts=""
        fi
        completion="$(sort -u <<< "$(tr ' ' '\n' <<< ${completion})")"
        if [ $index -eq $COMP_CWORD ]; then
            if [ "$test" = "true" ]; then
                completion="$(_ovs_vsctl_trim_compreply "$word" ":=" ${completion} | \
                              tr ' ' '\n')"
                if [ "${_OVS_VSCTL_COMP_NOSPACE}" = "true" ]; then
                    printf "%s" "$completion" | sed -e '/^$/d'
                else
                    printf "%s" "$completion" | sed -e '/^$/d; s/$/ /g'
                fi
                printf "\n"
            else
                if [ "${_OVS_VSCTL_COMP_NOSPACE}" = "true" ]; then
                    compopt -o nospace
                    COMPREPLY=( $(compgen -W "${completion}" -- $word) )
                else
                    compopt +o nospace
                    COMPREPLY=( $(compgen -W "${completion}" -- $word) )
                fi
                COMPREPLY=( $(_ovs_vsctl_trim_compreply "$word" \
                              "${COMP_WORDBREAKS}" ${COMPREPLY[@]}) )
            fi
        fi
        index=$(($index+1))
    done
}

if [ "$1" = "test" ]; then
    _ovs_vsctl_bashcomp "$@"
else
    complete -F _ovs_vsctl_bashcomp ovs-vsctl
fi
