#autoload

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

local long cmd="$words[1]" descr odescr mesg subopts opt opt2 usecc autod
local oldcontext="$curcontext" hasopts rawret optarg singopt alwopt
local setnormarg start rest
local -a match mbegin mend

subopts=()
singopt=()
while [[ "$1" = -([AMO]*|[CRSWnsw]) ]]; do
  case "$1" in
  -C)  usecc=yes; shift ;;
  -O)  subopts=( "${(@P)2}" ); shift 2 ;;
  -O*) subopts=( "${(@P)${1[3,-1]}}" ); shift ;;
  -R)  rawret=yes; shift;;
  -n)  setnormarg=yes; NORMARG=-1; shift;;
  -w)  optarg=yes; shift;;
  -W)  alwopt=arg; shift;;
  -[Ss])  singopt+=( $1 ); shift;;
  -[AM])  singopt+=( $1 $2 ); shift 2 ;;
  -[AM]*) singopt+=( $1 ); shift ;;
  esac
done

[[ $1 = ':' ]] && shift
singopt+=( ':' )  # always end with ':' to indicate the end of options

[[ "$PREFIX" = [-+] ]] && alwopt=arg

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

  tmpargv=( "${(@)argv[1,long-1]}" )  # optspec's before --, if any

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

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

  if (( ! ${(P)+name} )); then
    local iopts sopts lflag pattern tmpo dir cur cache
    typeset -Ua 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" = -[lis]* ]]; do
      if [[ "$1" = -l ]]; then
	lflag='-l'
	shift
	continue
      fi
      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+=( "$tmp[@]" )
      else
        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.

   tmp=()
   _call_program $lflag options ${~words[1]} --help 2>&1 |
     while IFS= read -r opt; do
     if (( ${#tmp} )); then
       # Previous line had no comment.  Is the current one suitable?
       # It's hard to be sure, but if it there was nothing on the
       # previous line and the current one is indented more than
       # a couple of spaces (and isn't completely whitespace or punctuation)
       # there's a pretty good chance.
       if [[ $opt = [[:space:]][[:space:]][[:space:]]*[[:alpha:]]* ]]; then
	 # Assume so.
	 opt=${opt##[[:space:]]##}
	 # Same substitution as below.
	 lopts+=("${^tmp[@]}":${${${opt//:/-}//\[/(}//\]/)})
	 tmp=()
	 # Finished with this line.
	 continue
       else
	 # Still no comment, add the previous options anyway.
         # Add a ':' after the option anyways, to make the matching of
         # the options lateron work as intended.
         # It will be removed again later.
	 lopts+=("${^tmp[@]}":)
	 tmp=()
       fi
     fi
     while [[ $opt = [,[:space:]]#(#b)(-[^,[:space:]]#)(*) ]]; do
       # We used to remove the brackets from "[=STUFF]",
       # but later the code appears to handle it with the brackets
       # present.  Maybe the problem was that the intervening code
       # didn't.  If it's buggy without removing them, the problem
       # probably is later, not here.
       start=${match[1]}
       rest=${match[2]}
       if [[ -z ${tmp[(r)${start%%[^a-zA-Z0-9_-]#}]} ]]; then
	 # variant syntax seen in fetchmail:
	 # --[fetch]all  means --fetchall or --all.
	 # maybe needs to be more general
	 if [[ $start = (#b)(*)\[(*)\](*) ]]; then
	   tmp+=("${match[1]}${match[2]}${match[3]}" "${match[1]}${match[3]}")
	 else
	   tmp+=($start)
	 fi
       fi
       opt=$rest
     done
     # If there's left over text, assume it's a description; it
     # may be truncated but if it's too long it's no use anyway.
     # There's one hiccup: we sometimes get descriptions like
     # --foo fooarg   Do some foo stuff with foo arg
     # and we need to remove fooarg.  Use whitespace for hints.
     opt=${opt## [^[:space:]]##  }
     opt=${opt##[[:space:]]##}
     if [[ -n $opt ]]; then
       # Add description after a ":", converting any : in the description
       # to a -.  Use RCQUOTES to append this to all versions of the option.
       lopts+=("${^tmp[@]}":${${${opt//:/-}//\[/(}//\]/)})
       tmp=()
       # If there's no comment, we'll see if there's one on the
       # next line.
     fi
   done
   # Tidy up any remaining uncommented options.
   if (( ${#tmp} )); then
     lopts+=("${^tmp[@]}":)
   fi

    # Remove options also described by user-defined specs.

    tmp=()
    # Ignore any argument and description information when searching
    # the long options array here and below.
    for opt in "${(@)${(@)lopts:#--}%%[\[:=]*}"; do

      # Using (( ... )) gives a parse error.

      let "$tmpargv[(I)(|\([^\)]#\))(|\*)${opt}(|[-+]|=(|-))(|\[*\])(|:*)]" ||
          tmp+=( "$lopts[(r)$opt(|[\[:=]*)]" )
    done
    lopts=( "$tmp[@]" )

    # Now remove all ignored options ...

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

    # ... and add "same" options

    while (( $#sopts )); do
      # This implements adding things like --disable-* based
      # on the existence of --enable-*.
      # TODO: there's no anchoring here, is that correct?
      # If it's not, careful with the [\[:=]* stuff.
      lopts+=( ${lopts/$~sopts[1]/$sopts[2]} )
      shift 2 sopts
    done

    # Then we walk through the descriptions plus a few builtin ones.
    # The last one matches all options; the `special' description and action
    # makes those options be completed without an argument description.

    argv+=(
      '*=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.

      # This is the first bit of the arguments in the special form
      # for converting --help texts, taking account of any quoting
      # of colons.
      pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}"
      # Any action specifications that go with it.
      descr="${1#${pattern}}"
      if [[ "$pattern" = *\(-\) ]]; then
	# This is the special form to disallow arguments
	# in the next word.
        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.

      # Ignore :descriptions at the ends of lopts for matching this;
      # they aren't in the patterns.
      tmp=("${(@M)lopts:##$~pattern:*}")
      lopts=("${(@)lopts:##$~pattern:*}")

      (( $#tmp )) || continue

      opt=''

      # Clean suffix ':' added earlier
      tmp=("${(@)tmp%:}")

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

      tmpo=("${(@M)tmp:#[^:]##\[\=*}")
      if (( $#tmpo )); then
        tmp=("${(@)tmp:#[^:]##\[\=*}")

	for opt in "$tmpo[@]"; do
	  # Look for --option:description and turn it into
	  # --option[description].  We didn't do that above
	  # since it could get confused with the [=ARG] stuff.
	  if [[ $opt = (#b)(*):([^:]#) ]]; then
	    opt=$match[1]
	    odescr="[${match[2]}]"
	  else
	    odescr=
	  fi
	  if [[ $opt = (#b)(*)\[\=* ]]; then
	    opt2=${${match[1]}//[^a-zA-Z0-9_-]}=-${dir}${odescr}
	  else
	    opt2=${${opt}//[^a-zA-Z0-9_-]}=${dir}${odescr}
	  fi
	  if [[ "$descr" = :\=* ]]; then
	    cache+=( "${opt2}::${(L)${opt%\]}#*\=}: " )
	  elif [[ "$descr" = ::* ]]; then
	    cache+=( "${opt2}${descr}" )
	  else
	    cache+=( "${opt2}:${descr}" )
	  fi
	done
      fi

      # Descriptions with `=': mandatory argument.
      # Basically the same as the foregoing.
      # TODO: could they be combined?

      tmpo=("${(@M)tmp:#[^:]##\=*}")
      if (( $#tmpo )); then
        tmp=("${(@)tmp:#[^:]##\=*}")

	for opt in "$tmpo[@]"; do
	  if [[ $opt = (#b)(*):([^:]#) ]]; then
	    opt=$match[1]
	    odescr="[${match[2]}]"
	  else
	    odescr=
	  fi
	  opt2="${${opt%%\=*}//[^a-zA-Z0-9_-]}=${dir}${odescr}"
	  if [[ "$descr" = :\=* ]]; then
	    cache+=( "${opt2}:${(L)${opt%\]}#*\=}: " )
	  else
	    cache+=( "${opt2}${descr}" )
	  fi
	done
      fi

      # Everything else is just added as an option without arguments or
      # as described by $descr.

      if (( $#tmp )); then
        tmp=(
	  # commands with a description of the option (as opposed
	  # to the argument, which is what descr contains): needs to be
	  # "option[description]".
	  # Careful: \[ on RHS of substitution keeps the backslash,
	  # I discovered after about half an hour, so don't do that.
	  "${(@)^${(@)tmp:#^*:*}//:/[}]"
	  # commands with no description
	  "${(@)${(@)tmp:#*:*}//[^a-zA-Z0-9_-]}")
        if [[ -n "$descr" && "$descr" != ': :  ' ]]; then
	  cache+=( "${(@)^tmp}${descr}" )
        else
	  cache+=( "$tmp[@]" )
        fi
      fi
    done
    set -A "$name" "${(@)cache:# #}"
  fi
  set -- "$tmpargv[@]" "${(@P)name}"
fi

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

if (( $# )) && comparguments -i "$autod" "$singopt[@]" "$@"; then
  local action noargs aret expl local tried ret=1
  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=()
  state_descr=()

  while true; do
    while _tags; do
      anum=1
      if [[ -z "$tried" ]]; then
        while [[ anum -le  $#descrs ]]; do

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

	  if [[ $subc = argument* && -n $setnormarg ]]; then
	    comparguments -n NORMARG
	  fi

          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+=( "$action" )
                state_descr+=( "$descr" )
	        if [[ -n "$usecc" ]]; then
	          curcontext="${oldcontext%:*}:$subc"
	        else
	          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 -e "$subc" "$descr"
	        mesg=yes
	        tried=yes
                alwopt=${alwopt:-yes}
              elif [[ "$action" = \(\(*\)\) ]]; then

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

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

                _describe -t "$subc" "$descr" ws -M "$matcher" "$subopts[@]" ||
                    alwopt=${alwopt:-yes}
	        tried=yes

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

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

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

                _all_labels "$subc" expl "$descr" compadd "$subopts[@]" -a - ws ||
                    alwopt=${alwopt:-yes}
	        tried=yes
              elif [[ "$action" = \{*\} ]]; then

                # A string in braces is evaluated.

                while _next_label "$subc" expl "$descr"; do
                  eval "$action[2,-2]" && ret=0
                done
                (( ret )) && alwopt=${alwopt:-yes}
	        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[@]" && ret=0
                done
                (( ret )) && alwopt=${alwopt:-yes}
	        tried=yes
              else

                # Otherwise we call it with the description-arguments.

	        eval "action=( $action )"
                while _next_label "$subc" expl "$descr"; do
                  "$action[1]" "$subopts[@]" "$expl[@]" "${(@)action[2,-1]}" && ret=0
	        done
                (( ret )) && alwopt=${alwopt:-yes}
	        tried=yes
              fi
            fi
          fi
        done
      fi
      if _requested options &&
         [[ -z "$hasopts" &&
            -z "$matched" &&
            ( -z "$aret" || "$PREFIX" = "$origpre" ) ]] &&
          { ! zstyle -T ":completion:${oldcontext%:*}:options" prefix-needed ||
            [[ "$origpre" = [-+]* || -z "$aret$mesg$tried" ]] } ; then
	local prevpre="$PREFIX" previpre="$IPREFIX" prevcontext="$curcontext"

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

	hasopts=yes

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

        if [[ -z "$alwopt" || -z "$tried" || "$alwopt" = arg ]] &&
           comparguments -s single; then

          if [[ "$single" = direct ]]; then
            _all_labels options expl option \
	        compadd -QS '' - "${PREFIX}${SUFFIX}"
          elif [[ -z "$optarg" && "$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=( "${(@M)tmp1:#${PREFIX[1]}*}" )

            [[ "$single" = next ]] &&
                tmp1=( "${(@)tmp1:#[-+]${PREFIX[-1]}((#e)|:*)}" )

	    [[ "$PREFIX" != --* ]] && tmp1=( "${(@)tmp1:#--*}" )
	    tmp3=( "${(M@)tmp1:#[-+]?[^:]*}" )
	    tmp1=( "${(M@)tmp1:#[-+]?(|:*)}" )
	    tmp2=( "${PREFIX}${(@M)^${(@)${(@)tmp1%%:*}#[-+]}:#?}" )

            _describe -O option \
                      tmp1 tmp2 -Q -S '' -- \
		      tmp3 -Q

            [[ -n "$optarg" && "$single" = next && nm -eq $compstate[nmatches] ]] &&
                _all_labels options expl option \
	            compadd -Q - "${PREFIX}${SUFFIX}"

          fi
          single=yes
        else
          next+=( "$odirect[@]" )
          _describe -O option \
                    next -Q -M "$matcher" -- \
                    direct -QS '' -M "$matcher" -- \
                    equal -QqS= -M "$matcher"
        fi
	PREFIX="$prevpre"
	IPREFIX="$previpre"
        curcontext="$prevcontext"
      fi
      [[ -n "$tried" && "${${alwopt:+$origpre}:-$PREFIX}" != [-+]* ]] && break
    done
    if [[ -n "$opts" && -z "$aret" &&
          -z "$matched" &&
          ( -z "$tried" || -n "$alwopt" ) &&
          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"

  if [[ -n "$aret" ]]; then
    [[ -n $rawret ]] && return 300

### Returning non-zero would allow the calling function to add its own
### completions if we generated only options and have to use a ->state
### action.  But if that then doesn't generate matches, the calling
### function's return value would be wrong unless it compares
### $compstate[nmatches] to its previous value.  Ugly.
###
###    return 1
  else
    [[ -n "$noargs" && nm -eq "$compstate[nmatches]" ]] && _message "$noargs"
  fi
  # Set the return value.

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