#compdef zstyle

local state context ostate line expl ctop suf
local nm=$compstate[nmatches] taglist patterns pstyles contexts
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:
#   c   completion
#   d   chpwd
#   e   line editor
#   v   vcs_info
#   z   zftp
# or any combination of the above,
# followed by a colon, followed by a state to enter, empty if none.
styles=(
  accept-exact		 c:bool
  accept-exact-dirs	 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
  command-path		 c:_dir_list
  commands               c:
  complete               c:bool
  complete-options	 c:bool
  completer		 c:completer
  condition		 c:bool
  cursor		 c:cursor
  delimiters		 c:
  disabled               c:bool
  domains                c:
  environ		 c:environ
  expand		 c:
  extra-verbose		 c:bool
  fake			 c:
  fake-always            c:
  fake-files		 c:fake-files
  fake-parameters	 c:fake-params
  file-list              c:bool
  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			 ce:listwhen
  list-colors		 c:
  list-dirs-first	 c:bool
  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
  path-completion	 c:bool
  pine-directory         c:_directories
  ports			 c:_ports
  prefix-hidden		 c:bool
  prefix-needed		 c:bool
  preserve-prefix        c:preserve-prefix
  range                  c:
  recent-dirs-insert     c:recent-dirs-insert
  regular                c:bool
  rehash		 c:bool
  remote-access		 c:bool
  remove-all-dups	 c:bool
  select-prompt          c:
  select-scroll          c:
  separate-sections      c:bool
  show-completer	 c:bool
  single-ignored         c:single-ignored
  sort			 c:bool
  special-dirs		 c:sdirs
  squeeze-slashes	 c:bool
  stop			 c:stop
  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
  use-ip		 c:bool
  use-perl		 c:bool
  users			 c:_users
  users-hosts		 c:user-host
  users-hosts-ports	 c:user-host-port
  verbose		 ce:bool
  word			 c:bool

  recent-dirs-default    d:bool
  recent-dirs-file       d:_files
  recent-dirs-max        d:
  recent-dirs-prune      d:
  recent-dirs-pushd      d:bool

  auto-previous          e:bool
  break-keys             e:
  cursor                 e:
  edit-buffer            e:bool
  edit-previous          e:bool
  insert-kept            e:
  leave-cursor           e:bool
  match                  e:
  prompt                 e:
  stop-keys              e:
  toggle                 e:bool
  url-globbers           e:
  url-local-schema       e:
  url-metas              e:
  url-other-schema       e:
  url-seps               e:
  whence                 e:
  word-chars             e:
  word-style             e:word-style
  word-context           e:

  formats                v:vcs-format
  actionformats          v:vcs-format
  branchformat           v:branch-format
  nvcsformats            v:
  stgitformat            v:stg-format
  max-exports            v:
  enable                 v:vcs
  disable                v:vcs
  disable-patterns       v:
  check-for-changes      v:bool
  check-for-staged-changes
                         v:bool
  stagedstr              v:
  unstagedstr            v:
  command                v:_command_names
  use-server             v:bool
  use-simple             v:bool
  get-revision           v:bool
  use-prompt-escapes     v: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 : -qS : completion vcs_info zftp
      elif compset -P :completion:; then
        contexts=( functions _completers cmdorcont argument tag )
      elif compset -P :vcs_info:; then
        contexts=( vcs-string user-context repo-root-name )
      fi
      if (( $#contexts )); then
        for ostate in $contexts; do
	  compset -P '[^:]#:' || break
	done
	suf=()
	compset -S ':*' || suf=( -qS: )
	[[ $ostate = $contexts[-1] ]] && 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.
      case ${(Q)line[1]} in
	(:completion:*)
	ctop=c
	;;

	(:chwpd:*)
	ctop=d
	;;

	(:zftp:*)
	ctop=z
	;;

        (:vcs_info:*)
        ctop=v
        ;;

	(:zle:*)
	ctop=e
	;;

	(*)
        ctop=cdez
	;;
      esac
      _wanted styles expl style \
         compadd -M 'r:|-=* r:|=*' -k "styles[(R)[^:]#[$ctop][^:]#:*]"
      ;;

    (style-arg)
      state+=( "${styles[${(Q)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
      ;;

    (environ)
      if [[ "$PREFIX" = *\=* ]]; then
	compstate[parameter]="${PREFIX%%\=*}"
	compset -P 1 '*='
	_value
      else
        suf=()
	compset -S '=*' || suf=(-S '=')
	_parameters -q $suf
      fi
      ;;

    (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'
      ;;

    (recent-dirs-insert)
      _wanted values expl 'inserting recent directories' \
	compadd true false always fallback both
      ;;

    (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
      ;;

    (word-style)
      _wanted word-styles expl 'word style' compadd normal shell space
      ;;

    (vcs-string)
      _wanted vcs expl vcs compadd $suf - \
          ${${(f)"$(_call_program vcs vcs_info_printsys 2>/dev/null)"}:#\#*}
      ;;

    (vcs)
      _wanted vcs expl vcs compadd \
          ${${(f)"$(_call_program vcs vcs_info_printsys 2>/dev/null)"}:#(\#|*-)*}
      ;;

    (user-context)
      _wanted user-contexts expl 'user context' compadd $suf default command
      ;;

    (repo-root-name)
      _message -e names 'repository root name'
      ;;

    (vcs-format)
      if [[ $PREFIX = *% ]]; then
        compset -P '*%'
        _values -s '' 'format replacement' \
          's[the vcs in use]' \
          'b[the current branch]' \
          'a[an identifier describing the action]' \
          'i[current revision no/id]' \
          'c[details of staged changes]' \
          'u[details of unstaged changes]' \
          'r[repository name]' \
          'S[subdirectory within repository]' \
          'm[misc replacement - backend specific]'
      else
        _message -e formats 'vcs format'
      fi
      ;;

    (branch-format)
      if [[ $PREFIX = *% ]]; then
        compset -P '*%'
        _values -s '' 'format replacement' \
          'b[the current branch]' \
          'r[current revision number]' \
      else
        _message -e formats 'vcs format'
      fi
      ;;

    (stg-format)
      if [[ $PREFIX = *% ]]; then
        compset -P '*%'
        _values -s '' 'format replacement' \
          'p[patch currently on top of the stack]' \
          'c[the number of unapplied patches]'
      else
        _message -e formats 'vcs format'
      fi
      ;;

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

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