"""
Run DPS-Pro pipeline
"""
import logging
import sys
import pkg_resources
from typing import List, Optional
from pathlib import Path

from snakemake import snakemake
from snakemake.utils import available_cpu_count

logger = logging.getLogger(__name__)


class SnakemakeError(Exception):
    pass


def add_arguments(parser):
    arg = parser.add_argument
    # Positionals
    arg("targets", nargs="*", metavar="<TARGETS>",
        help="File(s) to create excluding paths). If omitted, the full pipeline is run.")
    # Options
    arg("-n", "--dryrun", default=False, action="store_true",
        help="Perform dry run of pipeline. DEFAULT: False.")
    arg("-j", "--cores", "--jobs", metavar="<JOBS>", type=int, default=available_cpu_count(),
        help="Maximum number of cores to run in parallel. DEFAULT: Use as many as available.")
    arg('--keepgoing', '-k', default=False, action='store_true',
        help='If one job fails, finish the others.')
    arg('--delete-all-output', default=False, action='store_true',
        help="Remove all files generated by the snakemake workflow. Use together with -n/--dry-run to list files "
             "without actually deleting anything. Write-protected files are not removed. Nevertheless, use with care! "
             "Default: %(default)s")
    arg('--force-run', '-R', nargs='*',
        help="Force the re-execution or creation of the given rules or files. Use this option if you changed a rule "
             "and want to have all its output in your workflow updated. Default: %(default)s")
    arg('--unlock', default=False, action='store_true',
        help='Remove a lock on the working directory.')
    arg("--dir", help="Path to analysis directory. DEFAULT: CWD")

    dags = parser.add_mutually_exclusive_group()
    dags.add_argument(
        "--dag", default=False, action="store_true",
        help="Print the dag in the graphviz dot language. DEFAULT: False. To det output to pdf file, "
             "pipe output into dot as follows: '$ dbspro run --dag | dot -Tpdf > dag.pdf'")
    dags.add_argument(
        "--filegraph", default=False, action='store_true',
        help="Print the file graph showing input/output file from rules in the graphviz dot language (requires "
             "graphviz to be installed). Default: %(default)s. To get output to pdf file, pipe output into dot "
             "as follows: blr run --filegraph | dot -Tpdf > filegraph.pdf")


def main(args):
    targets = args.targets if args.targets else None
    try:
        run(dryrun=args.dryrun,
            cores=args.cores,
            keepgoing=args.keepgoing,
            unlock=args.unlock,
            printdag=args.dag,
            printfilegraph=args.filegraph,
            targets=targets,
            delete_all_output=args.delete_all_output,
            force_run=args.force_run)
    except SnakemakeError:
        sys.exit(1)
    sys.exit(0)


def run(
        dryrun: bool = False,
        cores: int = 4,
        keepgoing: bool = False,
        unlock: bool = False,
        printdag: bool = False,
        printfilegraph: bool = False,
        targets: Optional[List[str]] = None,
        delete_all_output: bool = False,
        force_run: Optional[List[str]] = None,
        workdir: Optional[Path] = None,
):
    # snakemake sets up its own logging, and this cannot be easily changed
    # (setting keep_logger=True crashes), so remove our own log handler
    # for now
    logger.root.handlers = []
    snakefile_path = pkg_resources.resource_filename("dbspro", "rules.smk")
    success = snakemake(snakefile_path,
                        snakemakepath="snakemake",  # Needed in snakemake 3.9.0
                        dryrun=dryrun,
                        printdag=printdag,
                        printfilegraph=printfilegraph,
                        quiet=False if not printdag else True,
                        cores=cores,
                        keepgoing=keepgoing,
                        unlock=unlock,
                        printshellcmds=True,
                        targets=targets,
                        delete_all_output=delete_all_output,
                        forcerun=force_run,
                        workdir=workdir,
                        log_handler=[print_log_on_error],
                        printreason=dryrun)
    if not success:
        raise SnakemakeError()


def print_log_on_error(msg):
    """Prints logs of failed rules in case of error"""
    if msg["level"] == "job_error" and msg["log"]:
        for log in msg["log"]:
            head = f"=== Output from log: '{log}' ==="
            print(head)
            if log.exists:
                with open(log) as f:
                    print(f.read().strip())
            print("-"*len(head))
