#!python
""" nf-core: Helper tools for use with nf-core Nextflow pipelines. """

from __future__ import print_function

import click
import sys
import os
import re

import nf_core
import nf_core.bump_version
import nf_core.create
import nf_core.download
import nf_core.launch
import nf_core.licences
import nf_core.lint
import nf_core.list
import nf_core.sync

import logging

# Customise the order of subcommands for --help
# https://stackoverflow.com/a/47984810/713980
class CustomHelpOrder(click.Group):
    def __init__(self, *args, **kwargs):
        self.help_priorities = {}
        super(CustomHelpOrder, self).__init__(*args, **kwargs)

    def get_help(self, ctx):
        self.list_commands = self.list_commands_for_help
        return super(CustomHelpOrder, self).get_help(ctx)

    def list_commands_for_help(self, ctx):
        """reorder the list of commands when listing the help"""
        commands = super(CustomHelpOrder, self).list_commands(ctx)
        return (c[1] for c in sorted((self.help_priorities.get(command, 1000), command) for command in commands))

    def command(self, *args, **kwargs):
        """Behaves the same as `click.Group.command()` except capture
        a priority for listing command names in help.
        """
        help_priority = kwargs.pop('help_priority', 1000)
        help_priorities = self.help_priorities
        def decorator(f):
            cmd = super(CustomHelpOrder, self).command(*args, **kwargs)(f)
            help_priorities[cmd.name] = help_priority
            return cmd
        return decorator

@click.group(cls=CustomHelpOrder)
@click.version_option(nf_core.__version__)
@click.option(
    '-v', '--verbose',
    is_flag = True,
    default = False,
    help = "Verbose output (print debug statements)"
)
def nf_core_cli(verbose):
    if verbose:
        logging.basicConfig(level=logging.DEBUG, format="\n%(levelname)s: %(message)s")
    else:
        logging.basicConfig(level=logging.INFO, format="\n%(levelname)s: %(message)s")

# nf-core list
@nf_core_cli.command(help_priority=1)
@click.argument(
    'keywords',
    required = False,
    nargs = -1,
    metavar = "<filter keywords>"
)
@click.option(
    '-s', '--sort',
    type = click.Choice(['release', 'pulled', 'name', 'stars']),
    default = 'release',
    help = "How to sort listed pipelines"
)
@click.option(
    '--json',
    is_flag = True,
    default = False,
    help = "Print full output as JSON"
)
def list(keywords, sort, json):
    """ List nf-core pipelines with local info """
    nf_core.list.list_workflows(keywords, sort, json)

# nf-core launch
@nf_core_cli.command(help_priority=2)
@click.argument(
    'pipeline',
    required = True,
    metavar = "<pipeline name>"
)
@click.option(
    '-p', '--params',
    type = str,
    help = "Local parameter settings file in JSON."
)
@click.option(
    '-d', '--direct',
    is_flag = True,
    help = "Uses given values from the parameter file directly."
)
def launch(pipeline, params, direct):
    """ Run pipeline, interactive parameter prompts """
    nf_core.launch.launch_pipeline(pipeline, params, direct)

# nf-core download
@nf_core_cli.command(help_priority=3)
@click.argument(
    'pipeline',
    required = True,
    metavar = "<pipeline name>"
)
@click.option(
    '-r', '--release',
    type = str,
    help = "Pipeline release"
)
@click.option(
    '-s', '--singularity',
    is_flag = True,
    default = False,
    help = "Download singularity containers"
)
@click.option(
    '-o', '--outdir',
    type = str,
    help = "Output directory"
)
@click.option(
    '-c', '--compress',
    type = click.Choice(['tar.gz', 'tar.bz2', 'zip', 'none']),
    default = 'tar.gz',
    help = "Compression type"
)
def download(pipeline, release, singularity, outdir, compress):
    """ Download a pipeline and singularity container """
    dl = nf_core.download.DownloadWorkflow(pipeline, release, singularity, outdir, compress)
    dl.download_workflow()

# nf-core licences
@nf_core_cli.command(help_priority=4)
@click.argument(
    'pipeline',
    required = True,
    metavar = "<pipeline name>"
)
@click.option(
    '--json',
    is_flag = True,
    default = False,
    help = "Print output in JSON"
)
def licences(pipeline, json):
    """ List software licences for a given workflow """
    lic = nf_core.licences.WorkflowLicences(pipeline)
    lic.fetch_conda_licences()
    lic.print_licences(as_json=json)

# nf-core create
def validate_wf_name_prompt(ctx, opts, value):
    """ Force the workflow name to meet the nf-core requirements """
    if not re.match(r'^[a-z]+$', value):
        click.echo('Invalid workflow name: must be lowercase without punctuation.')
        value = click.prompt(opts.prompt)
        return validate_wf_name_prompt(ctx, opts, value)
    return value
@nf_core_cli.command(help_priority=5)
@click.option(
    '-n', '--name',
    prompt = 'Workflow Name',
    required = True,
    callback = validate_wf_name_prompt,
    type = str,
    help = 'The name of your new pipeline'
)
@click.option(
    '-d', '--description',
    prompt = True,
    required = True,
    type = str,
    help = 'A short description of your pipeline'
)
@click.option(
    '-a', '--author',
    prompt = True,
    required = True,
    type = str,
    help = 'Name of the main author(s)'
)
@click.option(
    '--new-version',
    type = str,
    default = '1.0dev',
    help = 'The initial version number to use'
)
@click.option(
    '--no-git',
    is_flag = True,
    default = False,
    help = "Do not initialise pipeline as new git repository"
)
@click.option(
    '-f', '--force',
    is_flag = True,
    default = False,
    help = "Overwrite output directory if it already exists"
)
@click.option(
    '-o', '--outdir',
    type = str,
    help = "Output directory for new pipeline (default: pipeline name)"
)
def create(name, description, author, new_version, no_git, force, outdir):
    """ Create a new pipeline using the template """
    create_obj = nf_core.create.PipelineCreate(name, description, author, new_version, no_git, force, outdir)
    create_obj.init_pipeline()

@nf_core_cli.command(help_priority=6)
@click.argument(
    'pipeline_dir',
    type = click.Path(exists=True),
    required = True,
    metavar = "<pipeline directory>"
)
@click.option(
    '--release',
    is_flag = True,
    default = os.path.basename(os.path.dirname(os.environ.get('GITHUB_REF','').strip(' \'"'))) == 'master' and os.environ.get('GITHUB_REPOSITORY', '').startswith('nf-core/') and not os.environ.get('GITHUB_REPOSITORY', '') == 'nf-core/tools',
    help = "Execute additional checks for release-ready workflows."
)
def lint(pipeline_dir, release):
    """ Check pipeline against nf-core guidelines """

    # Run the lint tests!
    lint_obj = nf_core.lint.run_linting(pipeline_dir, release)
    if len(lint_obj.failed) > 0:
        sys.exit(1)


@nf_core_cli.command('bump-version', help_priority=7)
@click.argument(
    'pipeline_dir',
    type = click.Path(exists=True),
    required = True,
    metavar = "<pipeline directory>"
)
@click.argument(
    'new_version',
    required = True,
    metavar = "<new version>"
)
@click.option(
    '-n', '--nextflow',
    is_flag = True,
    default = False,
    help = "Bump required nextflow version instead of pipeline version"
)
def bump_version(pipeline_dir, new_version, nextflow):
    """ Update nf-core pipeline version number """

    # First, lint the pipeline to check everything is in order
    logging.info("Running nf-core lint tests")
    lint_obj = nf_core.lint.run_linting(pipeline_dir, False)
    if len(lint_obj.failed) > 0:
        logging.error("Please fix lint errors before bumping versions")
        return

    # Bump the pipeline version number
    if not nextflow:
        nf_core.bump_version.bump_pipeline_version(lint_obj, new_version)
    else:
        nf_core.bump_version.bump_nextflow_version(lint_obj, new_version)


@nf_core_cli.command('sync', help_priority=8)
@click.argument(
    'pipeline_dir',
    type = click.Path(exists=True),
    nargs = -1,
    metavar = "<pipeline directory>"
)
@click.option(
    '-t', '--make-template-branch',
    is_flag = True,
    default = False,
    help = "Create a TEMPLATE branch if none is found."
)
@click.option(
    '-b', '--from-branch',
    type = str,
    help = 'The git branch to use to fetch workflow vars.'
)
@click.option(
    '-p', '--pull-request',
    is_flag = True,
    default = False,
    help = "Make a GitHub pull-request with the changes."
)
@click.option(
    '-u', '--username',
    type = str,
    help = 'GitHub username for the PR.'
)
@click.option(
    '-r', '--repository',
    type = str,
    help = 'GitHub repository name for the PR.'
)
@click.option(
    '-a', '--auth-token',
    type = str,
    help = 'GitHub API personal access token.'
)
@click.option(
    '--all',
    is_flag = True,
    default = False,
    help = "Sync template for all nf-core pipelines."
)
def sync(pipeline_dir, make_template_branch, from_branch, pull_request, username, repository, auth_token, all):
    """ Sync a pipeline TEMPLATE branch with the nf-core template"""

    # Pull and sync all nf-core pipelines
    if all:
        nf_core.sync.sync_all_pipelines(username, auth_token)
    else:
        # Manually check for the required parameter
        if not pipeline_dir or len(pipeline_dir) != 1:
            logging.error("Either use --all or specify one <pipeline directory>")
            sys.exit(1)
        else:
            pipeline_dir = pipeline_dir[0]

        # Sync the given pipeline dir
        sync_obj = nf_core.sync.PipelineSync(pipeline_dir, make_template_branch, from_branch, pull_request)
        try:
            sync_obj.sync()
        except (nf_core.sync.SyncException, nf_core.sync.PullRequestException) as e:
            logging.error(e)
            sys.exit(1)


if __name__ == '__main__':
    click.echo(click.style("\n                                          ,--.", fg='green')+click.style("/",fg='black')+click.style(",-.", fg='green'), err=True)
    click.echo(click.style("          ___     __   __   __   ___     ", fg='blue')+click.style("/,-._.--~\\", fg='green'), err=True)
    click.echo(click.style("    |\ | |__  __ /  ` /  \ |__) |__      ", fg='blue')+click.style("   }  {", fg='yellow'), err=True)
    click.echo(click.style("    | \| |       \__, \__/ |  \ |___     ", fg='blue')+click.style("\`-._,-`-,", fg='green'), err=True)
    click.secho("                                          `._,._,'\n", fg='green', err=True)
    nf_core_cli()
