#compdef 7z 7za 7zr 7zz

# this covers both the official 7-zip and the now deprecated p7zip (but not the
# p7zip command, which is a gzip-like wrapper)
#
# notes:
# - seemingly all 7zip commands, options, and option arguments are
#   case-insensitive. we only partially support this
# - most options only work with certain commands. the html documentation lists
#   which ones, but it was so consistently wrong or contradictory that i gave up
#   on trying to separate them. do not blindly trust the documentation on this

local ret=1
local -a context line state state_descr args
local -A opt_args

args=(
  '*-ai-[specify archives to include]: :->file-refs'
  "(2)-an[don't take archive name on command line (with -ai)]"
  '-ao-[specify overwrite mode]:overwrite mode:((
    a\:"overwrite existing files without prompt"
    s\:"skip extracting files that already exist"
    t\:"auto-rename existing files"
    u\:"auto-rename extracting files"
  ))'
  '*-ax-[specify archives to exclude]: :->file-refs'
  '-bb-[specify output log level]::log level:((
    0\:"disable log"
    1\:"show names of processed files"
    2\:"show names of additional files processed internally"
    3\:"show information about additional operations"
  ))'
  '-bd[disable progress indicator]'
  '*-bs-[specify output stream]: :->output-streams'
  '-bt[show execution time statistics]'
  '*-i-[specify files to include]: :->file-refs'
  '*-m-[specify compression method parameter]:method parameter' # @todo complete these
  '-o-[specify output directory]:output directory:_files -/'
  '-p-[specify password]:password'
  "-r-[recurse into subdirectories (or specify)]::recursion method:((
    -\:\"don't recurse into subdirectories\"
    0\:'recurse into subdirectories only for wildcard names'
  ))"
  '-sa-[specify archive naming mode]:archive naming mode:((
    a\:"always add archive type extension"
    e\:"use file name exactly as given"
    s\:"add archive type extension only if file name has no extension"
  ))'
  '-scc-[specify charset for console input/output]: :->charsets-io'
  '-scrc-[specify hash function]: :->hash-functions'
  '-scs-[specify charset for list files]: :->charsets-listfile'
  '-sdel[delete files after adding to archive]'
  '-seml-[send archive by e-mail (specify behaviour)]::e-mail behaviour:((
    .\:"delete archive after attaching to e-mail"
  ))'
  '-sfx-[create SFX archive]:: :->sfx-modules'
  '-si-[compress data from stdin (specify file name)]::file name in archive:_files'
  '-slt[show technical information]'
  '-snc[extract as alternate stream if : in file name]'
  '-snh[store hard links as links (tar and WIM only)]'
  '-sni[store NTFS security information (WIM only)]'
  '-snl[store symbolic links as links (tar and WIM only)]'
  "-sns-[store NTFS alternate streams (or specify) (WIM only)]::alternate-streams behaviour:((
    -\:\"don't store NTFS alternate streams\"
  ))"
  "-snt-[replace trailing dots and spaces in file names (or specify)]::trailing-character behaviour:((
    -\:\"don't replace trailing dots and spaces in file names\"
  ))"
  '-so[write data to stdout]'
  '-spd[disable wildcard matching for file names]'
  '-spe[eliminate duplication of root folder]'
  '-spf-[use fully qualified (absolute) file paths (or specify)]::absolute-file-path behaviour::((
    2\:"use full path without leading slash or drive letter"
  ))'
  '-ssc-[enable case-sensitive mode (or specify)]::case-sensitive mode:((
    -\:"disable case-sensitive mode"
  ))'
  '-sse[stop archive creation if unable to open input file]'
  "-ssp[don't change access time of source files]"
  '-ssw[compress files open for writing by other programs]'
  '-stl[set archive time stamp from most recently modified file]'
  '-stm-[specify CPU thread affinity mask]:affinity mask (hexadecimal)'
  '-stx-[exclude specified archive type]: :->archive-types'
  '-t-[specify archive type]: :->archive-types'
  '-u-[specify update options]:update options' # @todo complete these
  '-v-[create volume of specified size]: :_numbers -u bytes "volume size" b k m g'
  '-w-[use temporary work directory (or specify)]::work directory:_files -/'
  '*-x-[specify files to exclude]: :->file-refs'
  '-y[assume yes to all queries]'
  '1:command:((
    a\:"add files to archive"
    b\:"benchmark"
    d\:"delete files from archive"
    e\:"extract files from archive (without full paths)"
    h\:"calculate hash values for files"
    i\:"show information about supported formats"
    l\:"list contents of archive"
    rn\:"rename files in archive"
    t\:"test integrity of archive"
    u\:"update files in archive"
    x\:"extract files (with full paths)"
  ))'
  '(-an)2:archive file:_files'
  '*: :_files' # @todo complete more specifically, handle list files
)

_arguments -S : $args && ret=0

case $state in
  archive-types)
    local -a formats=( "${(@f)"$( _call_program 7z-info $words[1] i )"}" )
    formats=( "${(@)formats[(r)*Formats:*,-1]}" )
    formats=( ${formats[2,(r)]} )

    # 'C' appears in fourth column if compression is supported
    [[ ${(L)words[(rn:2:)^-*]} == (a|d|rn|u) ]] &&
    formats=( ${(M)formats:#???C*} )

    # new (?) format
    if [[ -n ${(M)formats:#*....*} ]]; then
      formats=( ${formats/#?(#c28)/} )
    # old/p7zip format
    else
      formats=( ${formats/#?(#c17)/} )
    fi
    formats=( ${formats%%[[:space:]]*} )

    (( $#formats )) || formats=( 7z bzip2 gzip tar xz zip )
    formats=( ${formats//:/\\:} )

    # @todo also complete the various -t* and -t# modes
    _describe \
      -t formats \
      'archive type' \
      formats \
      -M 'm:{a-z}={A-Z}' \
    && ret=0
    ;;

  charsets-io)
    _describe \
      -t charsets \
      'character set' \
      '( UTF-8 WIN DOS )' \
      -M 'm:{a-z}={A-Z}' \
    && ret=0
    ;;

  charsets-listfile)
    _describe \
      -t charsets \
      'character set (or Windows code-page number)' \
      '( UTF-8 UTF-16LE UTF-16BE WIN DOS )' \
      -M 'm:{a-z}={A-Z}' \
    && ret=0
    ;;

  file-refs)
    # @todo complete this properly
    if compset -P 1 '*[@!]'; then
      _files && ret=0
    else
      _describe -t file-refs 'file-ref type' '(
        "@:list file"
        "!:file name or wildcard"
      )' && ret=0
    fi
    ;;

  hash-functions)
    local -a hashers=( "${(@f)"$( _call_program 7z-info $words[1] i )"}" )
    hashers=( "${(@)hashers[(r)*Hashers:*,-1]}" )
    hashers=( ${hashers[2,(r)]} )
    hashers=( ${hashers##*[[:space:]]} )
    (( $#hashers )) || hashers=( CRC32 CRC64 SHA1 SHA256 BLAKE2sp )
    hashers+=( \* )
    hashers=( ${hashers//:/\\:} )
    _describe -t hashers 'hash function' hashers -M 'm:{a-z}={A-Z}' && ret=0
    ;;

  output-streams)
    if compset -P '?'; then
      _describe -t stream-dests 'output stream destination' '(
        0:disabled
        1:stdout
        2:stderr
      )' && ret=0
    else
      _describe -t stream-types 'output stream type' '(
        "e:error messages"
        "o:standard output messages"
        "p:progress information"
      )' && ret=0
    fi
    ;;

  sfx-modules)
    # sfx modules should be in the same directory as the 7zip executable. on
    # some systems they live in some lib directory so we look there too
    local -aU sfxes=(
      ${${(Q)words[1]}:c:h}/*.sfx(#q.N:t)
      ${${(Q)words[1]}:c:P:h}/*.sfx(#q.N:t)
      {,/opt,/usr}{,/local}/lib/7zip/*.sfx(#q.N:t)
    )
    _describe -t sfx-modules 'SFX modules' sfxes && ret=0
    ;;
esac

return ret
