#compdef mvn mvnDebug
# ------------------------------------------------------------------------------
# Copyright (c) 2010-2011 zsh-users
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the zsh-users nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL ZSH-USERS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# Description
# -----------
#
#  Completion script for Maven (http://maven.apache.org).
#
#  Status: Incomplete, see FIXME and TODO tags.
#
# ------------------------------------------------------------------------------
# Authors
# -------
#
#  * Julien Nicoulaud <julien.nicoulaud@gmail.com>
#
# ------------------------------------------------------------------------------
# How to tweak
# ------------
#
#   * By default only a few common utility plugins are displayed. To define your
#     own list of plugins:
#
#       maven_plugins=(
#         'jboss'
#         'tomcat'
#         'gwt:Maven plugin for the Google Web Toolkit'
#         'android:Maven Plugin for Android'
#       )
#       zstyle ':completion:*:mvn:*' plugins $maven_plugins
#
#   * By default advanced phases are not displayed. To have them displayed:
#
#           zstyle ':completion:*:mvn:*' show-advanced-phases true
#
#   * To have a better presentation of completions:
#
#       zstyle ':completion:*:*:mvn:*:matches' group 'yes'
#       zstyle ':completion:*:*:mvn:*:options' description 'yes'
#       zstyle ':completion:*:*:mvn:*:options' auto-description '%d'
#       zstyle ':completion:*:*:mvn:*:descriptions' format $'\e[1m -- %d --\e[22m'
#       zstyle ':completion:*:*:mvn:*:messages' format $'\e[1m -- %d --\e[22m'
#       zstyle ':completion:*:*:mvn:*:warnings' format $'\e[1m -- No matches found --\e[22m'
#
# ------------------------------------------------------------------------------
# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
# vim: ft=zsh sw=2 ts=2 et
# ------------------------------------------------------------------------------


_mvn()
{
  local curcontext="$curcontext" state line cmds ret=1 maven_version excl_opts

  excl_opts=(-h --help -v --version -ep --encrypt-password -emp --encrypt-master-password)

  _pick_variant -r maven_version maven3='Maven 3' maven2='Maven 2' unknown --version
  if [[ $maven_version == 'maven3' ]]; then
    opts=(
      "($excl_opts -T --threads)"{-T,--threads}'[thread count, for instance 2.0C where C is core multiplied]:thread count:_mvn_thread_counts'
      "($excl_opts -t --toolchains)"{-t,--toolchains}'[alternate path for the user toolchains file]:toolchains file:_mvn_toolchains_files'
      "($excl_opts -l --log-file)"{-l,--log-file}'[log file to where all build output will go]:log file:_mvn_log_files'
    )
  elif [[ $maven_version == 'maven2' ]]; then
    opts=(
      "($excl_opts -cpu --check-plugin-updates -up --update-plugins -npu --no-plugin-updates -o --offline)"{-cpu,--check-plugin-updates,-up,--update-plugins}'[force upToDate check for any relevant registered plugins]'
      "($excl_opts -npr --no-plugin-registry)"{-npr,--no-plugin-registry}'[don'\''t use plugin-registry.xml for plugin versions]'
      "($excl_opts -npu --no-plugin-updates -cpu --check-plugin-updates -up --update-plugins)"{-npu,--no-plugin-updates}'[suppress upToDate check for any relevant registered plugins]'
      "($excl_opts -r --reactor)"{-r,--reactor}'[dynamically build reactor from subdirectories]:reactor:_mvn_reactors'
    )
  else
    return ret
  fi

  [[ -n ${(M)words:#"-pl"} || -n ${(M)words:#"--projects"} ]] && opts+=(
    "($excl_opts -am --also-make)"{-am,--also-make}'[if project list is specified, also build projects required by the list]'
    "($excl_opts -amd --also-make-dependents)"{-amd,--also-make-dependents}'[if project list is specified, also build projects that depend on projects on the list]'
  )

  _arguments \
    "(- : *)"{-h,--help}'[display help information]' \
    "(- : *)"{-v,--version}'[display version information]' \
    "(- : *)"{-emp,--encrypt-master-password}'[encrypt master security password]:master password:_mvn_passwords' \
    "(- : *)"{-ep,--encrypt-password}'[encrypt server password]:password:_mvn_passwords' \
    "($excl_opts -B --batch-mode)"{-B,--batch-mode}'[run in non-interactive (batch) mode]' \
    "($excl_opts -V --show-version)"{-V,--show-version}'[display version information without stopping build]' \
    "($excl_opts -q --quiet -X --debug)"{-q,--quiet}'[quiet output, only show errors]' \
    "($excl_opts -X --debug -q --quiet)"{-X,--debug}'[produce execution debug output]' \
    "($excl_opts -N --non-recursive)"{-N,--non-recursive}'[do not recurse into sub-projects]' \
    "($excl_opts -C --strict-checksums -c --lax-checksums)"{-C,--strict-checksums}'[fail the build if checksums don'\''t match]' \
    "($excl_opts -c --lax-checksums -C --strict-checksums)"{-c,--lax-checksums}'[warn if checksums don'\''t match]' \
    "($excl_opts -e --errors)"{-e,--errors}'[produce execution error messages]' \
    "($excl_opts -f --file)"{-f,--file}'[force the use of an alternate POM file]:POM file:_mvn_pom_files' \
    "($excl_opts -s --settings)"{-s,--settings}'[alternate path for the user settings file]:settings file:_mvn_settings_files' \
    "($excl_opts -gs --global-settings)"{-gs,--global-settings}'[alternate path for the global settings file]:global settings file:_mvn_settings_files' \
    "($excl_opts -fae --fail-at-end -ff --fail-fast -fn --fail-never)"{-fae,--fail-at-end}'[only fail the build afterwards, allow all non-impacted builds to continue]' \
    "($excl_opts -ff --fail-fast -fae --fail-at-end -fn --fail-never)"{-ff,--fail-fast}'[stop at first failure in reactorized builds]' \
    "($excl_opts -fn --fail-never -fae --fail-at-end -ff --fail-fast)"{-fn,--fail-never}'[nerver fail the build, regardless of project result]' \
    "($excl_opts -P --activate-profiles)"{-P,--activate-profiles}'[comma-delimited list of profiles to activate]:profile:_mvn_profiles' \
    "($excl_opts -pl --projects)"{-pl,--projects}'[build specified reactor projects instead of all projects]:project list:_mvn_projects -s ,' \
    "($excl_opts -rf --resume-from)"{-rf,--resume-from}'[resume reactor from specified project]:project:_mvn_projects' \
    "($excl_opts -o --offline -U --update-snapshots -cpu --check-plugin-updates -up --update-plugins)"{-o,--offline}'[work offline]' \
    "($excl_opts -U --update-snapshots -nsu --no-snapshot-updates -o --offline)"{-U,--update-snapshots}'[force a check for updated releases and snapshots on remote repositories]' \
    "($excl_opts -nsu --no-snapshot-updates -U --update-snapshots -o --offline)"{-nsu,--no-snapshot-updates}'[Supress SNAPSHOT updates]' \
    "($excl_opts)"{-D-,--define}'[define a system property]:property:_mvn_properties' \
    "$opts[@]" \
    "($excl_opts)*: :_mvn_args" \
    && ret=0

  return ret
}

_mvn_args()
{
  local ret=1
  _alternative \
    'phases:phase:_mvn_phases' \
    'goals:goal:_mvn_plugin_goals' \
    && ret=0
  return ret
}

_mvn_phases()
{
  local ret=1 phases
  phases=(
    'clean:remove all files generated by the previous build'
    'compile:compile the source code of the project'
    'test:run tests using a suitable unit testing framework'
    'package:take the compiled code and package it in its distributable format, such as a JAR'
    'integration-test:process and deploy the package if necessary into an environment where integration tests can be run'
    'verify:run any checks to verify the package is valid and meets quality criteria'
    'install:install the package into the local repository, for use as a dependency in other projects locally'
    'deploy:done in an integration or release environment, copies the final package to the remote repository'
    'site:generates the projects site documentation'
    'site-deploy:deploys the generated site documentation to the specified web server'
  )
  if zstyle -t ":completion:${curcontext}:" show-advanced-phases; then
    phases+=(
      'pre-clean:executes processes needed prior to the actual project cleaning'
      'post-clean:executes processes needed to finalize the project cleaning'
      'validate:validate the project is correct and all necessary information is available'
      'initialize:initialize build state, e.g. set properties or create directories'
      'generate-sources:generate any source code for inclusion in compilation'
      'process-sources:process the source code, for example to filter any values'
      'generate-resources:generate resources for inclusion in the package'
      'process-resources:copy and process the resources into the destination directory, ready for packaging'
      'process-classes:post-process the generated files from compilation'
      'generate-test-sources:generate any test source code for inclusion in compilation'
      'process-test-sources:process the test source code, for example to filter any values'
      'generate-test-resources:create resources for testing'
      'process-test-resources:copy and process the resources into the test destination directory'
      'test-compile:compile the test source code into the test destination directory'
      'process-test-classes:post-process the generated files from test compilation'
      'prepare-package:perform any operations necessary to prepare a package before the actual packaging'
      'pre-integration-test:perform actions required before integration tests are executed'
      'post-integration-test:perform actions required after integration tests have been executed'
      'pre-site:executes processes needed prior to the actual project site generation.'
      'post-site:executes processes needed to finalize the site generation, and to prepare for site deployment'
    )
  fi
  _describe -t 'phases' "phase" phases && ret=0
  return ret
}

_mvn_plugin_goals()
{
  local ret=1
  if compset -P '*:'; then
    _mvn_goals "${IPREFIX%:}" && ret=0
  else
    _mvn_plugins && ret=0
  fi
  return ret
}

_mvn_plugins()
{
  local ret=1 plugins
  zstyle -a ":completion:${curcontext}:" plugins plugins
  [[ ${#plugins} -gt 0 ]] || plugins=(
    'assembly:create archives of your projects sources, classes, dependencies etc. from flexible assembly descriptors'
    'release:release a project with Maven, saving a lot of repetitive, manual work'
    'help:provides goals aimed at helping to make sense out of the build environment.'
    'versions:diagnose and update the versions of components in the POM'
    'deploy:add your artifact(s) to a remote repository for sharing with other developers and project'
    'dependency:provides the capability to manipulate artifacts'
    'archetype:create a Maven project from an existing template called an archetype'
    'site:generate a site for the project'
  )
  _describe -t 'plugin' "plugin" plugins -S ':' && ret=0
  return ret
}

_mvn_goals_caching_policy()
{
  # Rebuild if cache is older than one month.
  local -a oldp
  oldp=( "$1"(NmM+1) )
  (( $#oldp ))
}

_mvn_goals()
{
  local ret=1 plugin="$@" update_policy

  zstyle -s ":completion:${curcontext}:" cache-policy update_policy
  [[ -z "$update_policy" ]] && zstyle ":completion:${curcontext}:" cache-policy _mvn_goals_caching_policy

  unset goals
  if ( [[ ${+goals} -eq 0 ]] || _cache_invalid "mvn/plugins/$plugin" ) && ! _retrieve_cache "mvn/plugins/$plugin"; then
    setopt localoptions extendedglob
    goals=(${(s:,,,:)${${${(f)${${${(f)${${${${(F)${(S)${(f)"$(_call_program goals $words[1] -N org.apache.maven.plugins:maven-help-plugin:2.1.1:describe -Dplugin=$plugin)"}//#$plugin:/,,,}}:#*BUILD FAILURE*}#*This plugin has*goals#:}%For more information, run \'mvn help:describe*}}//:/\\:}}}// ##/ }// Description\\: /:}})
    [[ ${#goals} -gt 0 ]] && _store_cache "mvn/plugins/$plugin" goals
  fi

  [[ ${#goals} -gt 0 ]] && _describe -t "plugin-$plugin-goals" "$plugin plugin goal" goals && ret=0

  return
}

_mvn_reactors()
{
  # FIXME No idea what kind of value the "--reactor" option is supposed to take
  local ret=1
  _message -e reactors 'reactor' && ret=0
  return ret
}

_mvn_passwords()
{
  local ret=1
  _message -e passwords 'password' && ret=0
  return ret
}

_mvn_thread_counts()
{
  local ret=1 thread_counts
  thread_counts=(
    '1:build with 1 thread'  '1C:build with 1 thread per CPU core'
    '2:build with 2 threads' '2C:build with 2 threads per CPU core'
    '3:build with 3 threads' '3C:build with 3 threads per CPU core'
    '4:build with 4 threads' '4C:build with 4 threads per CPU core'
    '5:build with 5 threads' '5C:build with 5 threads per CPU core'
    '6:build with 6 threads' '6C:build with 6 threads per CPU core'
    '7:build with 7 threads' '7C:build with 7 threads per CPU core'
    '8:build with 8 threads' '8C:build with 8 threads per CPU core'
  )
  _describe -t 'thread-counts' "thread count" thread_counts && ret=0
  return ret
}

_mvn_pom_files()
{
  local ret=1
  _files -g '*pom*\.xml*' && ret=0
  return ret
}

_mvn_toolchains_files()
{
  local ret=1
  _files && ret=0
  return ret
}

_mvn_settings_files()
{
  local ret=1
  _files -g '*settings*\.xml*' && ret=0
  return ret
}

_mvn_log_files()
{
  local ret=1
  _files && ret=0
  return ret
}

_mvn_profiles_caching_policy()
{
  # Resolve the cache directory.
  local cache_dir
  zstyle -s ":completion:${curcontext}:" cache-path cache_dir
  : ${cache_dir:=${ZDOTDIR:-$HOME}/.zcompcache}

  # Rebuild if cached file more recent than cache.
  local cached_file="${1#$cache_dir}"
  [[ -f $cached_file && $cached_file -nt "$1" ]] && return 0

  # Rebuild if cache is older than one week.  
  local -a oldp
  oldp=( "$1"(Nmw+1) )
  (( $#oldp )) && return 0

  return 1
}

_mvn_profiles()
{
  # FIXME Use "mvn help:all-profiles" output instead of parsing...
  # Blocked on http://jira.codehaus.org/browse/MPH-82 and http://jira.codehaus.org/browse/MPH-83
  local ret=1 profs update_policy index settings_file files parent_pom_file cache_name profiles_section

  zstyle -s ":completion:${curcontext}:" cache-policy update_policy
  [[ -z "$update_policy" ]] && zstyle ":completion:${curcontext}:" cache-policy _mvn_profiles_caching_policy

  profs=()

  index=$words[(i)-s]
  [[ $index -gt ${#words} ]] && index=$words[(i)--settings]
  [[ $index -gt ${#words} ]] && settings_file=~/.m2/settings.xml || settings_file=$words[$index+1]

  if [[ -f  $settings_file ]]; then
    unset profiles
    cache_name="mvn/profiles${settings_file:A}"
    if ( [[ ${+profiles} -eq 0 ]] || _cache_invalid "$cache_name" ) && ! _retrieve_cache "$cache_name"; then
      profiles=()
      profiles_section="${(M)${(f)$(<$settings_file)}:#*<profiles>*}"
      if [[ -n "$profiles_section" ]]; then
        for profile in ${(s:,,,:)${${${(S)${(S)${(S)${(S)${${profiles_section#*<profile>}%</profile>*}//<repositories>*<\/repositories>}//<pluginRepositories>*<\/pluginRepositories>}//<build>*<\/build>}//<\/id>*<id>/,,,}##*<id>}%%</id>*}}; do
          [[ -z ${(M)profiles:#"$profile"*} ]] && profiles+=("$profile"'['"in settings file"']')
        done
      fi
      [[ ${#profiles} -gt 0 ]] && _store_cache "$cache_name" profiles
    fi
    profs+=($profiles)
  fi

  index=$words[(i)-f]
  [[ $index -gt ${#words} ]] && index=$words[(i)--file]
  [[ $index -gt ${#words} ]] && parent_pom_file=pom.xml || parent_pom_file=$words[$index+1]
  while [[ -f ${parent_pom_file:a:h:h}/pom.xml ]]; do parent_pom_file=${parent_pom_file:a:h:h}/pom.xml; done

  if [[ -f  $parent_pom_file ]]; then
    unset profiles
    cache_name="mvn/profiles${parent_pom_file:A}"
    if ( [[ ${+profiles} -eq 0 ]] || _cache_invalid "$cache_name" ) && ! _retrieve_cache "$cache_name"; then
      profiles=()
      setopt localoptions extendedglob
      for file in ${parent_pom_file:h}/**/pom.xml~*target\/*; do
        profiles_section="${(M)${(f)$(<$file)}:#*<profiles>*}"
        if [[ -n "$profiles_section" ]]; then
          for profile in ${(s:,,,:)${${${(S)${(S)${(S)${(S)${${profiles_section#*<profile>}%</profile>*}//<repositories>*<\/repositories>}//<pluginRepositories>*<\/pluginRepositories>}//<build>*<\/build>}//<\/id>*<id>/,,,}##*<id>}%%</id>*}}; do
            [[ -z ${(M)profiles:#"$profile"*} ]] && profiles+=("$profile"'['"in ${file#${parent_pom_file:h}\/}"']')
          done
        fi
      done
      [[ ${#profiles} -gt 0 ]] && _store_cache "$cache_name" profiles
    fi
    profs+=($profiles)
  fi

  [[ ${#profs} -gt 0 ]] && _values -s ',' 'profile' "$profs[@]" && ret=0

  return ret
}

_mvn_projects()
{
  # TODO projects can also be given in the form [groupId:]artifactId.
  local ret=1 projects

  index=$words[(i)-f]
  [[ $index -gt ${#words} ]] && index=$words[(i)--file]
  [[ $index -gt ${#words} ]] && pom_file=pom.xml || pom_file=$words[$index+1]
  while [[ -f ${pom_file:a:h:h}/pom.xml ]]; do pom_file=${pom_file:a:h:h}/pom.xml; done

  if [[ -f $pom_file ]]; then
    setopt localoptions extendedglob
    projects=(${pom_file:h}/*/**/pom.xml~*target\/*)
    projects=(${projects#.\/})
    [[ ${#projects} -gt 0 ]] && _values "$@" 'project' "${projects[@]:h}" && ret=0
  fi

  return ret
}

_mvn_properties()
{
  # TODO Complete some very common props like -DskipTests, etc.
  local ret=1
  _message -e property-names 'property name' && ret=0
  return ret
}

_mvn "$@"
