#compdef fail2ban-client
# ------------------------------------------------------------------------------
# Copyright (c) 2020 Github zsh-users - http://github.com/zsh-users
# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# ------------------------------------------------------------------------------
# Description
# -----------
#
#  Completion script for fail2ban-client (https://www.fail2ban.org/).
#
# ------------------------------------------------------------------------------
# Authors
# -------
#
#  * Felix Neumärker <xdch47@posteo.de>
#
# ------------------------------------------------------------------------------

_f2bc_jails() {
  LANG=C fail2ban-client status  2> /dev/null | sed -n -e 's/.*Jail list:\s\+//' -e 'T' -e 's/,\s\+/\'$'\n/g' -e 'p'
}

_complete_f2bc_cmds() {
  local cmds=(
    'unban:unbans all IP addresses'
    'set:set property'
    'get:get property'
    'status:gets the current status of the server'
    'reload:reloads the configuration/jails'
    'restart:restarts the server'
    'start:starts the server and the jails'
    'stop:stops all jails and terminate the server'
    'ping:tests if the server is alive'
    'flushlogs:flushes the logtarget if a file and reopens it'
    'help:return this output'
    'version:return the server version'
  )

  _describe -V "fail2ban commands" cmds
}

_complete_f2bc_cmdargs() {
  local f2barg="$words[$NORMARG]"
  case "$f2barg" in
    unban)
      local jail
      if (( $words[(I)(--all)] == 0 )) ; then
        for jail in $(_f2bc_jails) ; do
          _complete_f2bc_ips $jail
        done
        local unban_opts=(--all)
        _describe -o "unban options" unban_opts
      else
        _nothing
      fi
      ;;
    (set|get))
      if (( $NORMARG + 1 == $CURRENT )) ; then
        _complete_f2bc_jails
        _complete_f2bc_settings
      else
        _complete_f2bc_jail${f2barg}
      fi
      ;;
    status)
      if (( $NORMARG + 1 == $CURRENT )) ; then
        _complete_f2bc_jails
      elif (( $NORMARG + 2 == $CURRENT )) ; then
        _values "flavor" basic cymru
      else
        _nothing
      fi
      ;;
  esac
}

_complete_f2bc_jails() {
  local jails=($(_f2bc_jails))
  _describe -V "jails" jails
}

_complete_f2bc_ips() {
  local ips=("${(@f)$(LANG=C fail2ban-client status $1 2> /dev/null | sed -n -e 's/^.*Banned IP list:\s\+//' -e 'T' -e 's/\s\+/\'$'\n/g' -e 'p')}")
  if [[ -n "${ips[@]}" ]] ; then
    _describe -t "f2b_jail_$1" -V "banned ips of jail $1" ips
  else
    _nothing
  fi
}

_complete_f2bc_jailset() {
  if (( $NORMARG + 2 == $CURRENT )) ; then
    case $words[$NORMARG+1] in
      loglevel)
        local loglevel=(CRITICAL ERROR WARNING NOTICE INFO DEBUG TRACEDEBUG HEAVYDEBUG)
        _describe -V "loglevel" loglevel ;;
      logtarget)
        local logtarget=(STDOUT STDERR SYSLOG)
        _describe -V "logtarget" logtarget
        _files ;;
      syslogsocket)
        local syslogsocket=(auto)
        _describe -V "logtarget" syslogsocket
        _files ;;
      dbfile)
        _files ;;
      dbpurgeage)
        _message "sets the max age in <SECONDS> that history of bans will be kept" ;;
      *)
        # jail
        local jailsettings=(
          unbanip
          banip
          action
          addaction
          addfailregex
          addignoreip
          addignoreregex
          addjournalmatch
          addlogpath
          bantime
          datepattern
          delaction
          delfailregex
          delignoreip
          delignorerexgex
          deljournalmatch
          dellogpath
          findtime
          idle
          ignorecache
          ignorecommand
          ignoreself
          logencoding
          maxlines
          maxretry
          usedns
        )
        _describe -t "f2b_jail_setting" -V "jail setting" jailsettings ;;
    esac
  else
    local jail="$words[$NORMARG+1]"

    if (( $NORMARG + 3 == $CURRENT )) ; then
      case $words[$NORMARG+2] in
        unbanip)
          _complete_f2bc_ips "$jail" ;;
        delfailregex)
          _complete_f2bc_regex fail "$jail" ;;
        delignorerexgex)
          _complete_f2bc_regex ignore "$jail" ;;
        dellogpath)
          local filelist=("${(@f)$(LANG=C fail2ban-client status $jail 2> /dev/null | sed -n -e 's/^.*File list:\s\+//' -e 'T' -e 's/\s\+/\'$'\n/g' -e 'p')}")

          if [[ -n "${filelist[@]}" ]] ; then
            _describe -t "f2b_filelist" -V "filelist of jail $1" filelist
          else
            _nothing
          fi ;;
        idle)
          _values 'fail2ban idle' on off ;;
        ignoreself)
          _values 'fail2ban ignoreself' true false ;;
        delignoreip)
          local ignoreips=("${(@f)$(fail2ban-client get "$jail" ignoreip 2> /dev/null | sed -e 's/^[|`]-\s\+//p')}")
          if [[ -n "${ignoreips[@]}" ]] ; then
            _describe -t "f2b_ignoreip" -V "fail2ban ignored ips" ignoreips
          else
            _nothing
            fi ;;
          delaction|action)
            _complete_f2bc_action "$jail" ;;
          addlogpath)
            _files ;;
          *)
            _message "No completion for ${words[NORMARG+2]}" ;;
        esac
      elif (( $NORMARG + 4 == $CURRENT )) ; then
        case $words[$NORMARG+2] in
          action)
            _complete_f2bc_actionproperties "$jail" $words[$NORMARG+3] ;;
          addaction)
            _files ;;
          *)
            _nothing ;;
        esac
      else
        _nothing
    fi
  fi
}

_complete_f2bc_jailget() {
  if (( $NORMARG + 2 == $CURRENT )) ; then
    case $words[$NORMARG+1] in
      (loglevel|logtarget|syslogsocket|dbfile|dbpurgeage))
        _nothing ;;
      *)
        # jail
        local jailprops=(
          logpath
          logencoding
          journalmatch
          ignoreself
          ignoreip
          ignorecommand
          failregex
          ignoreregex
          findtime
          bantime
          datepattern
          usedns
          maxretry
          maxlines
          actions
          action
          actionproperties
          actionmethods
        )
        _describe -t "f2b_jail_props" -V "jail properties" jailprops ;;
    esac
  else
    local jail="$words[$NORMARG+1]"

    if (( $NORMARG + 3 == $CURRENT )) ; then
      case $words[$NORMARG+2] in
        (action|actionproperties|actionmethods))
          _complete_f2bc_action "$jail" ;;
        *)
          _nothing ;;
      esac
    elif (( $NORMARG + 4 == $CURRENT )) ; then
      case $words[$NORMARG+2] in
        (action|actionproperties|actionmethods))
          _complete_f2bc_actionproperties "$jail" $words[$NORMARG+3] ;;
        *)
          _nothing ;;
      esac
    else
      _nothing
    fi
  fi
}

_complete_f2bc_action() {
  local jailactions=("${(@f)$(fail2ban-client get $1 actions 2>/dev/null | sed -e '1d' -e 's/,\s\+/\'$'\n/g')}")

  if [[ -n "${jailactions[@]}" ]] ; then
    _describe -t "f2b_jail_actions" -V "jail actions" jailactions
  else
    _nothing
  fi
}

_complete_f2bc_actionproperties() {
    local default_actionproperties=(
    actionstart
    actionstop
    actioncheck
    actionban
    actionunban
    timeout
  )
  local all_actionproperties=("${(@f)$(fail2ban-client get $1 actionproperties $2 2>/dev/null | sed -e '1d' -e 's/,\s\+/\'$'\n/g')}")
  local add_actionproperties=("${(@)all_actionproperties:|default_actionproperties}")

  _describe -t "f2b_actions_defprops" -V "default action properties" default_actionproperties

  if [[ -n "${add_actionproperties[@]}" ]] ; then
    _describe -t "f2b_actions_remprops" -V "additional action properties" add_actionproperties
  else
    _nothing
  fi
}

_complete_f2bc_regex() {
  local regex=("${(@f)$(fail2ban-client get $2 ${1}regex 2> /dev/null | sed -n -e 's/[|`]- \[\([0-9]\+\)\]:\s\+/\1:/p')}")
  if [[ -n "${regex[@]}" ]] ; then
    _describe -t "f2b_regex" -V "jail $2 ${1}regex" regex
  else
    _nothing
  fi
}

_complete_f2bc_settings() {
  local setargs=(loglevel logtarget syslogsocket dbfile dbpurgeage)
  _describe -t "f2b_settings" -V "fail2ban-client settings" setargs
}

integer NORMARG

_arguments -A "-*" -n \
  '-c[configuration directory]:_files -/' \
  '-s[socket path]:_files' \
  '-p[pidfile path]:_files' \
  '--loglevel[logging level]:(CRITICAL ERROR WARNING, NOTICE INFO, DEBUG, TRACEDEBUG HEAVYDEBUG)' \
  '--logtarget[logging target]:(stdout stderr syslog sysout)' \
  '--syslogsocket:_files' \
  '-d[dump configuration]' \
  '(--dp --dump-pretty)'{--dp,--dump-pretty}'[dump the configuration using more human readable representation]' \
  '(-t --test)'{-t,--test}'[test configuration]' \
  '-i[interactive mode]' \
  '-v[increase verbosity]' \
  '-q[decrease verbosity]' \
  '-x[force execution of the server (remove socket file)]' \
  '-b[start server in background]' \
  '-f[start server in foreground]' \
  '--str2sec[convert time abbreviation format to seconds]:_message str2sec' \
  '(-h --help)'{-h,--help}'[display this help message]' \
  '(-V --version)'{-V,--version}'[print the version]' \
  '1:fail2ban command:_complete_f2bc_cmds' \
  '*:fail2ban command argument:_complete_f2bc_cmdargs'

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