#compdef zstyle

local state context ostate line expl ctop suf
local nm=$compstate[nmatches] taglist patterns pstyles
typeset -A opt_args styles

(( $+functions[_completers] )) ||
_completers() {
  # option: -p - needs a `_' prefix
  local us
  local -a disp list expl
  
  list=( complete approximate correct match expand list menu oldlist
         ignored prefix history )
  zparseopts -D -K -E 'p=us'
  [[ -n "$us" ]] && us='_'
  zstyle -t ":completion:${curcontext}:completers" prefix-hidden &&
      disp=(-d list)
  _wanted completers expl 'completer' \
      compadd "$@" "$disp[@]" - "$us${^list[@]}"
}

# Assoc array of styles; the values give the possible top-level
# contexts (currently c for completion, z for zftp or cz for both),
# followed by a colon, followed by a state to enter, empty if none.
styles=(
  accept-exact		 c:bool
  add-space		 c:bool
  ambiguous              c:bool
  assign-list            c:_parameters
  auto-description	 c:
  avoid-completer        c:completer
  break-keys             c:
  cache-path		 c:_directories
  cache-policy           c:_functions
  call-command           c:bool
  command                c:command
  commands               c:
  complete               c:bool
  completer		 c:completer
  condition		 c:bool
  cursor		 c:cursor
  disabled               c:bool
  domains                c:
  expand		 c:
  fake-files		 c:fake-files
  fake-parameters	 c:fake-params
  file-patterns		 c:filepat
  file-sort              c:fsort
  filter		 c:
  force-list             c:
  format		 c:
  glob			 c:bool
  global                 c:bool
  group-name		 c:
  group-order		 c:tag
  groups		 c:_groups
  hidden		 c:bool
  hosts			 c:_hosts
  hosts-ports		 c:host-port
  ignore-line            c:ignline
  ignore-parents         c:ignorepar
  ignored-patterns	 c:
  insert-ids             c:insert-ids
  insert-tab             c:bool
  insert-unambiguous	 c:insunambig
  keep-prefix		 c:keep-prefix
  last-prompt		 c:bool
  list			 c:listwhen
  list-colors		 c:
  list-grouped		 c:bool
  list-packed		 c:bool
  list-prompt            c:
  list-rows-first	 c:bool
  list-separator	 c:separator
  list-suffixes		 c:bool
  local			 c:
  mail-directory         c:_directories
  match-original	 c:match-orig
  matcher		 c:
  matcher-list		 c:
  max-errors		 c:
  max-matches-width      c:max-matches-width
  menu			 c:boolauto
  muttrc                 c:_files
  numbers		 c:bool
  old-list		 c:bool
  old-matches            c:oldmatches
  old-menu		 c:bool 
  original		 c:bool
  packageset		 c:packageset
  path			 c:_directories
  pager			 c:_command_names
  pine-directory         c:_directories
  ports			 c:_ports
  prefix-hidden		 c:bool
  prefix-needed		 c:bool
  preserve-prefix        c:preserve-prefix
  range                  c:
  regular                c:bool
  remote-access		 c:bool
  remove-all-dups	 c:bool
  select-prompt          c:
  select-scroll          c:
  separate-sections      c:bool
  single-ignored         c:single-ignored
  sort			 c:bool
  special-dirs		 c:sdirs
  squeeze-slashes	 c:bool
  stop			 c:stop
  stop-keys		 c:
  strip-comments	 c:bool
  subst-globs-only       c:bool
  substitute		 c:bool
  suffix		 c:bool
  tag-order		 c:tag-order
  try-to-use-pminst	 c:bool
  urls                   c:_urls
  use-cache		 c:bool
  use-compctl		 c:urgh
  users			 c:_users
  users-hosts		 c:user-host
  users-hosts-ports	 c:user-host-port
  verbose		 c:bool
  word			 c:bool

  chpwd			 z:bool
  progress		 z:progress
  remote-glob		 z:bool
  titlebar		 z:bool
  update		 z:
)

taglist=(
  accounts all-expansions all-files arguments arrays association-keys
  bookmarks builtins characters colormapids colors commands contexts
  corrections cursors default descriptions devices directories
  directory-stack displays expansions extensions files flags fstypes
  fonts functions globbed-files groups history-words hosts indexes
  interfaces jobs keymaps keysyms libraries limits local-directories
  mailboxes manuals maps messages modifiers modules my-accounts
  named-directories names newsgroups nicknames options original
  other-accounts packages parameters path-directories paths pods ports
  prefixes printers processes processes-names ps regex sequences
  sessions signals strings styles tags targets timezones types urls
  users values variant visuals warnings widgets windows zsh-options
  email-address ${(k)functions[(I)_email-*]#_}
)

_arguments -C \
  '(: -)-L[output in form of zstyle commands]' \
  '(: -)-d[delete style definitions]:context pattern:->patterns:*:styles:->pstyles' \
  '(-)-e[value is evaluated when style is looked up]' \
  ':context:->contexts' ':style:->styles' '*:argument:->style-arg'

while (( $#state )); do
  case "$state[1]" in
    contexts)
      if [[ ! -prefix :*: ]]; then
	_wanted contexts expl context compadd -P : -S : completion zftp
      elif compset -P :completion:; then
        for ostate in functions _completers cmdorcont argument tag; do
	  compset -P '[^:]#:' || break
	done
	suf=()
	compset -S ':*' || suf=( -qS: )
	[[ $ostate = tag ]] && suf=()
	if compset -P '(|\\)\((*\||)'; then  # handle (x|y) patterns
	  suf=()
	  compset -S '(|\\)[)|]*' ||
	      suf=( -S "${${QIPREFIX:+|}:-\|}" -r "${${QIPREFIX:+|}:-\\\\} \t)" )
	fi
	state+=( "$ostate" )
      fi
      ;;

    patterns)
      zstyle -g patterns
      _wanted contexts expl 'context pattern' compadd -a patterns
      ;;
    
    pstyles)
      zstyle -g pstyles ${(Q)${(M)opt_args[-d]#*[^\\]:}%:}
      _wanted styles expl style compadd -a pstyles
    ;;

    styles)
      # Get the top-level context we're completing for, if any.
      if [[ $line[1] = :(completion|zftp):* ]]; then
	ctop=${line[1][2]}
      else
        ctop=cz
      fi
      _wanted styles expl style \
         compadd -M 'r:|-=* r:|=*' -k "styles[(R)[^:]#[$ctop][^:]#:*]"
      ;;
      
    style-arg)
      state+=( "${styles[$line[2]]#*:}" )
      ;;

    argument)
      _message -e arguments argument
      ;;
    
    bool) 
      _wanted values expl boolean compadd true false
      ;;

    boolauto) 
      _wanted values expl boolean \
          compadd true false auto select search search-backward interactive
      ;;

    cmdorcont)
      _alternative -O suf \
        'commands:command:_command ' \
        'contexts:context:(-array-value- -brace-parameter- -command- -condition- -math- -parameter- -redirect- -subscript- -value-)'
     ;;

    cursor)
      _wanted values expl 'cursor positioning' compadd complete key default
      ;;

    completer)
      _wanted values expl completer _completers -p
      ;;

    fsort)
      _wanted values expl 'how to sort files' \
	compadd name size links time date modification access inode change reverse
      ;;

    function)
      _wanted control-function expl 'control function' \
          compadd predict-on all-matches
      ;;

    functions)
      _wanted comp-widget expl 'completion widget' \
          compadd $suf - all-matches complete-debug complete-tag \
	  correct-word expand-word expand-alias-word history-words
      ;;

    user-host-port)
      if [[ $PREFIX != *[@:]* ]]; then
	_users -S @
      elif [[ $PREFIX = *[@:]*[[@:]* ]]; then
	compset -P 2 '*[:@]'
	_ports
      else
	compset -P 1 '*[:@]'
	_hosts -S :
      fi
      ;;

    host-port)
      if [[ $PREFIX != *:* ]]; then
	_hosts -S :
      else
	compset -P 1 '*:'
	_ports
      fi
      ;;

    listwhen)
      _wanted values expl 'when to list completions' \
	compadd always never sometimes
      ;;

    packageset)
      _wanted values expl 'default package set' \
        compadd available installed uninstalled
      ;;

    progress)
      _wanted values expl 'progress meter style' \
        compadd none bar percent
      ;;

    sdirs)
      _wanted values expl 'whether to complete . or ..' \
        compadd true false ..
      ;;

    stop)
      _wanted values expl 'when to insert matches' \
	compadd true false verbose
      ;;

    tag)
      _wanted tags expl tag compadd $suf -a taglist
      ;;

    tag-order)
      if compset -P '*:*:'; then
        _message -e descriptions description
      elif compset -P '*:'; then
        _message -e labels 'tag label'
      else
        suf=()
        compset -S ':*' || suf=( -qS: )
        _wanted values expl tag compadd $suf -a taglist
      fi
      ;;

    filepat)
      if compset -P '*:*:'; then
        _message -e descriptions description
      elif compset -P '*:'; then
        _message -e tags tag
      else
        _message -e patterns 'glob patterns'
      fi
      ;;

    user-host)
      if [[ $PREFIX = *[@:]* ]]; then
	compset -P '*[@:]'
	_hosts
      else
	_users -S @
      fi
      ;;

    ignorepar)
      _wanted values expl 'which parents to ignore' \
        compadd parent pwd .. directory
      ;;

    single-ignored)
      _wanted values expl 'how to handle a single ignored match' \
          compadd - show menu
      ;;

    command)
      shift 3 words
      (( CURRENT -= 3 ))
      _normal
      ;;

    insert-ids)
      _wanted values expl 'when to insert process IDs' \
          compadd - menu single longer
      ;;

    fake-files)
      _message -e fakes 'prefix and names'
      ;;

    fake-params)
      if compset -P '*:'; then
	_wanted values expl 'parameter type' compadd scalar array integer
      else
        suf=''
        compset -S ':*' || suf='-qS:'
      	_wanted values expl 'fake parameter' _parameters $suf
      fi
      ;;

    ignline) 
      _wanted values expl 'ignore strings on the line' compadd true false current current-shown other
      ;;

    keep-prefix) 
      _wanted values expl 'keep prefix' compadd true false changed
      ;;

    match-orig) 
      _wanted values expl "match without inserting \`*'" compadd only both
      ;;

    oldmatches) 
      _wanted values expl 'use list of old matches' compadd true false only
      ;;

    insunambig) 
      _wanted values expl 'insert unambiguous string' compadd true false pattern
      ;;

    preserve-prefix)
      _message -e prefixes 'pattern matching prefix to keep'
      ;;

    separator)
      _message -e separators 'separator string'
      ;;

    max-matches-width)
      _message -e numbers 'maximum display width for matches'
      ;;

    urgh) 
      _wanted values expl no compadd no false off 0
      ;;

    _*)
      ${=state[1]} $suf
      ;;
  esac
  shift state
done

[[ $compstate[nmatches] != $nm ]]
