#!/usr/bin/env python3
#
# This file is part of Script of Scripts (sos), a workflow system
# for the execution of commands and scripts in different languages.
# Please visit https://github.com/bpeng2000/SOS for more information.
#
# Copyright (C) 2016 Bo Peng (bpeng@mdanderson.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import sys
import argparse

from pysos._version import __version__, SOS_FULL_VERSION
from pysos.sos_script import sos_run, sos_dryrun, sos_prepare, sos_show

def addCommonArgs(parser):
    parser.add_argument('-v', '--verbosity', type=int, choices=range(5),
            help='''Output error (0), warning (1), info (2), debug (3) and trace (4)
            information to standard output (default to 2).'''),

if __name__ == '__main__':
    master_parser = argparse.ArgumentParser(description='''A workflow system
            for the execution of commands and scripts in different languages.''',
        prog='sos',
        fromfile_prefix_chars='@',
        epilog='''Use 'sos cmd -h' for details about each subcommand. Please
            contact Bo Peng (bpeng at mdanderson.org) if you have any question.''')

    master_parser.add_argument('--version', action='version',
        version='%(prog)s {}'.format(SOS_FULL_VERSION))
    subparsers = master_parser.add_subparsers(title='subcommands')
    script_help = '''A SoS script that defines one or more workflows. The
        script can be a filename or a URL from which the content of a SoS will
        be read. If a valid file cannot be located or downloaded, SoS will
        search for the script in a search path specified by variable `sos_path`
        defined in the global SoS configuration file (~/.sos/config.json).'''
    workflow_spec =  '''Name of the workflow to execute. This option can be
        ignored if the script defines a default workflow (with no name or with
        name `default`) or defines only a single workflow. A subworkflow or a 
        combined workflow can also be specified, where a subworkflow executes a
        subset of workflow (`name_steps` where `steps` can be `n` (a step `n`),
        `-n` (up to step `n`), `n-m` (from step `n` to `m`), and `n-` (from step
        `n`)), and a combined workflow executes to multiple (sub)workflows
        combined by `+` (e.g. `A_0+B+B`).'''
    workflow_options = '''Arbitrary parameters defined by the [parameters] step
        of the script, and [parameters] steps of other scripts if nested workflows
        are defined in other SoS files (option `source`). The name, default and
        type of the parameters are specified in the script. Single value parameters
        should be passed using option `--name value` and multi-value parameters
        should be passed using option `--name value1 value2`. '''
    #
    # command run
    parser = subparsers.add_parser('run',
        description='Execute a workflow defined in script',
        epilog=workflow_options,
        help='Execute a SoS script')
    parser.add_argument('script', metavar='SCRIPT', help=script_help)
    parser.add_argument('workflow', metavar='WORKFLOW', nargs='?',
        help=workflow_spec)
    parser.add_argument('-j', type=int, metavar='JOBS', default=1, dest='__max_jobs__',
        help='''Number of concurrent process allowed. A workflow is by default
            executed sequentially (-j 1). If a greater than 1 number is specified
            SoS will execute the workflow in parallel mode and execute up to 
            specified processes concurrently. These include looped processes
            within a step (with runtime option `concurrent=True`) and steps with
            non-missing required files.''')
    parser.add_argument('-c', dest='__config__', metavar='CONFIG_FILE',
        help='''A configuration file in the format of YAML/JSON. The content
            of the configuration file will be available as a dictionary 
            CONF in the SoS script being executed.''')
    runmode = parser.add_argument_group(title='Run mode options',
        description='''SoS scripts are by default executed in run mode where all
            steps are executed. If one or more of the runmode options are
            specified (e.g. '-d' or '-dpr'), SoS will execute the script in
            dryrun mode, preparation mode, and run mode if respective options
            are specified, but will stop if an error occurs at one mode. 
            This allows you to detect problems and download resources before
            running a script in full power. ''')
    runmode.add_argument('-d', action='store_true', dest='__dryrun__',
        help='''Execute the workflow in dryrun mode in which step processes
            are executed normally but with most SoS actions return directly.
            SoS also produces diagnoistic warning messages in this mode.''')
    runmode.add_argument('-p', action='store_true', dest='__prepare__',
        help='''Execute the workflow in preparation mode in which SoS prepare
            the execution of workflow by, for example, download required 
            resources and docker images.''')
    runmode.add_argument('-r', action='store_true', dest='__run__',
        help='''Execute the workflow in run mode in which all SoS steps will be
            executed.''')
    runmode.add_argument('-f', action='store_true', dest='__rerun__',
        help='''Execute the workflow in a special run mode that ignores saved runtime
            signatures and re-execute all the steps. This option overrides '-r'
            if both are specified.''')
    addCommonArgs(parser)
    parser.set_defaults(func=sos_run)
    #
    # command dryrun
    parser = subparsers.add_parser('dryrun',
        description='''Execute a workflow in dryrun mode. In comparison to
            run mode, SoS does not panic for non-existing input or output
            files, does not execute most SoS actions, and does not save or
            check runtime signatures. It simply goes through the workflow,
            figure out input/output of steps, and produce warning messages
            if necessary.''',
        epilog=workflow_options,
        help='Execute a SoS script in dryrun mode')
    parser.add_argument('script', metavar='SCRIPT', help=script_help)
    parser.add_argument('workflow', metavar='WORKFLOW', nargs='?',
        help=workflow_spec)
    #parser.add_argument('options', metavar='WORKFLOW_OPTIONS', 
    #    nargs=argparse.REMAINDER, help=workflow_options)
    addCommonArgs(parser)
    parser.set_defaults(func=sos_dryrun)
    #
    # command prepare
    parser = subparsers.add_parser('prepare',
        description='''Execute a workflow in prepare mode in which SoS 
            prepares the exeuction of workflow by, for example, download
            required resources and docker images.''',
        epilog=workflow_options,
        help='Execute a SoS script in prepare mode')
    parser.add_argument('script', metavar='SCRIPT', help=script_help)
    parser.add_argument('workflow', metavar='WORKFLOW', nargs='?',
        help=workflow_spec)
    #parser.add_argument('options', metavar='WORKFLOW_OPTIONS', 
    #    nargs=argparse.REMAINDER, help=workflow_options)
    addCommonArgs(parser)
    parser.set_defaults(func=sos_prepare)
    #
    # command show
    parser = subparsers.add_parser('show',
        description='''The show command displays details of all workflows
            defined in a script, including description of script, workflow,
            steps, and command line parameters. The output can be limited
            to a specified workflow (which can be a subworkflow or a combined
            workflow) if a workflow is specified.''',
        help='Show details of a SoS script')
    parser.add_argument('script', metavar='SCRIPT', help=script_help)
    parser.add_argument('workflow', metavar='WORKFLOW', nargs='?',
        help=workflow_spec)
    addCommonArgs(parser)
    parser.set_defaults(func=sos_show)
    #
    if len(sys.argv) == 1:
        master_parser.print_help()
        sys.exit(0)
    args, workflow_args = master_parser.parse_known_args()
    # calling the associated functions
    args.func(args, workflow_args)
