#compdef cf
# ------------------------------------------------------------------------------
#
# Copyright 2015 Ferran Rodenas & Danny Rosen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------
#
# Description
# -----------
#
#  Completion script for Cloud Foundry CLI (https://github.com/cloudfoundry/cli#downloads)
#
# ------------------------------------------------------------------------------
#
# Authors
# -------
#
#  * Ferran Rodenas (https://github.com/frodenas)
#  * Danny Rosen (https://github.com/dannyzen)
#
# ------------------------------------------------------------------------------

_cf() {
  typeset -A opt_args
  local context state line
  local curcontext="$curcontext"
  local ret=1

  _arguments -C \
    '1: :__cf_subcommands' \
    '*:: :->command' \
    && ret=0

  case "$state" in
    (command)
      local cmd="$words[1]"
      if (( $+functions[__cf_${cmd}] )); then
        __cf_$cmd
      fi
      ;;
  esac

  return ret
}

__cf_subcommands() {
  local -a commands=(
    "login:Log user in"
    "logout:Log user out"
    "passwd:Change user password"
    "target:Set or view the targeted org or space"
    "api:Set or view target api url"
    "auth:Authenticate user non-interactively"
    "apps:List all apps in the target space"
    "app:Display health and status for app"
    "push:Push a new app or sync changes to an existing app"
    "scale:Change or view the instance count, disk space limit, and memory limit for an app"
    "delete:Delete an app"
    "rename:Rename an app"
    "start:Start an app"
    "stop:Stop an app"
    "restart:Restart an app"
    "restage:Restage an app"
    "restart-app-instance:Terminate the running application Instance at the given index and instantiate a new instance of the application with the same index"
    "events:Show recent app events"
    "files:Print out a list of files in a directory or the contents of a specific file"
    "logs:Tail or show recent logs for an app"
    "env:Show all env variables for an app"
    "set-env:Set an env variable for an app"
    "unset-env:Remove an env variable"
    "stacks:List all stacks"
    "stack:Show information for a stack"
    "copy-source:Make a copy of app source code from one application to another.  Unless overridden, the copy-source command will restart the application"
    "create-app-manifest:Create an app manifest for an app that has been pushed successfully"
    "marketplace:List available offerings in the marketplace"
    "services:List all service instances in the target space"
    "service:Show service instance info"
    "create-service:Create a service instance"
    "update-service:Update a service instance"
    "delete-service:Delete a service instance"
    "rename-service:Rename a service instance"
    "create-service-key:Create key for a service instance"
    "service-keys:List keys for a service instance"
    "service-key:Show service key info"
    "delete-service-key:Delete a service key"
    "bind-service:Bind a service instance to an app"
    "unbind-service:Unbind a service instance from an app"
    "create-user-provided-service:Make a user-provided service instance available to cf apps"
    "update-user-provided-service:Update user-provided service instance name value pairs"
    "orgs:List all orgs"
    "org:Show org info"
    "create-org:Create an org"
    "delete-org:Delete an org"
    "rename-org:Rename an org"
    "spaces:List all spaces in an org"
    "space:Show space info"
    "create-space:Create a space"
    "delete-space:Delete a space"
    "rename-space:Rename a space"
    "domains:List domains in the target org"
    "create-domain:Create a domain in an org for later use"
    "delete-domain:Delete a domain"
    "create-shared-domain:Create a domain that can be used by all orgs (admin-only)"
    "delete-shared-domain:Delete a shared domain"
    "routes:List all routes in the current space or the current organization"
    "create-route:Create a url route in a space for later use"
    "check-route:Perform a simple check to determine whether a route currently exists or not"
    "map-route:Add a url route to an app"
    "unmap-route:Remove a url route from an app"
    "delete-route:Delete a route"
    "delete-orphaned-routes:Delete all orphaned routes (e.g.: those that are not mapped to an app)"
    "buildpacks:List all buildpacks"
    "create-buildpack:Create a buildpack"
    "update-buildpack:Update a buildpack"
    "rename-buildpack:Rename a buildpack"
    "delete-buildpack:Delete a buildpack"
    "running-environment-variable-group:Retrieve the contents of the running environment variable group"
    "staging-environment-variable-group:Retrieve the contents of the staging environment variable group"
    "set-staging-environment-variable-group:Pass parameters as JSON to create a staging environment variable group"
    "set-running-environment-variable-group:Pass parameters as JSON to create a running environment variable group"
    "feature-flags:Retrieve list of feature flags with status of each flag-able feature"
    "feature-flag:Retrieve an individual feature flag with status"
    "enable-feature-flag:Enable the use of a feature so that users have access to and can use the feature"
    "disable-feature-flag:Disable the use of a feature so that users have access to and can use the feature"
    "curl:Executes a raw request, content-type set to application/json by default"
    "config:write default values to the config"
    "oauth-token:Retrieve and display the OAuth token for the current session"
    "add-plugin-repo:Add a new plugin repository"
    "remove-plugin-repo:Remove a plugin repository"
    "list-plugin-repos:list all the added plugin repository"
    "repo-plugins:List all available plugins in all added repositories"
    "plugins:list all available plugin commands"
    "install-plugin:Install the plugin defined in command argument"
    "uninstall-plugin:Uninstall the plugin defined in command argument"
    "targets:List all saved targets (requires cf-targets plugin)"
    "save-target:Save the current target under a given name (requires cf-targets plugin)"
    "set-target:Restore a previously saved target (requires cf-targets plugin)"
    "delete-target:Delete a saved target (requires cf-targets plugin)"
  )

  _describe -t commands "cf command" commands
}

# ----------------------
# ----- Helper functions
# ----------------------

# Output a selectable list of organizations
__cf_orgs() {
  local -a orgs=($(CF_COLOR=false CF_TRACE=false cf orgs | awk 'NR>3{print $1}'))
  if (( $#orgs )); then
    _describe "org" orgs
  fi
}

# Output a selectable list of spaces
__cf_spaces() {
  local -a spaces=($(CF_COLOR=false CF_TRACE=false cf spaces | awk 'NR>3{print $1}'))
  if (( $#spaces )); then
    _describe 'SPACE' spaces
  fi
}

# Output a selectable list of applications
__cf_apps() {
  local -a apps=($(CF_COLOR=false CF_TRACE=false cf apps | awk 'NR>4{print $1}'))
  if (( $#apps )); then
    _describe 'APP' apps
  fi
}

# Output a selectable list of stacks
__cf_stacks() {
  local -a stacks=($(CF_COLOR=false CF_TRACE=false cf stacks | awk 'NR>4{print $1}'))
  if (( $#stacks )); then
    _describe 'STACK' stacks
  fi
}

# Output a selectable list of services
__cf_marketplace_services() {
  local -a market_places=($(CF_COLOR=false CF_TRACE=false cf marketplace | awk 'NR>4{print $1}'))
  if (( $#market_places )); then
    _describe 'SERVICE' market_places
  fi
}

# Output a selectable list of services
__cf_services() {
  local -a services=($(CF_COLOR=false CF_TRACE=false cf services | awk 'NR>4{print $1}'))
  if (( $#services )); then
    _describe 'SERVICE' services
  fi
}

# Output a selectable list of domains
__cf_domains() {
  local -a domains=($(CF_COLOR=false CF_TRACE=false cf domains | grep -v shared | awk 'NR>2{print $1}'))
  if (( $#domains )); then
    _describe 'DOMAIN' domains
  fi
}

# Output a selectable list of shared domains
__cf_shared_domains() {
  local shared_domains=($(CF_COLOR=false CF_TRACE=false cf domains | grep -v owned | awk 'NR>2{print $1}'))
  if (( $#shared_domains )); then
    _describe 'SHARED-DOMAIN' shared_domains
  fi
}

# Output a selectable list of hostnames
__cf_hostnames() {
  local -a hostnames=($(CF_COLOR=false CF_TRACE=false cf routes | awk 'NR>3{print $2}'))
  if (( $#hostnames )); then
    _describe 'ROUTE' hostnames
  fi
}

# Output a selectable list of buildpacks
__cf_buildpacks() {
  local -a build_packs=($(CF_COLOR=false CF_TRACE=false cf buildpacks | awk 'NR>3{print $1}'))
  if (( $#build_packs )); then
    _describe 'BUILDPACK' build_packs
  fi
}

# Output a selectable list of feature flags
__cf_feature_flags() {
  local -a flags=($(CF_COLOR=false CF_TRACE=false cf feature-flags | awk 'NR>4{print $1}'))
  if (( $#flags )); then
    _describe 'FEATURE-FLAG' flags
  fi
}

# Output a selectable list of plugin repos
__cf_repo_plugins() {
  local -a repo_plugins=($(CF_COLOR=false CF_TRACE=false cf list-plugin-repos | awk 'NR>3{print $1}'))
  if (( $#repo_plugins )); then
    _describe 'REPO-PLUGIN' repo_plugins
  fi
}

# Output a selectable list of plugins
__cf_plugins() {
  local -a plugins=($(cf plugins | awk 'NR>4{print $1}'))
  if (( $#plugins )); then
    _describe 'PLUGIN' plugins
  fi
}

# Output a selectable list of targets (requires cf-targets plugin)
__cf_targets() {
  local -a targets=($(cf targets | awk '{print $1}'))
  if (( $#targets )); then
    _describe 'TARGET' targets
  fi
}

# --------------------------
# ----- end Helper functions
# --------------------------

# --------------
# ----- Commands
# --------------

__cf_login() {
  _arguments \
    '-a=[API endpoint (e.g. https://api.example.com)]:api endpoint:' \
    '-u=[Username]:username:' \
    '-p=[Password]:password:' \
    '-o=[Organization]:organization name:__cf_orgs' \
    '-s=[Space]:space name:__cf_spaces' \
    '--sso[Use a one-time password to login]' \
    '--skip-ssl-validation[Skip SSL validation]'
}

__cf_logout() {
    # no arguments
}

__cf_passwd() {
  _arguments \
    '1:password:'
}

__cf_target() {
  _arguments \
    '-o=[Organization]:organization name:__cf_orgs' \
    '-s=[Space]:space name:__cf_spaces'
}

__cf_api() {
  _arguments \
    '1:API url:' \
    '--unset[Remove all api endpoint targeting]' \
    '--skip-ssl-validation[Skip SSL validation]'
}

__cf_app() {
  _arguments \
    '1:application name:__cf_apps' \
    '--guid[Retrieve and display the given app guid. All other health and status output for the app is suppressed]'
}

__cf_push() {
  _arguments \
    '1:application name:__cf_apps' \
    '-b=[Custom buildpack by name (e.g. my-buildpack) or GIT URL or GIT BRANCH URL]:buildpack name:__cf_buildpacks' \
    '-c=[Startup command, set to null to reset to default start command]:startup command:' \
    '-d=[Domain (e.g. example.com)]:domain (e.g. example.com):__cf_domains' \
    '-f=[Path to manifest]:file:_files:' \
    '-i=[Number of instances]:number of instances:' \
    '-k=[Disk limit (e.g. 256M, 1024M, 1G)]:disk limit (e.g. 256M, 1024M, 1G):' \
    '-m=[Memory limit (e.g. 256M, 1024M, 1G)]:memory limit (e.g. 256M, 1024M, 1G):' \
    '-n=[Hostname (e.g. my-subdomain)]:hostname (e.g. my-subdomain):' \
    '-p=[Path to app directory or to a zip file of the contents of the app directory]:file:_files' \
    '-s=[Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)]:stack name:__cf_stacks:' \
    '-t=[Maximum time (in seconds) for CLI to wait for application start, other server side timeouts may apply]:maximum time (in seconds):' \
    '--no-hostname[Map the root domain to this app]' \
    '--no-manifest[Ignore manifest file]' \
    '--no-route[Do not map a route to this app and remove routes from previous pushes of this app]' \
    '--no-start[Do not start an app after pushing]' \
    '--random-route[Create a random route for this app]'
}

__cf_scale() {
  _arguments \
    '1:application name:__cf_apps' \
    '-i=[Number of instances]:number of instances:' \
    '-k=[Disk limit (e.g. 256M, 1024M, 1G)]:disk limit (e.g. 256M, 1024M, 1G):' \
    '-m=[Memory limit (e.g. 256M, 1024M, 1G)]:memory limit (e.g. 256M, 1024M, 1G):' \
    '-f[Force restart of app without prompt]'
}

__cf_delete() {
  _arguments \
    '1:application name:__cf_apps' \
    '--f[Force deletion without confirmation]' \
    '--r[Also delete any mapped routes]'
}

__cf_rename() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:application name:'
}

__cf_start() {
  _arguments \
    '1:application name:__cf_apps'
}

__cf_stop() {
  _arguments \
    '1:application name:__cf_apps'
}

__cf_restart() {
  _arguments \
    '1:application name:__cf_apps'
}

__cf_restage() {
  _arguments \
    '1:application name:__cf_apps'
}

__cf_restart-app-instance() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:application index:'
}

__cf_events() {
  _arguments \
    '1:application name:__cf_apps'
}

__cf_files() {
  _arguments \
    '1:application name:__cf_apps' \
    '2::path:' \
    '-i=[instance]'
}

__cf_logs() {
  _arguments \
    '1:application name:__cf_apps' \
    '--recent[Dump recent logs instead of tailing]'
}

__cf_env() {
  _arguments \
    '1:application name:__cf_apps'
}

__cf_set-env() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:env var name:' \
    '3:env var value:'
}

__cf_unset-env() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:env var name:'
}

__cf_stack() {
  _arguments \
    '1:stack name:__cf_stacks' \
    '--guid[Retrieve and display the given stack guid. All other output for the stack is suppressed]'
}

__cf_copy-source() {
  _arguments \
    '1:source application name:__cf_apps' \
    '2:target application name:' \
    '-o=[Org that contains the target application]:organization name:__cf_orgs' \
    '-s=[Space that contains the target application]:space name:__cf_spaces' \
    '--no-restart[Override restart of the application in target environment after copy-source completes]'
}

__cf_create-app-manifest() {
  _arguments \
    '1:application name:__cf_apps' \
    '-p=[Specify a path for file creation. If path not specified, manifest file is created in current working directory]:path:_files'
}

__cf_marketplace() {
  _arguments \
    '-s=[Show plan details for a particular service offering]'
}

__cf_service() {
  _arguments \
    '1:service name:__cf_services' \
    '--guid[Retrieve and display the given service guid. All other output for the service is suppressed]'
}

__cf_create-service() {
  _arguments \
    '1:service:__cf_marketplace_services' \
    '2:plan:' \
    '3:service name:' \
    '-c=[Valid JSON object containing service-specific configuration parameters, provided either in-line or in a file]' \
    '-t=[User provided tags]'
}

__cf_update-service() {
  _arguments \
    '1:service name:__cf_services' \
    '-p=[Change service plan for a service instance]' \
    '-c=[Valid JSON object containing service-specific configuration parameters, provided either in-line or in a file]' \
    '-t=[User provided tags]'
}

__cf_rename-service() {
  _arguments \
    '1:service name:__cf_services' \
    '2:service name:'
}

__cf_delete-service() {
  _arguments \
    '1:service name:__cf_services' \
    '-f[Force deletion without confirmation]'
}

__cf_create-service-key() {
  _arguments \
    '1:service name:__cf_services' \
    '2:service key:' \
    '-c=[Valid JSON object containing service-specific configuration parameters, provided either in-line or in a file]'
}

__cf_service-keys() {
  _arguments \
    '1:service name:__cf_services'
}

__cf_service-key() {
  _arguments \
    '1:service name:__cf_services' \
    '2:service key:'
}

__cf_delete-service-key() {
  _arguments \
    '1:service name:__cf_services' \
    '2:service key:' \
    '-f[Force deletion without confirmation]'
}

__cf_bind-service() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:service name:__cf_services' \
    '-c=[Valid JSON object containing service-specific configuration parameters, provided either in-line or in a file]'
}

__cf_unbind-service() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:service name:__cf_services'
}

__cf_create-user-provided-service() {
  _arguments \
    '1:service name:' \
    '-p=[Credentials]' \
    '-l=[Syslog Drain Url]'
}

__cf_update-user-provided-service() {
  _arguments \
    '1:service name:__cf_services' \
    '-p=[Credentials]' \
    '-l=[Syslog Drain Url]'
}

__cf_org() {
  _arguments \
    '1:organization name:__cf_orgs' \
    '--guid[Retrieve and display the given org guid. All other output for the org is suppressed]'
}

__cf_create-org() {
  _arguments \
    '1:organization name:' \
    '-q=[Quota to assign to the newly created org (excluding this option results in assignment of default quota)]'
}

__cf_delete-org() {
  _arguments \
    '1:organization name:__cf_orgs' \
    '-f[Force deletion without confirmation]'
}

__cf_space() {
  _arguments \
    '1:space name:__cf_spaces' \
    '--guid[Retrieve and display the given space guid. All other output for the space is suppressed]' \
    '--security-group-rules[Retrieve the rules for all the security groups associated with the space]'
}

__cf_create-space() {
  _arguments \
    '1:space name:' \
    '-o=[Org that contains the target application]:organization name:__cf_orgs' \
    '-q=[Quota to assign to the newly created space (excluding this option results in assignment of default quota)]'
}

__cf_delete-space() {
  _arguments \
    '1:space name:__cf_spaces' \
    '-f[Force deletion without confirmation]'
}

__cf_create-domain() {
  _arguments \
    '1:organization name:__cf_orgs' \
    '2:domain:'
}

__cf_delete-domain() {
  _arguments \
    '1:domain:__cf_domains' \
    '-f[Force deletion without confirmation]'
}

__cf_create-shared-domain() {
  _arguments \
    '1:domain:'
}

__cf_delete-shared-domain() {
  _arguments \
    '1:domain:__cf_shared_domains' \
    '-f[Force deletion without confirmation]'
}

__cf_routes() {
  _arguments \
    '--orglevel[List all the routes for all spaces of current organization]'
}

__cf_create-route() {
  _arguments \
    '1:space name:__cf_spaces' \
    '2:domain:__cf_domains' \
    '-n=[Hostname]'
}

__cf_check-route() {
  _arguments \
    '1:hostname:__cf_hostnames' \
    '2:domain:__cf_domains'
}

__cf_map-route() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:domain:__cf_domains' \
    '-n=[Hostname]:hostname:__cf_hostnames:'
}

__cf_unmap-route() {
  _arguments \
    '1:application name:__cf_apps' \
    '2:domain:__cf_domains' \
    '-n=[Hostname]:hostname:__cf_hostnames:'
}

__cf_delete-route() {
  _arguments \
    '1:domain:__cf_domains' \
    '-n=[Hostname]:hostname:__cf_hostnames:' \
    '-f[Force deletion without confirmation]'
}

__cf_delete-orphaned-routes() {
  _arguments \
    '-f[Force deletion without confirmation]'
}

__cf_create-buildpack() {
  _arguments \
    '1:buildpack name:' \
    '2:path:_files' \
    '3:position:' \
    '--enable[Enable the buildpack to be used for staging]' \
    '--disable[Disable the buildpack from being used for staging]'
}

__cf_update-buildpack() {
  _arguments \
    '1:buildpack name:__cf_buildpacks' \
    '-p=[Path to directory or zip file]:file:_files' \
    '-i=[The order in which the buildpacks are checked during buildpack auto-detection]' \
    '--enable[Enable the buildpack to be used for staging]' \
    '--disable[Disable the buildpack from being used for staging]' \
    '--lock[Lock the buildpack to prevent updates]' \
    '--unlock[Unlock the buildpack to enable updates]'
}

__cf_rename-buildpack() {
  _arguments \
    '1:buildpack name:__cf_buildpacks' \
    '2:new buildpack name:'
}

__cf_delete-buildpack() {
  _arguments \
    '1:buildpack name:__cf_buildpacks' \
    '-f[Force deletion without confirmation]'
}

__cf_feature-flag() {
  _arguments \
    '1:feature name:__cf_feature_flags'
}

__cf_enable-feature-flag() {
  _arguments \
    '1:feature name:__cf_feature_flags'
}

__cf_disable-feature-flag() {
  _arguments \
    '1:feature name:__cf_feature_flags'
}

__cf_curl() {
  _arguments \
    '1:path:' \
    '-i[Include response headers in the output]' \
    '-v[Enable CF_TRACE output for all requests and responses]' \
    '-X=[HTTP method]:http method:(GET POST PUT DELETE)' \
    '-h=[Custom headers to include in the request, flag can be specified multiple times]' \
    '-d=[HTTP data to include in the request body]' \
    '--output[Write curl body to FILE instead of stdout]'
}

__cf_config() {
  _arguments \
    '--async-timeout=[Timeout for async HTTP requests]' \
    '--trace=[Trace HTTP requests]:trace:(true false)' \
    '--color=[Enable or disable color]:color:(true false)' \
    '--locale=[Set default locale. If LOCALE is CLEAR, previous locale is deleted]'
}

__cf_add-plugin-repo() {
  _arguments \
    '1:repo name:' \
    '2:url:'
}

__cf_remove-plugin-repo() {
  _arguments \
    '1:repo name:__cf_repo_plugins' \
    '2:url:'
}

__cf_repo-plugins() {
  _arguments \
    '-r=[Repo Name]:repo name:__cf_repo_plugins'
}

__cf_plugins() {
  _arguments \
    '-checksum[Compute and show the sha1 value of the plugin binary file]'
}

__cf_install-plugin() {
  _arguments \
    '1:plugin URL or path:_files' \
    '-r=[repo name where the plugin binary is located]:repo name:__cf_repo_plugins'
}

__cf_uninstall-plugin() {
  _arguments \
    '1:plugin name:__cf_plugins'
}

__cf_save-target() {
  _arguments \
    '1:target-name:' \
    '-f[Force save even if current target is already saved under another name]'
}

__cf_set-target() {
  _arguments \
    '1:target-name:__cf_targets' \
    '-f[Force target change even if current target is unsaved]'
}

__cf_delete-target() {
  _arguments \
    '1:target-name:__cf_targets'
}

# ------------------
# ----- end Commands
# ------------------

_cf "$@"

# Local Variables:
# mode: Shell-Script
# sh-indentation: 2
# indent-tabs-mode: nil
# sh-basic-offset: 2
# End:
# vim: ft=zsh sw=2 ts=2 et
