"""Click definitions for various shared options and arguments."""

from __future__ import absolute_import

import functools
import os

import click
from galaxy.tool_util.deps import docker_util

from .config import planemo_option


def force_option(what="files"):
    """Annotate click command as consume the -f/--force option."""
    return planemo_option(
        "-f",
        "--force",
        is_flag=True,
        help="Overwrite existing %s if present." % what,
    )


def skip_venv_option():
    """Annotate click command as consume the --skip_venv option."""
    return planemo_option(
        "--skip_venv",
        is_flag=True,
        help=("Do not create or source a virtualenv environment for Galaxy, "
              "this should be used to preserve an externally configured "
              "virtual environment or conda environment.")
    )


def skip_client_build_option():
    """Annotate click command as consume the --skip_client_build option."""
    return planemo_option(
        "--skip_client_build",
        is_flag=True,
        default=False,
        help=("Do not build Galaxy client when serving Galaxy.")
    )


def run_engine_option():
    """Annotate click command as consume the --engine option."""
    return planemo_option(
        "--engine",
        type=click.Choice(["galaxy", "docker_galaxy", "cwltool", "toil", "external_galaxy"]),
        default=None,
        use_global_config=True,
        help=("Select an engine to run or test artifacts such as tools "
              "and workflows. Defaults to a local Galaxy, but running Galaxy within "
              "a Docker container or the CWL reference implementation 'cwltool' and "
              "'toil' be selected.")
    )


def non_strict_cwl_option():
    """Annotate click command as consume the --non_strict_cwl option."""
    return planemo_option(
        "--non_strict_cwl",
        default=False,
        is_flag=True,
        help="Disable strict validation of CWL.",
    )


def serve_engine_option():
    """Annotate click command as consume the --engine option.

    This variant of the engine command is restricted to engines that can serve Galaxy
    servers.
    """
    return planemo_option(
        "--engine",
        type=click.Choice(["galaxy", "docker_galaxy", "external_galaxy"]),
        default="galaxy",
        use_global_config=True,
        use_env_var=True,
        help=("Select an engine to serve artifacts such as tools "
              "and workflows. Defaults to a local Galaxy, but running Galaxy within "
              "a Docker container.")
    )


def ignore_dependency_problems_option():
    """Annotate click command as consume the --ignore_dependency_problems option."""
    return planemo_option(
        "--ignore_dependency_problems",
        is_flag=True,
        default=False,
        use_global_config=True,
        help=("When installing shed repositories for workflows, ignore dependency issues. "
              "These likely indicate a problem but in some cases may not prevent a workflow "
              "from successfully executing.")
    )


def cwltool_no_container_option():
    """Annotate click command as consume the --no_container option.

    This option is for the CWL CLI runner interface.
    """
    return planemo_option(
        "--no-container",
        "--no_container",
        is_flag=True,
        default=False,
        use_global_config=True,
        help=("If cwltool engine is used, disable Docker container usage.")
    )


def test_data_option():
    """Annotate click command as consume the --test_data option."""
    return planemo_option(
        "--test_data",
        type=click.Path(exists=True, file_okay=False, resolve_path=True),
        help="test-data directory to for specified tool(s).",
    )


def extra_tools_option():
    return planemo_option(
        "--extra_tools",
        type=click.Path(exists=True,
                        file_okay=True,
                        dir_okay=True,
                        resolve_path=True),
        multiple=True,
        help=("Extra tool sources to include in Galaxy's tool panel (file or "
              "directory). These will not be linted/tested/etc... but they "
              "will be available to workflows and for interactive use.")
    )


def tool_data_table_option():
    return planemo_option(
        "--tool_data_table",
        type=click.Path(exists=True, file_okay=True, resolve_path=True),
        help="tool_data_table_conf.xml file to for specified tool(s).",
    )


def galaxy_email_option():
    return planemo_option(
        "--galaxy_email",
        type=str,
        default="planemo@galaxyproject.org",
        use_global_config=True,
        use_env_var=True,
        help="E-mail address to use when launching single-user Galaxy server.",
    )


def galaxy_python_version():
    return planemo_option(
        '--galaxy_python_version',
        use_global_config=True,
        default=None,
        type=click.Choice(['3', '3.6', '3.7', '3.8', '3.9']),
        help="Python version to start Galaxy under",
    )


def galaxy_root_option():
    return planemo_option(
        "--galaxy_root",
        use_global_config=True,
        extra_global_config_vars=["galaxy_root"],
        use_env_var=True,
        type=click.Path(file_okay=False, dir_okay=True, resolve_path=True),
        help="Root of development galaxy directory to execute command with.",
    )


def galaxy_cwl_root_option():
    return planemo_option(
        "--cwl_galaxy_root",
        use_global_config=True,
        extra_global_config_vars=["cwl_galaxy_root"],
        use_env_var=True,
        type=click.Path(exists=True, file_okay=False, resolve_path=True),
        help=("Root of development galaxy directory to execute command with"
              " (must be branch of Galaxy with CWL support, this option"
              " is experimental and will be replaced with --galaxy_root when"
              " and if CWL support is merged into Galaxy."),
    )


def galaxy_port_option():
    return planemo_option(
        "--port",
        type=int,
        default="9090",
        use_global_config=True,
        help="Port to serve Galaxy on (default is 9090).",
    )


def galaxy_host_option():
    return planemo_option(
        "--host",
        type=str,
        default="127.0.0.1",
        use_global_config=True,
        help=("Host to bind Galaxy to. Default is 127.0.0.1 that is "
              "restricted to localhost connections for security reasons "
              "set to 0.0.0.0 to bind Galaxy to all ports including "
              "potentially publicly accessible ones."),
    )


def dependency_resolvers_option():
    return planemo_option(
        "--dependency_resolvers_config_file",
        type=click.Path(
            exists=True,
            file_okay=True,
            dir_okay=False,
            resolve_path=True
        ),
        use_global_config=True,
        help="Dependency resolver configuration for Galaxy to target.",
    )


def enable_cwl_option():
    return planemo_option(
        "--cwl",
        is_flag=True,
        help=("Configure Galaxy for use with CWL tool."
              " (this option is experimental and will be replaced when"
              " and if CWL support is merged into Galaxy)."),
    )


def build_cwl_option():
    return planemo_option(
        "--cwl",
        is_flag=True,
        help="Build a CWL tool instead of a Galaxy tool.",
    )


def run_history_tags_option():
    return planemo_option(
        "tags",
        "--tags",
        type=str,
        default=None,
        help="Comma-separated list of tags to add to the created history.",
    )


def run_output_directory_option():
    return planemo_option(
        "output_directory",
        "--output_directory",
        "--outdir",
        type=click.Path(
            file_okay=False,
            dir_okay=True,
            resolve_path=True,
        ),
        default=None,
        help=("Where to store outputs of a 'run' task."),
    )


def run_output_json_option():
    return planemo_option(
        "output_json",
        "--output_json",
        type=click.Path(
            file_okay=True,
            dir_okay=False,
            resolve_path=True,
        ),
        default=None,
        help=("Where to store JSON dictionary describing outputs of "
              "a 'run' task."),
    )


def run_download_outputs_option():
    return planemo_option(
        "--download_outputs/--no_download_outputs",
        is_flag=True,
        default=False,
        help=("After tool or workflow runs are complete, download "
              "the output files to the location specified by --output_directory. "
              ),
    )


def no_dependency_resolution():
    return planemo_option(
        "--no_dependency_resolution",
        is_flag=True,
        help="Configure Galaxy with no dependency resolvers.",
    )


def brew_dependency_resolution():
    return planemo_option(
        "--brew_dependency_resolution",
        is_flag=True,
        help="Configure Galaxy to use plain brew dependency resolution.",
    )


def conda_dependency_resolution():
    return planemo_option(
        "--conda_dependency_resolution",
        is_flag=True,
        help="Configure Galaxy to use only conda for dependency resolution.",
    )


def shed_dependency_resolution():
    return planemo_option(
        "--shed_dependency_resolution",
        is_flag=True,
        help=("Configure Galaxy to use brewed Tool Shed dependency"
              " resolution."),
    )


def file_path_option():
    return planemo_option(
        "--file_path",
        type=click.Path(
            file_okay=False,
            dir_okay=True,
            resolve_path=True
        ),
        help="Location for files created by Galaxy (e.g. database/files).",
        default=None,
        use_global_config=True,
    )


def database_connection_option():
    return planemo_option(
        "--database_connection",
        type=str,
        help="Database connection string to use for Galaxy.",
        default=None,
        use_global_config=True,
    )


def shed_tools_conf_option():
    return planemo_option(
        "--shed_tool_conf",
        type=str,
        help="Location of shed tools conf file for Galaxy.",
        default=None,
        use_global_config=True,
    )


def shed_tools_directory_option():
    return planemo_option(
        "--shed_tool_path",
        type=str,
        help="Location of shed tools directory for Galaxy.",
        default=None,
        use_global_config=True,
    )


def tool_dependency_dir_option():
    return planemo_option(
        "--tool_dependency_dir",
        type=click.Path(
            exists=True,
            file_okay=False,
            dir_okay=True,
            resolve_path=True
        ),
        default=None,
        use_global_config=True,
        help="Tool dependency dir for Galaxy to target.",
    )


def job_config_option():
    return planemo_option(
        "--job_config_file",
        type=click.Path(
            exists=True,
            file_okay=True,
            dir_okay=False,
            resolve_path=True
        ),
        help="Job configuration file for Galaxy to target.",
        default=None,
        use_global_config=True,
    )


def mulled_containers_option():
    return planemo_option(
        "mulled_containers",
        "--mulled_containers",
        "--biocontainers",
        is_flag=True,
        help="Test tools against mulled containers (forces --docker). Disables conda resolution unless any conda option has been set explicitly.",
    )


def install_galaxy_option():
    return planemo_option(
        "--install_galaxy",
        is_flag=True,
        help="Download and configure a disposable copy of Galaxy from github."
    )


def docker_galaxy_image_option():
    return planemo_option(
        "--docker_galaxy_image",
        default="quay.io/bgruening/galaxy",
        use_global_config=True,
        help=("Docker image identifier for docker-galaxy-flavor used if "
              "engine type is specified as ``docker-galaxy``. Defaults to "
              "quay.io/bgruening/galaxy.")
    )


def docker_extra_volume_option():
    arg_type = click.Path(
        exists=True,
        file_okay=True,
        dir_okay=True,
        readable=True,
        resolve_path=True,
    )
    return planemo_option(
        "--docker_extra_volume",
        type=arg_type,
        default=None,
        use_global_config=True,
        help=("Extra path to mount if --engine docker.")
    )


def galaxy_url_option():
    return planemo_option(
        "--galaxy_url",
        use_global_config=True,
        extra_global_config_vars=["galaxy_url"],
        use_env_var=True,
        type=str,
        help="Remote Galaxy URL to use with external Galaxy engine.",
    )


def galaxy_admin_key_option():
    return planemo_option(
        "--galaxy_admin_key",
        use_global_config=True,
        extra_global_config_vars=["admin_key"],
        use_env_var=True,
        type=str,
        help="Admin key to use with external Galaxy engine.",
    )


def galaxy_user_key_option():
    return planemo_option(
        "--galaxy_user_key",
        use_global_config=True,
        extra_global_config_vars=["admin_key"],
        use_env_var=True,
        type=str,
        help="User key to use with external Galaxy engine.",
    )


def history_name():
    return planemo_option(
        "--history_name",
        type=str,
        help="Name to give a Galaxy history, if one is created.",
    )


def no_cache_galaxy_option():
    return planemo_option(
        "--no_cache_galaxy",
        is_flag=True,
        help=("Skip caching of Galaxy source and dependencies obtained with "
              "--install_galaxy. Not caching this results in faster "
              "downloads (no git) - so is better on throw away instances such "
              "with TravisCI. ")
    )


def galaxy_branch_option():
    return planemo_option(
        "--galaxy_branch",
        default=None,
        use_global_config=True,
        use_env_var=True,
        help=("Branch of Galaxy to target (defaults to master) if a Galaxy "
              "root isn't specified.")
    )


def galaxy_source_option():
    return planemo_option(
        "--galaxy_source",
        default=None,
        use_global_config=True,
        help=("Git source of Galaxy to target (defaults to the official "
              "galaxyproject github source if a Galaxy root isn't "
              "specified.")
    )


def skip_install_option():
    return planemo_option(
        "--skip_install",
        is_flag=True,
        help="Skip installation - only source requirements already available."
    )


def brew_option():
    return planemo_option(
        "--brew",
        use_global_config=True,
        type=click.Path(exists=True, file_okay=True, dir_okay=False),
        help="Homebrew 'brew' executable to use."
    )


def conda_prefix_option():
    return planemo_option(
        "--conda_prefix",
        use_global_config=True,
        use_env_var=True,
        type=click.Path(file_okay=False, dir_okay=True),
        help="Conda prefix to use for conda dependency commands."
    )


def conda_exec_option():
    return planemo_option(
        "--conda_exec",
        use_global_config=True,
        type=click.Path(exists=True, file_okay=True, dir_okay=False),
        help="Location of conda executable."
    )


def conda_use_local_option():
    return planemo_option(
        "--conda_use_local",
        is_flag=True,
        help="Use locally built packages while building Conda environments."
    )


def conda_ensure_channels_option():
    return planemo_option(
        "conda_ensure_channels",
        "--conda_channels",
        "--conda_ensure_channels",
        type=str,
        use_global_config=True,
        use_env_var=True,
        help=("Ensure conda is configured with specified comma separated "
              "list of channels."),
        default="conda-forge,bioconda,defaults",
    )


def conda_auto_install_option():
    return planemo_option(
        "--conda_auto_install/--no_conda_auto_install",
        is_flag=True,
        default=True,
        help=("Conda dependency resolution for Galaxy will attempt to install "
              "requested but missing packages.")
    )


def conda_auto_init_option():
    return planemo_option(
        "--conda_auto_init/--no_conda_auto_init",
        is_flag=True,
        default=True,
        help=("Conda dependency resolution for Galaxy will auto install "
              "conda itself using miniconda if not availabe on conda_prefix.")
    )


def conda_global_option():
    return planemo_option(
        "--global",
        is_flag=True,
        default=False,
        help=("Install Conda dependencies globally instead of in requirement specific "
              "environments packaged for tools. If the Conda bin directory is on your "
              "PATH, tools may still use binaries but this is more designed for "
              "interactive testing and debugging.")
    )


def simultaneous_upload_option():
    return planemo_option(
        "--simultaneous_uploads/--no_simultaneous_uploads",
        is_flag=True,
        default=False,
        help=("When uploading files to Galaxy for tool or workflow tests or runs, "
              "upload multiple files simultaneously without waiting for the previous "
              "file upload to complete.")
    )


def check_uploads_ok_option():
    return planemo_option(
        "--check_uploads_ok/--no_check_uploads_ok",
        is_flag=True,
        default=True,
        help=("When uploading files to Galaxy for tool or workflow tests or runs, "
              "check that the history is in an 'ok' state before beginning tool "
              "or workflow execution.")
    )


def required_tool_arg(allow_uris=False):
    """ Decorate click method as requiring the path to a single tool.
    """
    arg_type_class = click.Path if not allow_uris else UriLike
    arg_type = arg_type_class(
        exists=True,
        file_okay=True,
        dir_okay=False,
        readable=True,
        resolve_path=True,
    )
    if allow_uris:
        name = "uri"
        metavar = "TOOL_URI"
    else:
        name = "path"
        metavar = "TOOL_PATH"
    return click.argument(name, metavar=metavar, type=arg_type)


def paste_test_data_paths_option():
    return planemo_option(
        "--paste_test_data_paths/--no_paste_test_data_paths",
        is_flag=True,
        default=None,
        help=("By default Planemo will use or not use Galaxy's path paste option to load "
              "test data into a history based on the engine type it is targeting. This can "
              "override the logic to explicitly enable or disable path pasting.")
    )


def shed_install_option():
    return planemo_option(
        "--shed_install/--no_shed_install",
        is_flag=True,
        default=True,
        help=("By default Planemo will attempt to install repositories needed for workflow "
              "testing. This may not be appropriate for production servers and so this can "
              "disabled by calling planemo with --no_shed_install.")
    )


def install_tool_dependencies_option():
    return planemo_option(
        "--install_tool_dependencies/--no_install_tool_dependencies",
        is_flag=True,
        default=False,
        help=("Turn on installation of tool dependencies using classic toolshed packages.")
    )


def install_resolver_dependencies_option():
    return planemo_option(
        "--install_resolver_dependencies/--no_install_resolver_dependencies",
        is_flag=True,
        default=True,
        help=("Skip installing tool dependencies through resolver (e.g. conda).")
    )


def install_repository_dependencies_option():
    return planemo_option(
        "--install_repository_dependencies/--no_install_repository_dependencies",
        is_flag=True,
        default=True,
        help=("Skip installing the repository dependencies.")
    )


def single_user_mode_option():
    return planemo_option(
        "galaxy_single_user",
        "--galaxy_single_user/--no_galaxy_single_user",
        is_flag=True,
        default=True,
        help=("By default Planemo will configure Galaxy to run in single-user mode where there "
              "is just one user and this user is automatically logged it. Use --no_galaxy_single_user "
              "to prevent Galaxy from running this way.")
    )


def required_workflow_arg():
    return click.argument(
        'workflow_identifier',
        metavar="WORKFLOW_PATH_OR_ID",
        type=str,
    )


def split_job_and_test():
    return click.option("--split_test/--no_split_test", default=False, help="Write workflow job and test definitions to separate files.")


def required_job_arg():
    """Decorate click method as requiring the path to a single tool.
    """
    arg_type = click.Path(
        exists=True,
        file_okay=True,
        dir_okay=False,
        readable=True,
        resolve_path=False,
    )
    return click.argument("job_path", metavar="JOB_PATH", type=arg_type)


def required_runnable_arg():
    return click.argument(
        'runnable_identifier',
        metavar="RUNNABLE_PATH_OR_ID",
        type=str,
    )


def required_new_job_arg():
    arg_type = click.Path()
    return click.argument("new_job_path", metavar="NEW_JOB_PATH", type=arg_type)


def _optional_tools_default(ctx, param, value):
    if param.name in ["paths", "uris"] and len(value) == 0:
        return [os.path.abspath(os.getcwd())]
    else:
        return value


def optional_tools_or_packages_arg(multiple=False):
    """ Decorate click method as optionally taking in the path to a tool
    or directory of tools or a Conda package. If no such argument is given
    the current working directory will be treated as a directory of tools.
    """
    name = "paths" if multiple else "path"
    nargs = -1 if multiple else 1
    return click.argument(
        name,
        metavar="TARGET",
        nargs=nargs,
    )


class UriLike(click.Path):

    def convert(self, value, param, ctx):
        if "://" in value:
            return value
        else:
            return super(UriLike, self).convert(value, param, ctx)


def optional_tools_arg(multiple=False, allow_uris=False):
    """ Decorate click method as optionally taking in the path to a tool
    or directory of tools. If no such argument is given the current working
    directory will be treated as a directory of tools.
    """
    arg_type_class = click.Path if not allow_uris else UriLike
    arg_type = arg_type_class(
        exists=True,
        file_okay=True,
        dir_okay=True,
        readable=True,
        resolve_path=True,
    )
    if allow_uris:
        name = "uris" if multiple else "uri"
    else:
        name = "paths" if multiple else "path"
    nargs = -1 if multiple else 1
    return click.argument(
        name,
        metavar="TOOL_PATH",
        type=arg_type,
        nargs=nargs,
        callback=_optional_tools_default,
    )


class ProjectOrRepositry(click.Path):

    def __init__(self, **kwds):
        super(ProjectOrRepositry, self).__init__(**kwds)

    def convert(self, value, param, ctx):
        if value and value.startswith("git:") or value.startswith("git+"):
            return value
        else:
            return super(ProjectOrRepositry, self).convert(value, param, ctx)


def shed_project_arg(multiple=True):
    arg_type = ProjectOrRepositry(
        exists=True,
        file_okay=False,
        dir_okay=True,
        resolve_path=True,
    )
    name = "paths" if multiple else "path"
    nargs = -1 if multiple else 1
    return click.argument(
        name,
        metavar="PROJECT",
        type=arg_type,
        nargs=nargs,
        callback=_optional_tools_default,
    )


def recipe_arg(multiple=True):
    name = "paths" if multiple else "path"
    nargs = -1 if multiple else 1
    return click.argument(
        name,
        metavar="RECIPE_DIR",
        type=click.Path(
            exists=True,
            file_okay=True,
            dir_okay=True,
            resolve_path=True,
        ),
        nargs=nargs,
        callback=_optional_tools_default,
    )


def optional_project_arg(exists=True, default="."):
    arg_type = click.Path(
        exists=exists,
        file_okay=False,
        dir_okay=True,
        writable=True,
        resolve_path=True,
    )
    return click.argument(
        "path",
        metavar="PROJECT",
        default=default,
        type=arg_type
    )


def no_cleanup_option():
    return planemo_option(
        "--no_cleanup",
        is_flag=True,
        help=("Do not cleanup temp files created for and by Galaxy.")
    )


def docker_enable_option():
    return planemo_option(
        "--docker/--no_docker",
        default=False,
        help=("Run Galaxy tools in Docker if enabled.")
    )


def docker_cmd_option():
    return planemo_option(
        "--docker_cmd",
        default=docker_util.DEFAULT_DOCKER_COMMAND,
        help="Command used to launch docker (defaults to docker)."
    )


def docker_sudo_option():
    return planemo_option(
        "--docker_sudo/--no_docker_sudo",
        is_flag=True,
        help="Flag to use sudo when running docker."
    )


def docker_sudo_cmd_option():
    return planemo_option(
        "--docker_sudo_cmd",
        help="sudo command to use when --docker_sudo is enabled " +
             "(defaults to sudo).",
        default=docker_util.DEFAULT_SUDO_COMMAND,
        use_global_config=True,
    )


def docker_host_option():
    return planemo_option(
        "--docker_host",
        help="Docker host to target when executing docker commands " +
             "(defaults to localhost).",
        use_global_config=True,
        default=docker_util.DEFAULT_HOST,
    )


def docker_config_options():
    return _compose(
        docker_cmd_option(),
        docker_sudo_option(),
        docker_host_option(),
        docker_sudo_cmd_option(),
    )


def galaxy_docker_options():
    return _compose(
        docker_enable_option(),
        docker_config_options(),
    )


def shed_owner_option():
    return planemo_option(
        "--owner",
        help="Tool Shed repository owner (username)."
    )


def shed_name_option():
    return planemo_option(
        "--name",
        help="Tool Shed repository name (defaults to the inferred "
             "tool directory name)."
    )


def validate_shed_target_callback(ctx, param, value):
    if value is None:
        ctx.fail("default_shed_target set to None, must specify a value for "
                 "--shed_target to run this command.")
    return value


def shed_target_option():
    return planemo_option(
        "-t",
        "--shed_target",
        help="Tool Shed to target (this can be 'toolshed', 'testtoolshed', "
             "'local' (alias for http://localhost:9009/), an arbitrary url "
             "or mappings defined ~/.planemo.yml.",
        default=None,
        use_global_config=True,
        callback=validate_shed_target_callback,
    )


def shed_key_option():
    return planemo_option(
        "--shed_key",
        help=("API key for Tool Shed access. An API key is required unless "
              "e-mail and password is specified. This key can be specified "
              "with either --shed_key or --shed_key_from_env.")
    )


def shed_key_from_env_option():
    return planemo_option(
        "--shed_key_from_env",
        help="Environment variable to read API key for Tool Shed access from."
    )


def shed_email_option():
    return planemo_option(
        "--shed_email",
        help="E-mail for Tool Shed auth (required unless shed_key is "
             "specified)."
    )


def shed_password_option():
    return planemo_option(
        "--shed_password",
        help="Password for Tool Shed auth (required unless shed_key is "
             "specified)."
    )


def shed_skip_upload():
    return planemo_option(
        "--skip_upload",
        is_flag=True,
        help=("Skip upload contents as part of operation, only update "
              "metadata.")
    )


def shed_skip_metadata():
    return planemo_option(
        "--skip_metadata",
        is_flag=True,
        help=("Skip metadata update as part of operation, only upload "
              "new contents.")
    )


def shed_message_option():
    return planemo_option(
        "-m",
        "--message",
        help="Commit message for tool shed upload."
    )


def shed_force_create_option():
    return planemo_option(
        "--force_repository_creation",
        help=("If a repository cannot be found for the specified user/repo "
              "name pair, then automatically create the repository in the "
              "toolshed."),
        is_flag=True,
        default=False
    )


def shed_check_diff_option():
    return planemo_option(
        "--check_diff",
        is_flag=True,
        help=("Skip uploading if the shed_diff detects there would be no "
              "'difference' (only attributes populated by the shed would "
              "be updated.)")
    )


def shed_upload_options():
    return _compose(
        shed_message_option(),
        shed_force_create_option(),
        shed_check_diff_option(),
    )


def shed_realization_options():
    return _compose(
        shed_project_arg(multiple=True),
        recursive_shed_option(),
        shed_fail_fast_option(),
    )


def shed_repo_options():
    return _compose(
        shed_owner_option(),
        shed_name_option(),
    )


def shed_publish_options():
    """ Common options for commands that require publishing to a
    a shed.
    """
    return _compose(
        shed_realization_options(),
        shed_repo_options(),
        shed_target_options(),
    )


def shed_read_options():
    """ Common options that require read access to mapped repositories
    in a shed.
    """
    return _compose(
        shed_realization_options(),
        shed_repo_options(),
        shed_target_options(),
    )


def shed_target_options():
    """ Common options for commands that require read-only
    interactions with a shed.
    """
    return _compose(
        shed_email_option(),
        shed_key_option(),
        shed_key_from_env_option(),
        shed_password_option(),
        shed_target_option(),
    )


def conda_target_options(include_local=True):
    return _compose(
        conda_prefix_option(),
        conda_exec_option(),
        conda_ensure_channels_option(),
        conda_use_local_option(),
    )


def github_namespace():
    return planemo_option(
        '--namespace',
        use_env_var=True,
        help=('Organization or username under which to create or update workflow repository. '
              'Must be a valid github username or organization'),
        default="iwc-workflows"
    )


def dry_run():
    return planemo_option(
        '--dry_run',
        help="Don't execute action, show preview of action.",
        is_flag=True,
        default=False,
    )


def galaxy_run_options():
    return _compose(
        galaxy_target_options(),
        galaxy_port_option(),
        galaxy_host_option(),
    )


def galaxy_config_options():
    return _compose(
        test_data_option(),
        tool_data_table_option(),
        dependency_resolvers_option(),
        brew_dependency_resolution(),
        shed_dependency_resolution(),
        no_dependency_resolution(),
        conda_target_options(),
        conda_dependency_resolution(),
        conda_auto_install_option(),
        conda_auto_init_option(),
        simultaneous_upload_option(),
        check_uploads_ok_option(),
        # Profile options...
        profile_option(),
        profile_database_options(),
        file_path_option(),
        database_connection_option(),
        shed_tools_conf_option(),
        shed_tools_directory_option(),
        single_user_mode_option(),
    )


def galaxy_target_options():
    return _compose(
        galaxy_root_option(),
        galaxy_python_version(),
        extra_tools_option(),
        install_galaxy_option(),
        galaxy_branch_option(),
        galaxy_source_option(),
        skip_venv_option(),
        no_cache_galaxy_option(),
        no_cleanup_option(),
        galaxy_email_option(),
        galaxy_docker_options(),
        mulled_containers_option(),
        # Profile options...
        job_config_option(),
        tool_dependency_dir_option(),
    )


def pid_file_option():
    pid_file_type = click.Path(
        file_okay=True,
        dir_okay=False,
        resolve_path=True,
    )
    return planemo_option(
        "--pid_file",
        type=pid_file_type,
        default=None,
        help="Location of pid file is executed with --daemon."
    )


def daemon_option():
    return planemo_option(
        "--daemon",
        is_flag=True,
        help="Serve Galaxy process as a daemon."
    )


def profile_option(required=False):
    return planemo_option(
        "--profile",
        type=click.STRING,
        required=required,
        default=None,
        help=("Name of profile (created with the profile_create command) to use "
              "with this command.")
    )


def alias_option(required=False):
    return planemo_option(
        "--alias",
        type=click.STRING,
        required=required,
        default=None,
        help=("Name of an alias.")
    )


def galaxy_serve_options():
    return _compose(
        galaxy_run_options(),
        serve_engine_option(),
        non_strict_cwl_option(),
        docker_galaxy_image_option(),
        docker_extra_volume_option(),
        galaxy_config_options(),
        daemon_option(),
        pid_file_option(),
        ignore_dependency_problems_option(),
        skip_client_build_option(),
        shed_install_option()
    )


def training_topic_name_option():
    return planemo_option(
        "--topic_name",
        required=True,
        help="Name (directory name) of the topic to create or in which "
             "a tutorial should be created or updates"
    )


def training_topic_option():
    return _compose(
        training_topic_name_option(),
        planemo_option(
            "--topic_title",
            default="Title of the topic",
            help="Title of the topic to create"),
        planemo_option(
            "--topic_summary",
            default="Summary of the topic",
            help="Summary of the topic"),
        planemo_option(
            "--topic_target",
            type=click.Choice(['use', 'admin-dev', 'instructors']),
            default="use",
            help="Target audience for the topic")
    )


def training_tutorial_name_option():
    return planemo_option(
        "--tutorial_name",
        help="Name (directory name) of the tutorial to create or to modify"
    )


def training_tutorial_name_req_option():
    return planemo_option(
        "--tutorial_name",
        required=True,
        help="Name (directory name) of the tutorial to modify"
    )


def training_datatype_option():
    return planemo_option(
        "--datatypes",
        type=click.Path(file_okay=True, resolve_path=True),
        help="YAML file with the correspondance between Zenodo extension and Galaxy datatypes",
        default="shared/datatypes.yaml"
    )


def training_zenodo_option():
    return planemo_option(
        "--zenodo_link",
        help="Zenodo URL with the input data")


def training_tutorial_worflow_option():
    return _compose(
        planemo_option(
            "--workflow",
            type=click.Path(file_okay=True, resolve_path=True),
            help="Workflow of the tutorial (locally)",
            default=None),
        planemo_option(
            "--galaxy_url",
            help="URL of a Galaxy instance with the workflow"),
        planemo_option(
            "--galaxy_api_key",
            help="API key on the Galaxy instance with the workflow"),
        planemo_option(
            "--workflow_id",
            help="ID of the workflow on the Galaxy instance")
    )


def training_tutorial_option():
    return _compose(
        training_tutorial_name_option(),
        planemo_option(
            "--tutorial_title",
            default="Title of the tutorial",
            help="Title of the tutorial"),
        planemo_option(
            "--hands_on",
            is_flag=True,
            default=True,
            help="Add hands-on for the new tutorial"),
        planemo_option(
            "--slides",
            is_flag=True,
            default=False,
            help="Add slides for the new tutorial"),
        training_tutorial_worflow_option(),
        training_zenodo_option()
    )


def training_init_options():
    return _compose(
        training_topic_option(),
        training_tutorial_option(),
        training_datatype_option()
    )


def training_fill_data_library_options():
    return _compose(
        training_topic_name_option(),
        training_tutorial_name_req_option(),
        training_zenodo_option(),
        training_datatype_option()
    )


def training_generate_tuto_from_wf_options():
    return _compose(
        training_topic_name_option(),
        training_tutorial_name_req_option(),
        training_tutorial_worflow_option()
    )


def shed_fail_fast_option():
    return planemo_option(
        "--fail_fast",
        is_flag=True,
        default=False,
        help="If multiple repositories are specified and an error occurs "
             "stop immediately instead of processing remaining repositories."
    )


def lint_xsd_option():
    return planemo_option(
        "--xsd/--no_xsd",
        is_flag=True,
        default=True,
        help=("Include tool XSD validation in linting process.")
    )


def report_level_option():
    return planemo_option(
        "--report_level",
        type=click.Choice(["all", "warn", "error"]),
        default="all",
    )


def report_xunit():
    return planemo_option(
        "--report_xunit",
        type=click.Path(file_okay=True, resolve_path=True),
        help="Output an XUnit report, useful for CI testing",
        default=None,
    )


def skip_option():
    return planemo_option(
        "-s",
        "--skip",
        default=None,
        help=("Comma-separated list of lint tests to skip (e.g. passing "
              "--skip 'citations,xml_order' would skip linting of citations "
              "and best-practice XML ordering.")
    )


def fail_level_option():
    return planemo_option(
        "--fail_level",
        type=click.Choice(['warn', 'error']),
        default="warn"
    )


def recursive_shed_option():
    return recursive_option(
        "Recursively perform command for nested repository directories.",
    )


def recursive_option(help="Recursively perform command for subdirectories."):
    return planemo_option(
        "-r",
        "--recursive",
        is_flag=True,
        help=help,
    )


def merge_test_json():
    target_path = click.Path(
        file_okay=True,
        dir_okay=False,
        resolve_path=True,
    )
    return click.argument(
        'input_paths',
        metavar="INPUT_PATHS",
        type=target_path,
        nargs=-1,
    )


def tool_test_json(var="path"):
    target_path = click.Path(
        file_okay=True,
        dir_okay=False,
        resolve_path=True,
    )
    return click.argument(
        var,
        metavar="FILE_PATH",
        type=target_path,
        default="tool_test_output.json",
    )


def engine_options():
    return _compose(
        run_engine_option(),
        non_strict_cwl_option(),
        cwltool_no_container_option(),
        docker_galaxy_image_option(),
        docker_extra_volume_option(),
        ignore_dependency_problems_option(),
        shed_install_option(),
        install_tool_dependencies_option(),
        install_resolver_dependencies_option(),
        install_repository_dependencies_option(),
        galaxy_url_option(),
        galaxy_admin_key_option(),
        galaxy_user_key_option(),
        history_name(),
        no_wait_option(),
    )


def test_report_options():
    return _compose(
        planemo_option(
            "--test_output",
            type=click.Path(file_okay=True, resolve_path=True),
            use_global_config=True,
            default="tool_test_output.html",
            help=("Output test report (HTML - for humans) defaults to "
                  "tool_test_output.html."),
        ),
        planemo_option(
            "--test_output_text",
            type=click.Path(file_okay=True, resolve_path=True),
            use_global_config=True,
            help=("Output test report (Basic text - for display in CI)"),
            default=None,
        ),
        planemo_option(
            "--test_output_markdown",
            type=click.Path(file_okay=True, resolve_path=True),
            use_global_config=True,
            help=("Output test report (Markdown style - for humans & "
                  "computers)"),
            default=None,
        ),
        planemo_option(
            "--test_output_xunit",
            type=click.Path(file_okay=True, resolve_path=True),
            use_global_config=True,
            help=("Output test report (xunit style - for CI systems"),
            default=None,
        ),
        planemo_option(
            "--test_output_junit",
            type=click.Path(file_okay=True, resolve_path=True),
            use_global_config=True,
            help=("Output test report (jUnit style - for CI systems"),
            default=None,
        ),
        planemo_option(
            "--test_output_allure",
            type=click.Path(file_okay=False, resolve_path=True),
            use_global_config=True,
            help=("Output test allure2 framework resutls"),
            default=None,
        )
    )


def profile_name_argument():
    return click.argument(
        'profile_name',
        metavar="PROFILE_NAME",
        type=str,
    )


def database_identifier_argument():
    return click.argument(
        'identifier',
        metavar="IDENTIFIER",
        type=str,
    )


def postgres_datatype_type_option():
    return planemo_option(
        "--postgres",
        "database_type",
        flag_value="postgres",
        help=("Use postgres database type."),
    )


def no_wait_option():
    return planemo_option(
        "--no_wait",
        is_flag=True,
        default=False,
        prompt=False,
        help="After invoking a job or workflow, do not wait for completion.",
    )


def database_type_option():
    return planemo_option(
        "--database_type",
        default="auto",
        type=click.Choice([
            "postgres",
            "postgres_docker",
            "sqlite",
            "auto",
        ]),
        use_global_config=True,
        help=("Type of database to use for profile - "
              "'auto', 'sqlite', 'postgres', and 'postgres_docker' are available options. "
              "Use postgres to use an existing postgres server you user can "
              "access without a password via the psql command. Use postgres_docker "
              "to have Planemo manage a docker container running postgres. "
              "Data with postgres_docker is not yet persisted past when you restart "
              "the docker container launched by Planemo so be careful with this option."),
    )


def database_source_options():
    """Database connection options for commands that utilize a database."""
    return _compose(
        planemo_option(
            "--postgres_psql_path",
            default="psql",
            use_global_config=True,
            help=("Name or or path to postgres client binary (psql)."),
        ),
        planemo_option(
            "--postgres_database_user",
            default="postgres",
            use_global_config=True,
            help=("Postgres username for managed development databases."),
        ),
        planemo_option(
            "--postgres_database_host",
            default=None,
            use_global_config=True,
            help=("Postgres host name for managed development databases."),
        ),
        planemo_option(
            "--postgres_database_port",
            default=None,
            use_global_config=True,
            help=("Postgres port for managed development databases."),
        ),
    )


def profile_database_options():
    return _compose(
        postgres_datatype_type_option(),
        database_type_option(),
        database_source_options(),
    )


def test_options():
    return _compose(
        planemo_option(
            "--update_test_data",
            is_flag=True,
            help="Update test-data directory with job outputs (normally"
                 " written to directory --job_output_files if specified.)"
        ),
        paste_test_data_paths_option(),
        test_report_options(),
        planemo_option(
            "--test_output_json",
            type=click.Path(file_okay=True, resolve_path=True),
            use_global_config=True,
            help=("Output test report (planemo json) defaults to "
                  "tool_test_output.json."),
            default="tool_test_output.json",
        ),
        planemo_option(
            "--job_output_files",
            type=click.Path(file_okay=False, resolve_path=True),
            help="Write job outputs to specified directory.",
            default=None,
        ),
        planemo_option(
            "--summary",
            type=click.Choice(["none", "minimal", "compact"]),
            default="minimal",
            help=("Summary style printed to planemo's standard output (see "
                  "output reports for more complete summary). Set to 'none' "
                  "to disable completely.")
        )
    )


def _compose(*functions):
    def compose2(f, g):
        return lambda x: f(g(x))
    return functools.reduce(compose2, functions)


def dependencies_script_options():
    return _compose(
        planemo_option(
            "--download_cache",
            type=click.Path(file_okay=False, resolve_path=True),
            use_global_config=True,
            help=("Directory to cache downloaded files, default is $DOWNLOAD_CACHE"),
            default=None,
        ),
    )


def filter_exclude_option():
    return planemo_option(
        "--exclude",
        type=click.Path(resolve_path=False),
        multiple=True,
        help="Paths to exclude.",
    )


def filter_exclude_from_option():
    return planemo_option(
        "--exclude_from",
        type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True),
        multiple=True,
        help="File of paths to exclude.",
    )


def filter_changed_in_commit_option():
    return planemo_option(
        "--changed_in_commit_range",
        help="Exclude paths unchanged in git commit range.",
    )


def ci_chunk_count_option():
    return planemo_option(
        "--chunk_count",
        type=int,
        help="Split output into chunks of this many item and print --chunk such group.",
        default=1,
    )


def ci_group_tools_option():
    return planemo_option(
        "--group_tools",
        is_flag=True,
        help="Group tools of the same repository on a single line."
    )


def ci_chunk_option():
    return planemo_option(
        "--chunk",
        type=int,
        help=("When output is split into --chunk_count groups, output the group 0-indexed"
              "by this option."),
        default=0,
    )


def ci_output_option():
    return planemo_option(
        "--output",
        help="File to output to, or - for standard output.",
        default="-",
    )


def ci_find_options():
    return _compose(
        filter_exclude_option(),
        filter_exclude_from_option(),
        filter_changed_in_commit_option(),
        ci_chunk_count_option(),
        ci_chunk_option(),
        ci_output_option(),
    )


def workflow_output_artifact():
    return planemo_option(
        "-o", "--output",
        default=None,
        type=click.Path(
            file_okay=True,
            dir_okay=False,
            readable=True,
            resolve_path=True,
        )
    )


def tool_init_id_option(prompt=True):
    return planemo_option(
        "-i",
        "--id",
        type=click.STRING,
        prompt=prompt,
        help="Short identifier for new tool (no whitespace)",
    )


def tool_init_tool_option():
    return planemo_option(
        "-t",
        "--tool",
        default=None,
        type=click.Path(exists=False,
                        file_okay=True,
                        dir_okay=False,
                        writable=True,
                        resolve_path=True),
        help="Output path for new tool (default is <id>.xml)",
    )


def tool_init_name_option(prompt=True, help="Name for new tool (user facing)"):
    return planemo_option(
        "-n",
        "--name",
        type=click.STRING,
        prompt=prompt,
        help=help,
    )


def tool_init_version_option():
    return planemo_option(
        "--version",
        default="0.1.0",
        type=click.STRING,
        help="Tool XML version.",
    )


def tool_init_description_option():
    return planemo_option(
        "-d",
        "--description",
        type=click.STRING,
        default=None,
        prompt=False,
        help="Short description for new tool (user facing)",
    )


def tool_init_command_option():
    return planemo_option(
        "-c",
        "--command",
        type=click.STRING,
        default=None,
        prompt=False,
        help=("Command potentially including cheetah variables ()"
              "(e.g. 'seqtk seq -a $input > $output')"),
    )


def tool_init_doi_option():
    return planemo_option(
        "--doi",
        type=click.STRING,
        default=None,
        multiple=True,
        prompt=False,
        help=("Supply a DOI (http://www.doi.org/) easing citation of the tool "
              "for Galxy users (e.g. 10.1101/014043).")
    )


def tool_init_test_case_option():
    return planemo_option(
        "--test_case",
        is_flag=True,
        default=None,
        prompt=False,
        help=("For use with --example_commmand, generate a tool test case from "
              "the supplied example."),
    )


def tool_init_macros_option():
    return planemo_option(
        "--macros",
        is_flag=True,
        default=None,
        prompt=False,
        help="Generate a macros.xml for reuse across many tools.",
    )


def tool_init_cite_url_option():
    return planemo_option(
        "--cite_url",
        type=click.STRING,
        default=None,
        multiple=True,
        prompt=False,
        help=("Supply a URL for citation.")
    )


def tool_init_input_option():
    return planemo_option(
        "--input",
        type=click.STRING,
        default=None,
        prompt=False,
        multiple=True,
        help="An input description (e.g. input.fasta)",
    )


def tool_init_output_option():
    return planemo_option(
        "--output",
        type=click.STRING,
        multiple=True,
        default=None,
        prompt=False,
        help=("An output location (e.g. output.bam), the Galaxy datatype is "
              "inferred from the extension."),
    )


def tool_init_help_text_option():
    return planemo_option(
        "--help_text",
        type=click.STRING,
        default=None,
        prompt=False,
        help="Help text (reStructuredText)",
    )


def tool_init_help_from_command_option():
    return planemo_option(
        "--help_from_command",
        type=click.STRING,
        default=None,
        prompt=False,
        help="Auto populate help from supplied command.",
    )


def tool_init_example_input_option():
    return planemo_option(
        "--example_input",
        type=click.STRING,
        default=None,
        prompt=False,
        multiple=True,
        help=("For use with --example_command, replace input file (e.g. 2.fastq "
              "with a data input parameter)."),
    )


def tool_init_example_output_option():
    return planemo_option(
        "--example_output",
        type=click.STRING,
        default=None,
        prompt=False,
        multiple=True,
        help=("For use with --example_command, replace input file (e.g. 2.fastq "
              "with a tool output)."),
    )


def tool_init_named_output_option():
    return planemo_option(
        "--named_output",
        type=click.STRING,
        multiple=True,
        default=None,
        prompt=False,
        help=("Create a named output for use with command block for example "
              "specify --named_output=output1.bam and then use '-o $output1' "
              "in your command block."),
    )


def tool_init_version_command_option():
    return planemo_option(
        "--version_command",
        type=click.STRING,
        default=None,
        prompt=False,
        help="Command to print version (e.g. 'seqtk --version')",
    )


REQUIREMENT_HELP = "Add a tool requirement package (e.g. 'seqtk' or 'seqtk@1.68')."


def tool_init_requirement_option(help=REQUIREMENT_HELP):
    return planemo_option(
        "--requirement",
        type=click.STRING,
        default=None,
        multiple=True,
        prompt=False,
        help=help,
    )


def tool_init_container_option():
    return planemo_option(
        "--container",
        type=click.STRING,
        default=None,
        multiple=True,
        prompt=False,
        help="Add a Docker image identifier for this tool."
    )


EXAMPLE_COMMAND_HELP = (
    "Example to command with paths to build Cheetah template from "
    "(e.g. 'seqtk seq -a 2.fastq > 2.fasta'). Option cannot be used "
    "with --command, should be used --example_input and "
    "--example_output."
)


def tool_init_example_command_option(help=EXAMPLE_COMMAND_HELP):
    return planemo_option(
        "--example_command",
        type=click.STRING,
        default=None,
        prompt=False,
        help=help,
    )


def mulled_conda_option():
    return planemo_option(
        "--mulled_conda_version",
        type=click.STRING,
        default=None,
        help=("Install a specific version of Conda before running the command, by "
              "default the version that comes with the continuumio miniconda3 image "
              "will be used under Linux and under Mac OS X Conda will be upgraded to "
              "to work around a bug in 4.2.")
    )


def mulled_namespace_option():
    return planemo_option(
        "--mulled_namespace",
        type=click.STRING,
        default="biocontainers",
        help=("Build a mulled image with the specified namespace - defaults to "
              "biocontainers. Galaxy currently only recognizes images with the "
              "namespace biocontainers.")
    )


def mulled_action_option():
    return planemo_option(
        "--mulled_command",
        type=click.STRING,
        default="build-and-test",
        help=("Mulled action to perform for targets - this defaults to 'build-and-test'.")
    )


def mulled_options():
    return _compose(
        mulled_conda_option(),
        mulled_namespace_option(),
        mulled_action_option(),
    )
