#autoload

# Complete the arguments of the current command according to the
# descriptions given as arguments to this function.

local long cmd="$words[1]" descr mesg subopts opt usecc autod
local oldcontext="$curcontext" hasopts

long=$argv[(I)--]
if (( long )); then
  local name tmp tmpargv

  if [[ long -eq 1 ]]; then
    tmpargv=()
  else
    tmpargv=( "${(@)argv[1,long-1]}" )
  fi

  name=${~words[1]}
  [[ "$name" = [^/]*/* ]] && name="$PWD/$name"

  name="_args_cache_${name}"
  name="${name//[^a-zA-Z0-9_]/_}"

  if (( ! ${(P)+name} )); then
    local iopts sopts pattern tmpo dir cur cache
    typeset -U lopts

    cache=()

    # We have to build a new long-option cache, get the `-i' and
    # `-s' options.

    set -- "${(@)argv[long+1,-1]}"

    iopts=()
    sopts=()
    while [[ "$1" = -[is]* ]]; do
      if [[ "$1" = -??* ]]; then
        tmp="${1[3,-1]}"
        cur=1
      else
        tmp="$2"
	cur=2
      fi
      if [[ "$tmp[1]" = '(' ]]; then
	tmp=( ${=tmp[2,-2]} )
      else
	tmp=( "${(@P)tmp}" )
      fi
      if [[ "$1" = -i* ]]; then
        iopts=( "$iopts[@]" "$tmp[@]" )
      else
        sopts=( "$sopts[@]" "$tmp[@]" )
      fi
      shift cur
    done

    # Now get the long option names by calling the command with `--help'.
    # The parameter expansion trickery first gets the lines as separate
    # array elements. Then we select all lines whose first non-blank
    # character is a hyphen. Since some commands document more than one
    # option per line, separated by commas, we convert commas into
    # newlines and then split the result again at newlines after joining 
    # the old array elements with newlines between them. Then we select
    # those elements that start with two hyphens, remove anything up to
    # those hyphens and anything from the space or tab after the
    # option up to the end.

    lopts=("--${(@)^${(@)${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$(_call options ${~words[1]} --help 2>&1)//\[--/
--}:#[ 	]#-*}//,/
}}:#[ 	]#--*}#*--}%%[]	 ]*}:#}")
    lopts=( "${(@)lopts:#--}" )

    # Now remove all ignored options ...

    while (( $#iopts )); do
      lopts=( ${lopts:#$~iopts[1]} )
      shift iopts
    done

    # ... and add "same" options

    while (( $#sopts )); do
      lopts=( $lopts ${lopts/$~sopts[1]/$sopts[2]} )
      shift 2 sopts
    done

    # Then we walk through the descriptions plus a few builtin ones.

    set -- "$@" '*=FILE*:file:_files' \
           '*=(DIR|PATH)*:directory:_files -/' '*: :'

    while (( $# )); do

      # First, we get the pattern and the action to use and take them
      # from the positional parameters.

      pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}"
      descr="${1#${pattern}}"
      if [[ "$pattern" = *\(-\) ]]; then
        pattern="$pattern[1,-4]"
	dir=-
      else
        dir=
      fi
      shift

      # We get all options matching the pattern and take them from the
      # list we have built. If no option matches the pattern, we
      # continue with the next.

      tmp=("${(@M)lopts:##$~pattern}")
      lopts=("${(@)lopts:##$~pattern}")

      (( $#tmp )) || continue

      opt=''

      # If there are option strings with a `[=', we take these to get an
      # optional argument.

      tmpo=("${(@M)tmp:#*\[\=*}")
      if (( $#tmpo )); then
        tmp=("${(@)tmp:#*\[\=*}")
        tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")

        if [[ "$descr" = ::* ]]; then
	  cache=( "$cache[@]" "${(@)^tmpo}=${dir}${descr}" )
        else
	  cache=( "$cache[@]" "${(@)^tmpo}=${dir}:${descr}" )
        fi
      fi

      # Descriptions with `=': mandatory argument.

      tmpo=("${(@M)tmp:#*\=*}")
      if (( $#tmpo )); then
        tmp=("${(@)tmp:#*\=*}")
        tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")

	cache=( "$cache[@]" "${(@)^tmpo}=${dir}${descr}" )
      fi

      # Everything else is just added as an option without arguments.

      if (( $#tmp )); then
        tmp=("${(@)tmp//[^a-zA-Z0-9-]}")
	cache=( "$cache[@]" "$tmp[@]" )
      fi
    done
    set -A "$name" "${(@)cache:# #}"
  fi
  set -- "$tmpargv[@]" "${(@P)name}"
fi

subopts=()
while [[ "$1" = -(O*|C) ]]; do
  case "$1" in
  -C) usecc=yes; shift ;;
  -O) subopts=( "${(@P)2}" ); shift 2 ;;
  -O*)  subopts=( "${(@P)1[3,-1]}" ); shift ;;
  esac
done

zstyle -s ":completion:${curcontext}:options" auto-description autod

if (( $# )) && comparguments -i "$autod" "$@"; then
  local action noargs aret expl local tried
  local next direct odirect equal single matcher matched ws tmp1 tmp2 tmp3
  local opts subc tc prefix suffix descrs actions subcs anum
  local origpre="$PREFIX" origipre="$IPREFIX" nm="$compstate[nmatches]"

  if comparguments -D descrs actions subcs; then
    if comparguments -O next direct odirect equal; then
      opts=yes
      _tags "$subcs[@]" options
    else
      _tags "$subcs[@]"
    fi
  else
    if comparguments -a; then
      noargs='no more arguments'
    else
      noargs='no arguments'
    fi
    if comparguments -O next direct odirect equal; then
      opts=yes
      _tags options
    elif [[ $? -eq 2 ]]; then
        compadd -Q - "${PREFIX}${SUFFIX}"
        return 0
    else
      _message "$noargs"
      return 1
    fi
  fi

  comparguments -M matcher

  context=()
  state=()

  while true; do
    while _tags; do
      anum=1
      while [[ anum -le  $#descrs ]]; do

	action="$actions[anum]"
	descr="$descrs[anum]"
	subc="$subcs[anum++]"

        if [[ -n "$matched" ]] || _requested "$subc"; then

          curcontext="${oldcontext%:*}:$subc"

          _description "$subc" expl "$descr"

          if [[ "$action" = \=\ * ]]; then
            action="$action[3,-1]"
            words=( "$subc" "$words[@]" )
	    (( CURRENT++ ))
          fi

          if [[ "$action" = -\>* ]]; then
	    action="${${action[3,-1]##[ 	]#}%%[ 	]#}"
	    if (( ! $state[(I)$action] )); then
              comparguments -W line opt_args
              state=( "$state[@]" "$action" )
	      if [[ -n "$usecc" ]]; then
	        curcontext="${oldcontext%:*}:$subc"
	      else
	        context=( "$context[@]" "$subc" )
	      fi
              compstate[restore]=''
              aret=yes
            fi
          else
            if [[ -z "$local" ]]; then
              local line
              typeset -A opt_args
              local=yes
            fi

            comparguments -W line opt_args

            if [[ "$action" = \ # ]]; then

              # An empty action means that we should just display a message.

	      _message "$descr"
	      mesg=yes
	      tried=yes

            elif [[ "$action" = \(\(*\)\) ]]; then

              # ((...)) contains literal strings with descriptions.

              eval ws\=\( "${action[3,-3]}" \)

              _describe -t "$subc" "$descr" ws -M "$matcher" "$subopts[@]"
	      tried=yes

            elif [[ "$action" = \(*\) ]]; then

              # Anything inside `(...)' is added directly.

              _all_labels "$subc" expl "$descr" \
                  compadd "$subopts[@]" - ${=action[2,-2]}
	      tried=yes
            elif [[ "$action" = \{*\} ]]; then

              # A string in braces is evaluated.

              while _next_label "$subc" expl "$descr"; do
                eval "$action[2,-2]"
              done
	      tried=yes
            elif [[ "$action" = \ * ]]; then

              # If the action starts with a space, we just call it.

	      eval "action=( $action )"
              while _next_label "$subc" expl "$descr"; do
                "$action[@]"
              done
	      tried=yes
            else

              # Otherwise we call it with the description-arguments.

              set -A action ${=~action}
              while _next_label "$subc" expl "$descr"; do
                "$action[1]" "$subopts[@]" "$expl[@]" "${(@)action[2,-1]}"
	      done
	      tried=yes
            fi
          fi
        fi
      done

      if [[ -z "$matched$hasopts" ]] && _requested options &&
          { ! zstyle -T ":completion:${curcontext}:options" prefix-needed ||
            [[ "$origpre" = [-+]* || -z "$aret$mesg$tried" ]] } ; then
	local prevpre="$PREFIX" previpre="$IPREFIX"

	hasopts=yes

	PREFIX="$origpre"
	IPREFIX="$origipre"

        if comparguments -s single; then

          if [[ "$single" = direct ]]; then
            _all_labels options expl option \
	        compadd -QS '' - "${PREFIX}${SUFFIX}"
          elif [[ "$single" = next ]]; then
            _all_labels options expl option \
	        compadd -Q - "${PREFIX}${SUFFIX}"
          elif [[ "$single" = equal ]]; then
            _all_labels options expl option \
	        compadd -QqS= - "${PREFIX}${SUFFIX}"
          else
	    tmp1=( "$next[@]" "$direct[@]" "$odirect[@]" "$equal[@]" )
	    [[ "$PREFIX" != --* ]] && tmp1=( "${(@)tmp1:#--*}" )
	    tmp3=( "${(M@)tmp1:#[-+]?[^:]*}" )
	    tmp1=( "${(M@)tmp1:#[-+]?(|:*)}" )
	    tmp2=( "${PREFIX}${(@M)^${(@)${(@)tmp1%%:*}#[-+]}:#?}" )

            _describe -o option \
                      tmp1 tmp2 -Q -S '' -- \
		      tmp3 -Q
          fi
          single=yes
        else
          next=( "$next[@]" "$odirect[@]" )
          _describe -o option \
                    next -Q -M "$matcher" -- \
                    direct -QS '' -M "$matcher" -- \
                    equal -QqS= -M "$matcher"
        fi
	PREFIX="$prevpre"
	IPREFIX="$previpre"
      fi
      [[ -n "$tried" && "$PREFIX" != [-+]* ]] && break
    done
    if [[ -n "$opts" && -z "$aret$matched$mesg" &&
          nm -eq compstate[nmatches] ]]; then

      PREFIX="$origpre"
      IPREFIX="$origipre"

      prefix="${PREFIX#*\=}"
      suffix="$SUFFIX"
      PREFIX="${PREFIX%%\=*}"
      SUFFIX=''

      compadd -M "$matcher" -D equal - "${(@)equal%%:*}"

      if [[ $#equal -eq 1 ]]; then
        PREFIX="$prefix"
	SUFFIX="$suffix"
	IPREFIX="${IPREFIX}${equal[1]%%:*}="
	matched=yes

	comparguments -L "${equal[1]%%:*}" descrs actions subcs

	_tags "$subcs[@]"

	continue
      fi
    fi
    break
  done

  [[ -z "$aret" || -z "$usecc" ]] && curcontext="$oldcontext"

  [[ -n "$aret" ]] && return 300

  [[ -n "$noargs" && nm -eq "$compstate[nmatches]" ]] && _message "$noargs"

  # Set the return value.

  [[ nm -ne "$compstate[nmatches]" ]]
else
  return 1
fi
