#autoload

# This function tries to automatically complete long option names. For 
# this it invokes the command from the line with the `--help' option
# and then parses the output to find possible option names, so you
# should be careful to make sure that this function is not called for
# a command that does not support this option.
#
# For options that get an argument after a `=', the function also tries
# to automatically find out what should be completed as the argument.
# The possible completions for option-arguments can be described with
# the arguments to this function. This is done by giving pairs of
# patterns and actions as consecutive arguments. The actions specify
# what should be done to complete arguments of those options that match 
# the pattern. The action may be a list of words in brackets or in
# parentheses, separated by spaces. A list in brackets denotes
# possible values for an optional argument, a list in parentheses
# gives words to complete for mandatory arguments. If the action does
# not start with a bracket or parentheses, it should be the name of a
# command (probably with arguments) that should be invoked to complete 
# after the equal sign. E.g.:
#
#  _long_options '*\*'     '(yes no)' \
#                '*=FILE*' '_files' \
#                '*=DIR*'  '_files -/'
#
# This makes `yes' and `no' be completed as the argument of options
# whose description ends in a star, file names for options that
# contain the substring `=FILE' in the description, and paths for
# options whose description contains `=DIR'. Note that the last two
# patterns are not needed since this function always completes files
# for option descriptions containing `=FILE' and paths for option
# descriptions that contain `=DIR' or `=PATH'. These builtin patterns
# can be overridden by patterns given as arguments, though.
# 
# This function accepts the following options:
#
# -t   do completion only on words starting with two hyphens
#
# -i   list of patterns. Options, matching these patterns, are ignored.
#      The list may be given as a array name or as a literal list in braces.
#      E.g. _long_options -i '(--(enable|disable)-FEATURE*)' will ignore
#      --enable-FEATURE, that is listed in configure help output
#
# -s   list of pattern/replacement pairs. The argument is the same as above.
#      E.g. configure often lists only --enable but accepts both
#      --enable and --disable options.
#      _long_options -s '(#--enable- --disable)' will accept both forms.
#
# This function also accepts the `-X', `-J', and `-V' options which
# are given to `compadd'. 

local opt expl group test i name action ret=1 tmp suffix iopts sopts

setopt extendedglob

# Get the options.

group=()
expl=()
if [[ $1 = -*~--* ]]; then
  while getopts "J:V:X:ti:s:" opt; do
    case "$opt" in
      [JV]) group=("-$opt" "$OPTARG");;
      X)    expl=(-X "$OPTARG");;
      t)    test=yes;;
      i)    if [[ "$OPTARG[1]" = '(' ]]; then
              iopts=( ${=OPTARG[2,-2]} )
	    else
              iopts=( ${(P)${OPTARG}} )
	    fi
      ;;
      s)    if [[ "$OPTARG[1]" = '(' ]]; then
              sopts=( ${=OPTARG[2,-2]} )
	    else
              sopts=( ${(P)${OPTARG}} )
	    fi
      ;;
    esac
  done
  shift OPTIND-1
fi

# Test if we are completing after `--' if we were asked to do so.

[[ -n "$test" && "$PREFIX" != --* ]] && return 1

# We cache the information about options and the command name, see if
# we can use the cache.

if [[ "$words[1]" = (.|..)/* ]]; then
  tmp="$PWD/$words[1]"
else
  tmp="$words[1]"
fi

if [[ "$tmp" != $_lo_cache_cmd ]]; then

  # No, store the new command name and clear the old parameters.

  _lo_cache_cmd="$tmp"
  (( $+_lo_cache_actions )) && unset "$_lo_cache_names[@]" _lo_cache_actions _lo_cache_names

  local opts pattern anum=1 tmpo str
  typeset -U opts

  # 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 int
  # 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 comma after the
  # option up to the end. 

  opts=("--${(@)^${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$("$words[1]" --help)}:#[ 	]#-*}//,/
}}:#[ 	]#--*}#*--}%%[, ]*}")

  # Now remove all ignored options ...

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

  # ... and add "same" options

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

  # The interpretation of the options is completely table driven. We
  # use the positional parameters we were given and a few standard
  # ones. Then we loop through this table.

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

  while [[ $# -gt 1 ]]; do

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

    pattern="$1"
    action="$2"
    shift 2

    # 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)opts:##$~pattern}")
    opts=("${(@)opts:##$~pattern}")

    (( $#tmp )) || continue

    # Now we collect the options for the pattern in an array. We also
    # check if the options take an argument after a `=', and if this
    # argument is optional. The name of the array built contains
    # `_arg_' for mandatory arguments, `_optarg_' for optional
    # arguments, and `_simple_' for options that don't get an
    # argument. In `_lo_cache_names' we save the names of these
    # arrays and in `_lo_cache_actions' the associated actions.

    # If the action is a list of words in brackets, this denotes
    # options that get an optional argument. If the action is a list
    # of words in parentheses, the option has to get an argument.
    # In both cases we just build the array name to use.

    if [[ "$action[1]" = '[' ]]; then
      name="_lo_cache_optarg_$anum"
    elif [[ "$action[1]" = '(' ]]; then
      name="_lo_cache_arg_$anum"
    else

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

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

        # ...by removing them from the option list and storing them in 
	# an array.

        tmp=("${(@)tmp:#*\[\=*}")
        tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
        _lo_cache_names[anum]="_lo_cache_optarg_$anum"
        _lo_cache_actions[anum]="$action"
        eval "_lo_cache_optarg_${anum}=(\"\$tmpo[@]\")"
	(( anum++ ))
      fi

      # Now we do the same for option strings containing `=', these
      # are options getting an argument.

      tmpo=("${(@M)tmp:#*\=*}")
      if (( $#tmpo )); then
        tmp=("${(@)tmp:#*\=*}")
        tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
        _lo_cache_names[anum]="_lo_cache_arg_$anum"
        _lo_cache_actions[anum]="$action"
        eval "_lo_cache_arg_${anum}=(\"\$tmpo[@]\")"
	(( anum++ ))
      fi

      # The name for the options without arguments, if any.

      name="_lo_cache_simple_$anum"
    fi
    # Now filter out any option strings we don't like and stuff them
    # in an array, if there are still some.

    tmp=("${(@)${(@)tmp%%\=*}//[^a-zA-Z0-9-]}")
    if (( $#tmp )); then
      _lo_cache_names[anum]="$name"
      _lo_cache_actions[anum]="$action"
      eval "${name}=(\"\$tmp[@]\")"
      (( anum++ ))
    fi
  done
fi

# We get the string from the line and and see if it already contains a 
# equal sign.

str="$PREFIX$SUFFIX"

if [[ "$str" = *\=* ]]; then

  # It contains a `=', now we ignore anything up to it, but first save 
  # the old contents of the special parameters we change.

  local oipre opre osuf pre parto parta pat patflags anum=1

  oipre="$IPREFIX"
  opre="$PREFIX"
  osuf="$SUFFIX"

  pre="${str%%\=*}"

  # Then we walk through the array names. For each array we test if it 
  # contains the option string. If so, we `invoke' the action stored
  # with the name. If the action is a list of words, we just add them, 
  # otherwise we invoke the command or function named.

  for name in "$_lo_cache_names[@]"; do
    action="$_lo_cache_actions[anum]"
    if (( ${(@)${(@P)name}[(I)$pre]} )); then
      IPREFIX="${oipre}${pre}="
      PREFIX="${str#*\=}"
      SUFFIX=""
      if [[ "$action[1]" = (\[|\() ]]; then
        compadd - ${=action[2,-2]}
      elif (( $#action )); then
        $=action
      fi

      # We found the option string, return.

      return
    fi

    # The array did not contain the full option string, see if it
    # contains a string matching the string from the line.
    # If there is one, we store the option string in `parto' and the
    # element from `_lo_actions' in `parta'. If we find more than one
    # such option or if we already had one, we set `parto' to `-'.

    PREFIX="${str%%\=*}"
    SUFFIX=""
    compadd -O tmp -M 'r:|-=* r:|=*' - "${(@P)name}"

    if [[ $#tmp -eq 1 ]]; then
      if [[ -z "$parto" ]]; then
        parto="$tmp[1]"
	parta="$action"
      else
        parto=-
      fi
    elif (( $#tmp )); then
      parto=-
    fi
    (( anum++ ))
  done

  # If we found only one matching option, we accept it and immediatly
  # try to complete the string after the `='.

  if [[ -n "$parto" && "$parto" != - ]]; then
    IPREFIX="${oipre}${parto}="
    PREFIX="${str#*\=}"
    SUFFIX=""
    if (( $#parta )); then
      if [[ "$parta[1]" = (\[|\() ]]; then
        compadd - ${=parta[2,-2]}
      else
        $=parta
      fi
    else
      compadd -S '' - "$PREFIX"
    fi
    return
  fi

  # The option string was not found, restore the special parameters.

  IPREFIX="$oipre"
  PREFIX="$opre"
  SUFFIX="$osuf"
fi

# The string on the line did not contain a `=', or we couldn't
# complete the option string since there were more than one matching
# what's on the line. So we just add the option strings as possible
# matches, giving the string from the `=' on as a suffix.

if [[ "$str" = *\=* ]]; then
  str="=${str#*\=}"
  PREFIX="${PREFIX%%\=*}"
  suffix=()
else
  str=""
  suffix=('-S=')
fi

anum=1
for name in "$_lo_cache_names[@]"; do
  action="$_lo_cache_actions[anum]"

  if [[ "$name" = *_optarg_* ]]; then
    compadd -M 'r:|-=* r:|=*' -Qq "$suffix[@]" -s "$str" - \
            "${(@P)name}" && ret=0
  elif [[ "$name" = *_arg_* ]]; then
    compadd -M 'r:|-=* r:|=*' -Q "$suffix[@]" -s "$str" - \
            "${(@P)name}" && ret=0
  elif [[ -z "$str" ]]; then
    compadd -M 'r:|-=* r:|=*' -Q - \
            "${(@P)name}" && ret=0
  fi
  (( anum++ ))
done

return ret
