#autoload

# This gets two arguments, a separator (which should be only one
# character) and an array. As usual, the array may be given by it's
# name or literal as in `(foo bar baz)' (words separated by spaces in
# parentheses).
# The parts of words from the array that are separated by the
# separator character are then completed independently.

local sep matches pref npref i tmp1 group expl menu pre suf
typeset -U tmp2

# Get the options.

group=()
expl=()
while getopts "J:V:X:" opt; do
  case "$opt" in
  [JV]) group=("-$opt" "$OPTARG");;
  X)    expl=(-X "$OPTARG");;
  esac
done
shift OPTIND-1

# Get the arguments, first the separator, then the array. The array is 
# stored in `matches'. Further on this array will always contain those 
# words from the original array that still match everything we have
# tried to match while we walk through the string from the line.

sep="$1"
if [[ "${2[1]}" = '(' ]]; then
  matches=( ${2[2,-2]} )
else
  matches=( "${(@P)2}" )
fi

# In `pre' and `suf' we will hold the prefix and the suffix from the
# line while we walk through them. The original string are used 
# temporarily for matching.

pre="$PREFIX"
suf="$SUFFIX"
orig="$PREFIX$SUFFIX"

# Special handling for menucompletion?

[[ $compstate[insert] = (*menu|[0-9]*) || -n "$_comp_correct" ||
   ( $#compstate[pattern_match] -ne 0 &&
     "$orig" != "${orig:q}" ) ]] && menu=yes

# In `pref' we collect the unambiguous prefix path.

pref=''

# If the string from the line matches at least one of the strings,
# we use only the matching strings.

compadd -O tmp1 -M "r:|${sep}=* r:|=*" - "$matches[@]"

(( $#tmp1 )) && matches=( "$tmp1[@]" )

while true; do

  # Get the prefix and suffix for matching.

  if [[ "$pre" = *${sep}* ]]; then
    PREFIX="${pre%%${sep}*}"
    SUFFIX=""
  else
    PREFIX="${pre}"
    SUFFIX="${suf%%${sep}*}"
  fi

  # Check if the component for some of the possible matches is equal
  # to the string from the line. If there are such strings, we directly
  # use the stuff from the line. This avoids having `foo' complete to
  # both `foo' and `foobar'.

  tmp1=( "${(@M)matches:#${PREFIX}${SUFFIX}${sep}*}" )

  if (( $#tmp1 )); then
    npref="${PREFIX}${SUFFIX}${sep}"
  else
    # No exact match, see how many strings match what's on the line.

    compadd -O tmp1 - "${(@)matches%%${sep}*}"

    if [[ $#tmp1 -eq 1 ]]; then

      # Only one match. If there are still separators from the line
      # we just accept this component. Otherwise we insert what we 
      # have collected, probably giving it a separator character
      # as a suffix.

      if [[ "$pre$suf" = *${sep}* ]]; then
        npref="${tmp1[1]}${sep}"
      else
        matches=( "${(@M)matches:#${tmp1[1]}*}" )
	tmp2=( "${(@M)matches:#${tmp1[1]}${sep}*}" )

	if (( $#tmp2 )); then
	  compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	          -p "$pref" -qS "$sep" - "$tmp1[1]"
        else
	  compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	          -p "$pref" - "$tmp1[1]"
        fi
	return 1
      fi
    elif (( $#tmp1 )); then

      # More than one match. First we get all strings that match the
      # rest from the line.

      PREFIX="$pre"
      SUFFIX="$suf"
      compadd -O matches -M "r:|${sep}=* r:|=*" - "$matches[@]"

      if [[ -n "$menu" ]]; then
        # With menucompletion we just add matches for the matching
        # components with the prefix we collected and the rest from the
        # line as a suffix.

        tmp2="$pre$suf"
        if [[ "$tmp2" = *${sep}* ]]; then
          compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	          -p "$pref" -s "${sep}${tmp2#*${sep}}" - "$tmp1[@]"
        else
          compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	          -p "$pref" - "$tmp1[@]"
        fi
      else
        # With normal completion we add all matches one-by-one with
	# the unmatched part as a suffix. This will insert the longest
	# unambiguous string for all matching strings.

        for i in "${(@M)matches:#(${(j:|:)~tmp1})*}"; do
	  if [[ "$i" = *${sep}* ]]; then
            compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	            -S '' -p "$pref" -s "${i#*${sep}}" - "${i%%${sep}*}${sep}"
          else
            compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	            -S '' -p "$pref" - "$i"
          fi
        done
      fi
      return 0
    else
      # We are here if no string matched what's on the line. In this
      # case we insert the expanded prefix we collected if it differs
      # from the original string from the line.

      [[ "$orig" = "$pref$pre$suf" ]] && return 1

      if [[ -n "$suf" ]]; then
        compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	        -s "$suf" - "$pref$pre"
      else
        compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	        -S '' - "$pref$pre$suf"
      fi
      return 0
    fi
  fi

  # We just accepted and/or expanded a component from the line. We
  # remove it from the matches (using only those that have a least
  # the skipped string) and ad it the `pref'.

  matches=( "${(@)${(@)${(@M)matches:#${npref}*}#*${sep}}:#}" )
  pref="$pref$npref"

  # Now we set `pre' and `suf' to their new values.

  if [[ "$pre" = *${sep}* ]]; then
    pre="${pre#*${sep}}"
  elif [[ "$suf" = *${sep}* ]]; then
    pre="${suf#*${sep}}"
    suf=""
  else
    # The string from the line is fully handled. If we collected an
    # unambiguous prefix and that differs from the original string,
    # we insert it.

    [[ -n "$pref" && "$orig" != "$pref" ]] &&
        compadd -U "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \
	        -S '' - "$pref"

    return
  fi
done
