# ----------------------------------------------------------------------------
# Copyright (c) 2016-2023, QIIME 2 development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------


# NOTE: This module is closely coupled with `q2cli.cache`. It is a separate
# module to avoid cluttering `q2cli.cache` with completion script code, and
# provides a place to support other shell completion scripts in the future
# (e.g. zsh).

def write_bash_completion_script(plugins, path):
    """

    Parameters
    ----------
    plugins : dict
        Decoded JSON object representing CLI state on a per-plugin basis (e.g.
        as returned by `DeploymentCache.plugins`). See note within this
        function for why this parameter is necessary.
    path : str
        Path to write completion script to.

    """
    import os
    import os.path
    import stat
    import textwrap
    from q2cli.__main__ import qiime as root

    # `write_bash_completion_script` is called by `q2cli.cache.DeploymentCache`
    # when it is refreshing its cache. `q2cli.commands.RootCommand` could have
    # already asked for the cache, for example, if the user ran a command and
    # the cache must be refreshed. The bash completion script is generated by
    # traversing the `RootCommand` tree, so there is a cycle when `RootCommand`
    # attempts to access the cache in order to build itself. We work around
    # this by bootstrapping the `RootCommand`'s `._plugins` attribute with the
    # plugin state that has already been loaded by `DeploymentCache`.
    root._plugins = plugins

    cmd_reply = _generate_command_reply(root)
    cmd_reply = textwrap.indent(cmd_reply, '  ')
    completion_script = COMPLETION_SCRIPT_TEMPLATE.format(cmd_reply=cmd_reply)

    with open(path, 'w') as fh:
        fh.write(completion_script)

    # Make bash completion script executable:
    #   http://stackoverflow.com/a/12792002/3776794
    st = os.stat(path)
    # Set executable bit for user,group,other for root/sudo installs
    os.chmod(path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)


def _generate_command_reply(cmd):
    """Recursively generate completion reply for this command and subcommands.

    Parameters
    ----------
    cmd : click.Command
        Command to generate completion replies for (including its subcommands).

    """
    import textwrap
    import click

    ctx = None

    options = ['--help']
    for param in cmd.params:
        if isinstance(param, click.Option):
            options.extend(param.opts)
            options.extend(param.secondary_opts)
            if hasattr(param, 'q2_extra_opts'):
                options.extend(param.q2_extra_opts)

    subcmd_names = []
    if isinstance(cmd, click.MultiCommand):
        subcmd_names.extend(cmd.list_commands(ctx))

    subcmd_cases = []
    for subcmd_name in subcmd_names:
        subcmd_reply = _generate_command_reply(
            cmd.get_command(ctx, subcmd_name))
        subcmd_reply = textwrap.indent(subcmd_reply, '  ')

        case = SUBCOMMAND_CASE_TEMPLATE.format(
            subcmd_name=subcmd_name, subcmd_reply=subcmd_reply)
        subcmd_cases.append(case)

    subcmd_cases = textwrap.indent('\n'.join(subcmd_cases), ' ' * 6)

    cmd_reply = COMMAND_REPLY_TEMPLATE.format(
        options=' '.join(options), subcmd_names=' '.join(subcmd_names),
        subcmd_cases=subcmd_cases)

    return cmd_reply


# NOTE: using double braces to avoid `str.format` interpolation when bash needs
# curly braces in the generated code.
#
# NOTE: the handling of a negative COMP_CWORD is necessary in certain versions
# of bash (e.g. at least the bash shipped with OS X 10.9.5). When adding
# whitespace to the end of a command, and then moving the cursor backwards in
# the command and hitting <tab>, COMP_CWORD can be negative (I've only seen -2
# as its value). This is a bash bug and is not documented behavior. Other CLIs
# with tab completion suffer from the same issue, and each one deals with this
# bug differently (some not at all, e.g. `git`). The workaround used below
# seems to provide the least destructive completion behavior for our CLI.
#
# Bug report reference:
#   https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00108.html

COMPLETION_SCRIPT_TEMPLATE = """\
#!/usr/bin/env bash

_qiime_completion()
{{
  local COMP_WORDS=(${{COMP_WORDS[*]}})
  local incomplete
  if [[ ${{COMP_CWORD}} -lt 0 ]] ; then
    COMP_CWORD="${{#COMP_WORDS[*]}}"
    incomplete=""
  else
    incomplete="${{COMP_WORDS[COMP_CWORD]}}"
  fi

  local curpos nextpos nextword
  nextpos=0

{cmd_reply}

  return 0
}}

_qiime_completion
"""

COMMAND_REPLY_TEMPLATE = """\
curpos=${{nextpos}}
while :
do
  nextpos=$((curpos + 1))
  nextword="${{COMP_WORDS[nextpos]}}"
  if [[ ${{nextpos}} -eq ${{COMP_CWORD}} ]] ; then
    if [[ ${{incomplete}} == -* ]] ; then
      echo "$(compgen -W "{options}" -- $incomplete)"
    else
      echo "$(compgen -W "{subcmd_names}" -- $incomplete)"
    fi
    return 0
  else
    case "${{nextword}}" in
{subcmd_cases}
    esac
    curpos=${{nextpos}}
  fi
done\
"""

SUBCOMMAND_CASE_TEMPLATE = """\
{subcmd_name})
{subcmd_reply}
  ;;
"""
