#autoload

# Utility function for in-path completion. This allows `/u/l/b<TAB>'
# to complete to `/usr/local/bin'.

local linepath realpath donepath prepath testpath exppath
local tmp1 tmp2 tmp3 tmp4 i orig pre suf tpre tsuf opre osuf cpre
local pats haspats=no ignore group expl addpfx addsfx remsfx
local nm=$compstate[nmatches] menu match matcher

typeset -U prepaths exppaths

setopt localoptions nullglob rcexpandparam
unsetopt markdirs globsubst shwordsplit nounset

local sopt='-' gopt='' opt
exppaths=()
prepaths=('')
ignore=()
group=()
pats=()
addpfx=()
addsfx=()
remsfx=()
expl=()
matcher=()

# Get the options.

while getopts "P:S:qr:R:W:F:J:V:X:f/g:M:" opt; do
  case "$opt" in
  P)     addpfx=(-P "$OPTARG")
         ;;
  S)     addsfx=(-S "$OPTARG")
         ;;
  q)     tmp1=yes
         ;;
  [rR])  remsfx=("-$opt" "$OPTARG")
         ;;
  W)     tmp1="$OPTARG"
         if [[ "$tmp1[1]" = '(' ]]; then
           prepaths=( ${^=tmp1[2,-2]}/ )
         else
           prepaths=( ${(P)=${tmp1}} )
           (( ! $#prepaths )) && prepaths=( ${tmp1}/ )
         fi
         (( ! $#prepaths )) && prepaths=( '' )
         ;;
  F)     tmp1="$OPTARG"
         if [[ "$tmp1[1]" = '(' ]]; then
           ignore=( ${^=tmp1[2,-2]}/ )
         else
           ignore=( ${(P)${tmp1}} )
         fi
	 (( $#ignore )) && ignore=(-F "( $ignore )")
         ;;
  [JV])  group=("-$opt" "$OPTARG")
         ;;
  X)     expl=(-X "$OPTARG")
         ;;
  f)     sopt="${sopt}f"
         pats=("$pats[@]" '*')
	 ;;
  /)     sopt="${sopt}/"
         pats=("$pats[@]" '*(-/)')
	 haspats=yes
	 ;;
  g)     gopt='-g'
         pats=("$pats[@]" ${=OPTARG})
	 haspats=yes
	 ;;
  M)     match="$OPTARG"
         matcher=(-M "$OPTARG")
         ;;
  esac
done

if (( ! ( $#group + $#expl ) )); then
  if [[ "$sopt" = -/ ]]; then
    _description expl directory
  else
    _description expl file
  fi
fi

[[ -n "$tmp1" && $#addsfx -ne 0 ]] && addsfx[1]=-qS

# If we were given no file selection option, we behave as if we were given
# a `-f'.

if [[ "$sopt" = - ]]; then
  if [[ -z "$gopt" ]]; then
    sopt='-f'
    pats=('*')
  else
    unset sopt
  fi
fi

# We get the prefix and the suffix from the line and save the whole
# original string. Then we see if we will do menucompletion.

pre="$PREFIX"
suf="$SUFFIX"
opre="$PREFIX"
osuf="$SUFFIX"
orig="${PREFIX}${SUFFIX}"

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

# If given no `-F' option, we want to use the `ignored-suffixes'-style.

if (( ! $#ignore )); then
  if _style -a files ignored-suffixes ignore; then
    ignore=(-F "( $ignore )")
  else

    # For now we still use the fignore parameter.
    # This may be removed some day.

    ignore=(-F fignore)
  fi
fi

# Now let's have a closer look at the string to complete.

if [[ "$pre[1]" = \~ ]]; then
  # It begins with `~', so remember anything before the first slash to be able
  # to report it to the completion code. Also get an expanded version of it
  # (in `realpath'), so that we can generate the matches. Then remove that
  # prefix from the string to complete, set `donepath' to build the correct
  # paths and make sure that the loop below is run only once with an empty
  # prefix path by setting `prepaths'.
  
  linepath="${pre%%/*}/"
  realpath=$~linepath
  [[ "$realpath" = "$linepath" ]] && return 1
  pre="${pre#*/}"
  orig="${orig#*/}"
  donepath=''
  prepaths=( '' )
elif [[ "$pre" = *\$*/* ]]; then

  # If there is a parameter expansion in the word from the line, we try
  # to complete the beast by expanding the prefix and completing anything
  # after the first slash after the parameter expansion.
  # This fails for things like `f/$foo/b/<TAB>' where the first `f' is
  # meant as a partial path.

  linepath="${(M)pre##*\$[^/]##/}"
  realpath=${(e)~linepath}
  [[ "$realpath" = "$linepath" ]] && return 1
  pre="${pre#${linepath}}"
  i="${#linepath//[^\\/]}"
  orig="${orig[1,(in:i:)/][1,-2]}"
  donepath=''
  prepaths=( '' )
else
  # If the string does not start with a `~' we don't remove a prefix from the
  # string.

  linepath=''
  realpath=''

  if [[ "$pre[1]" = / ]]; then
    # If it is a absolut path name, we remove the first slash and put it in
    # `donepath' meaning that we treat it as the path that was already handled.
    # Also, we don't use the paths from `-W'.

    pre="$pre[2,-1]"
    orig="$orig[2,-1]"
    donepath='/'
    prepaths=( '' )
  else
    # The common case, we just use the string as it is, unless it begins with
    # `./' or `../' in which case we don't use the paths from `-W'.
    
    [[ "$pre" = (.|..)/* ]] && prepaths=( '' )
    donepath=''
  fi
fi

# Now we generate the matches. First we loop over all prefix paths given
# with the `-W' option.

for prepath in "$prepaths[@]"; do

  # Get local copies of the prefix, suffix, and the prefix path to use
  # in the following loop, which walks through the pathname components
  # in the string from the line.

  tpre="$pre"
  tsuf="$suf"
  testpath="$donepath"

  tmp1=( "$prepath$realpath$donepath" )

  while true; do

    # Skip over `./' and `../'.

    if [[ "$tpre" = (.|..)/* ]]; then
      tmp1=( ${^tmp1}${tpre%%/*}/ )
      tpre="${tpre#*/}"
      continue
    fi

    # Get the prefix and suffix for matching.

    if [[ "$tpre" = */* ]]; then
      PREFIX="${tpre%%/*}"
      SUFFIX=""
    else
      PREFIX="${tpre}"
      SUFFIX="${tsuf%%/*}"
    fi

    # Get the matching files by globbing.

    if [[ "$tpre$tsuf" = */* ]]; then
      tmp2=( ${^tmp1}*(-/) )
      [[ ! -o globdots && "$PREFIX" = .* ]] &&
          tmp2=( "$tmp1[@]" ${^tmp1}.*(-/) )
    else
      tmp2=( ${^tmp1}${^~pats} )
      [[ ! -o globdots && "$PREFIX" = .* ]] &&
          tmp2=( "$tmp1[@]" ${^tmp1}.${^~pats} )
    fi
    tmp1=( "$tmp2[@]" )

    if [[ -n "$PREFIX$SUFFIX" ]]; then
      # See which of them match what's on the line.

      tmp2=("$tmp1[@]")
      compadd -D tmp1 "$ignore[@]" "$matcher[@]" - "${(@)tmp1:t}"

      # If no file matches, save the expanded path and continue with
      # the outer loop.

      if [[ $#tmp1 -eq 0 ]]; then
 	if [[ "$tmp2[1]" = */* ]]; then
	  tmp2=( "${(@)tmp2#${prepath}${realpath}}" )
	  if [[ "$tmp2[1]" = */* ]]; then
	    tmp2=( "${(@)tmp2:h}" )
	    compquote tmp2
	    exppaths=( "$exppaths[@]" ${^tmp2}/${tpre}${tsuf} )
          else
	    exppaths=( "$exppaths[@]" ${tpre}${tsuf} )
	  fi
        fi
        continue 2
      fi
    elif (( ! $#tmp1 )); then
      # A little extra hack: if we were completing `foo/<TAB>' and `foo'
      # contains no files, this will normally produce no matches and other
      # completers might think that's it's their time now. But if the next
      # completer is _correct or something like that, this will result in
      # an attempt to correct a valid directory name. So we just add the
      # original string in such a case so that the command line doesn't 
      # change but other completers still think there are matches.
      # We do this only if we weren't given a `-g' or `-/' option because
      # otherwise this would keep `_files' from completing all filenames
      # if none of the patterns match.

      if [[ -z "$tpre$tsuf" && -n "$pre$suf" ]]; then
        tmp1=( "$tmp2[@]" )
	addsfx=(-S '')
	remsfx=()
	break;
      elif [[ "$haspats" = no && -z "$tpre$tsuf" &&
	"$pre" = */ && -z "$suf" ]]; then
	PREFIX="${opre}"
	SUFFIX="${osuf}"
        compadd -nQS '' - "$linepath$donepath$orig"
        tmp4=-
      fi
      continue 2
    fi

    # Step over to the next component, if any.

    if [[ "$tpre" = */* ]]; then
      tpre="${tpre#*/}"
    elif [[ "$tsuf" = */* ]]; then
      tpre="${tsuf#*/}"
      tsuf=""
    else
      break
    fi

    # There are more components, so add a slash to the files we are
    # collecting.

    tmp1=( ${^tmp1}/ )
  done

  # The next loop searches the first ambiguous component.

  tmp3="$pre$suf"
  tpre="$pre"
  tsuf="$suf"
  tmp1=( "${(@)tmp1#${prepath}${realpath}${testpath}}" )

  while true; do

    # First we check if some of the files match the original string
    # for this component. If there are some we remove all other
    # names. This avoids having `foo' complete to `foo' and `foobar'.

    if [[ "$tmp3" = */* ]]; then
      tmp4=( "${(@M)tmp1:#${tmp3%%/*}/*}" )
      if (( $#tmp4 )); then
        tmp1=( "$tmp4[@]" )
      fi
    fi

    # Next we see if this component is ambiguous.

    if [[ "$tmp3" = */* ]]; then
      tmp4=( "${(@)tmp1:#${tmp1[1]%%/*}/*}" )
    else
      tmp4=( "${(@)tmp1:#${tmp1[1]}}" )
    fi

    if (( $#tmp4 )); then

      # It is. For menucompletion we now add the possible completions
      # for this component with the unambigous prefix we have built
      # and the rest of the string from the line as the suffix.
      # For normal completion we add the rests of the filenames
      # collected as the suffixes to make the completion code expand
      # it as far as possible.

      if [[ "$tpre" = */* ]]; then
        PREFIX="${donepath}${linepath}${cpre}${tpre%%/*}"
	SUFFIX="/${tsuf#*/}"
      else
        PREFIX="${donepath}${linepath}${cpre}${tpre}"
	SUFFIX="${tsuf}"
      fi

      tmp4="$testpath"
      compquote tmp1 tmp4

      if [[ -n $menu ]] || ! _style paths expand '*suffix*'; then
        _style paths cursor && compstate[to_end]=''
        if [[ "$tmp3" = */* ]]; then
	  compadd -Qf -p "$linepath$tmp4" -s "/${tmp3#*/}" \
	          -W "$prepath$realpath$testpath" "$ignore[@]" \
		  "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
                  -M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
		  - "${(@)tmp1%%/*}"
	else
	  compadd -Qf -p "$linepath$tmp4" \
	          -W "$prepath$realpath$testpath" "$ignore[@]" \
		   "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
                   -M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
		   - "$tmp1[@]"
	fi
      else
        if [[ "$tmp3" = */* ]]; then
          for i in "$tmp1[@]"; do
	    compadd -Qf -p "$linepath$tmp4" -s "/${i#*/}" \
		    -W "$prepath$realpath$testpath" "$ignore[@]" \
		    "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
                    -M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
		    - "${i%%/*}"
	  done
        else
	  compadd -Qf -p "$linepath$tmp4" \
		  -W "$prepath$realpath$testpath" "$ignore[@]" \
		  "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
                  -M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
		  - "$tmp1[@]"
        fi
      fi
      tmp4=-
      break
    fi

    # If we have checked all components, we stop now and add the 
    # strings collected after the loop.

    if [[ "$tmp3" != */* ]]; then
      tmp4=""
      break
    fi

    # Otherwise we add the unambiguous component to `testpath' and
    # take it from the filenames.

    testpath="${testpath}${tmp1[1]%%/*}/"
    tmp1=( "${(@)tmp1#*/}" )

    tmp3="${tmp3#*/}"

    if [[ "$tpre" = */* ]]; then
      cpre="${cpre}${tpre%%/*}/"
      tpre="${tpre#*/}"
    elif [[ "$tsuf" = */* ]]; then
      cpre="${cpre}${tpre}/"
      tpre="${tsuf#*/}"
      tsuf=""
    else
      tpre=""
      tsuf=""
    fi
  done

  if [[ -z "$tmp4" ]]; then
    if [[ "$osuf" = */* ]]; then
      PREFIX="${opre}${osuf}"
      SUFFIX=""
    else
      PREFIX="${opre}"
      SUFFIX="${osuf}"
    fi
    tmp4="$testpath"
    compquote tmp4 tmp1
    compadd -Qf -p "$linepath$tmp4" \
	    -W "$prepath$realpath$testpath" "$ignore[@]" \
	    "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
            -M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
	    - "$tmp1[@]"
  fi
done

# If we are configured to expand paths as far as possible and we collected
# expanded paths that are different from the string on the line, we add
# them as possible matches.

exppaths=( "${(@)exppaths:#$orig}" )

if _style paths expand '*prefix*' &&
   [[ $#exppaths -gt 0 && nm -eq compstate[nmatches] ]]; then
  PREFIX="${opre}"
  SUFFIX="${osuf}"
  compadd -Q -S '' "$group[@]" "$expl[@]" \
          -M "r:|/=* r:|=* $match" -p "$linepath" - "$exppaths[@]"
fi

[[ nm -ne compstate[nmatches] ]]
