#!/usr/bin/env python3
#
import argparse
import os
from   os         import path
import sys


if __name__ == '__main__':
    sys.path.append( path.abspath( path.join( path.dirname( path.realpath(__file__)), '..', '..' )))

import palm.config


LOG = palm.logger(__name__)


def main( prog=None, argv=None ):

    # palm-cli [-h] test self [<unittest-option>]
    # palm-cli [-h] test yaml [<palmrun-option>]
    #
    result = 0

    try:
        opt, argv = parse_option( prog, argv )

        palm.init_logging(opt.get('log_append'))

        if not opt.get('subject') or opt.get('help'):
            print_option( prog, opt.get('subject') )

        elif opt.get('subject') == 'self':
            result = test_self( opt )

        elif opt.get('subject') == 'yaml':
            result = test_yaml( opt )


    except KeyboardInterrupt:
        LOG.info('')
        LOG.info('+++ palm-cli test killed by "^C"')

        result = 2

    except Exception as exception:
        if palm.traceback:
            LOG.error( exception, exc_info=True )
        else:
            LOG.error( str(exception) )

        result = 1

    return result


def test_yaml( options ):

    # 'subject' is not a palmname
    #
    del options['subject']

    # get yamlfile from -c <id>
    #
    yamlfile = palm.system.yaml_config_file(options.get('configuration_identifier'))
    if os.path.isfile(yamlfile):
        report = palm.config.yaml_test(
            yamlfile,
            options, os.environ
            )

        LOG.info( "start testing: '%s'", yamlfile )
        for error in report:
            LOG.error( '> %s', error )
        LOG.info( '%d errors found', len(report) )

        result = 1 if report else 0
        return result

    else:
        LOG.error( "missing yaml file: '{}'".format(yamlfile))
        return 1


def test_self( opt ):

    LOG.info( 'start self testing ...' )

    test_argv = [
        'unittest',
        'discover'
        ]

    if opt.get('verbosity'):
        test_argv.append('-v')
    if opt.get('failfast'):
        test_argv.append('-f')
    if opt.get('catchbreak'):
        test_argv.append('-c')
    if opt.get('tb_locals'):
        test_argv.append('--locals')

    test_argv.extend([
        '-p', '*Tests.py',
        '-s', os.path.join(
            os.path.dirname(__file__),
            '..','..'
            )
        ])

    from unittest.main import main as unittest_main
    result = unittest_main( module=None, argv=test_argv )

    return result


def parse_option( prog=None, argv=None ):

    if argv is None:
        argv = sys.argv[1:]

    parser     = build_argparse( prog )
    opts, argv = parser.parse_known_args( argv )

    # NOTE: convert opt to dict ...
    #
    opts = dict( [ opt for opt in vars(opts).items() if not opt[1] is None ])
    if opts.get('traceback'):
        # show traceback on error
        #
        palm.traceback = True
        del opts['traceback']

    return opts, argv


def print_option( prog, subject ):

    if subject is None:
        build_argparse(prog).print_help()

    else:
        # FIXME: there is no regular way to
        #        get a subparser from an argparser, so
        #        we do it the ugly way
        #
        [
            action for action in build_parser()._actions
            if isinstance(action, argparse._SubParsersAction)
        ][0].choices[subject].print_help()


def build_argparse( prog=None ):

    result = argparse.ArgumentParser(
        prog=prog,
        description='''command for testing PALM''',
        add_help=False
        )
    result.add_argument(
        '-h', '--help',
        dest='help',
        action='store_true',
        help='show helps'
        )

    commands = result.add_subparsers(
        dest='subject',
        metavar='<SUBJECT>',
        help='subject to test:'
        )

    build_argparse_self( commands.add_parser(
        'self',
        help='execute self test'
        ))
    build_argparse_yaml( commands.add_parser(
        'yaml',
        help='test given YAML file'
        ))

    return result


def build_argparse_self( argparser=None ):

    if argparser is None:
        argparser = argparse.ArgumentParser(add_help=False)

    argparser.add_argument(
        '-v', '--verbose',
        dest='verbosity',
        action='store_true',
        help='Verbose output'
        )
    argparser.add_argument(
        '-f', '--failfast',
        dest='failfast',
        action='store_true',
        help='Stop on first fail or error'
        )
    argparser.add_argument(
        '-c', '--catch',
        dest='catchbreak',
        action='store_true',
        help='Catch Ctrl-C and display results so far'
        )
    argparser.add_argument(
        '--locals',
        dest='tb_locals',
        action='store_true',
        help='Show local variables in tracebacks'
        )

    return argparser


def build_argparse_yaml( argparser=None ):

    if argparser is None:
        argparser = argparse.ArgumentParser(add_help=False)

    argparser.add_argument(
        '-a',
        metavar='<ID>',
        dest='activation_string_list',
        type=palm.config.check_arg('-a','activation_string_list',str),
        action='store',
        default=argparse.SUPPRESS,
        help='For steering the handling of input and output files as defined in the file configuration file ..../trunk/SCRIPTS/.palm.iofiles. Argument "d3#" means that the parameter/NAMELIST file for steering PALM shall be provided as input file. This is the minimum setting for option -a, because PALM cannot run without this parameter file.'
        )
    argparser.add_argument(
        '-A',
        dest='project_account',
        metavar='<NO>',
        type=palm.config.check_arg( '-A', 'project_account', str ),
        action='store',
        default=argparse.SUPPRESS,
        help='project account number'
        )
    argparser.add_argument(
        '-b',
        dest='create_batch_job',
        action='store_true',
        default=argparse.SUPPRESS,
        help='create a batch job'
        )
    argparser.add_argument(
        '-B',
        dest='delete_temporary_catalog',
        action='store_false',
        default=argparse.SUPPRESS,
        help='Do not delete the temporary working directory'
        )
    argparser.add_argument(
        '-c',
        metavar='<ID>',
        dest='configuration_identifier',
        type=palm.config.check_arg('-c', 'configuration_identifier', str),
        action='store',
        default=argparse.SUPPRESS,
        help='Specifies the so-called configuration identifier. It tells palmrun which configuration file should be used. -c default means to use the configuration file .palm.config.default.'
        )
    argparser.add_argument(
        '-C',
        dest='restart_run',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Tells that it is a palmrun call for a restart job that has been automatically created. This is an internal option but it can be used for manually generated restart runs, if the user likes to re-use the contents of the SOURCES_FOR_RUN... folder.'
        )
    argparser.add_argument(
        '-F',
        dest='create_jobfile_only',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Create a batch job file only, and do not submit it.'
        )
    argparser.add_argument(
        '-G',
        metavar='<NO>',
        dest='global_revision',
        type=palm.config.check_arg('-G', 'global_revision', int),
        action='store',
        default=argparse.SUPPRESS,
        help='Global revision number of the PALM code in trunk/SOURCES'
        )
    argparser.add_argument(
        '-i',
        dest='run_id_number',
        type=palm.config.check_arg('-i', 'run_id_number', str),
        metavar='<NNNNN>',
        action='store',
        default=argparse.SUPPRESS,
        help='Five digit random number that gives a run-id and that is used as part of the batch job name as well as the name of the temporary working directory and other files. A new random number is created for each call of palmrun (either a manual call by the user or an automatic call for generating a restart job), and is passed to the batch job internal call of palmrun via this option. '
        )
    argparser.add_argument(
        '-j',
        dest='running_in_batch_mode',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Tells that palmrun is running within a batch job'
        )
    argparser.add_argument(
        '-k',
        dest='keep_data_from_previous_run',
        action='store_true',
        default=argparse.SUPPRESS,
        help='If set true, input files that have the ln attribute and that have been generated by a previous run within a job chain will be automatically deleted at the end of the run.'
        )
    argparser.add_argument(
        '-m',
        metavar='<MBYTE>',
        dest='memory',
        type=palm.config.check_arg( '-m', 'memory', int ),
        action='store',
        default=argparse.SUPPRESS,
        help='memory in MByte to be requested in batch jobs per MPI task'
        )
    argparser.add_argument(
        '-M',
        metavar='<FILENAME>',
        dest='makefile',
        type=palm.config.check_arg('-M', 'makefile', str),
        action='store',
        default=argparse.SUPPRESS,
        help='Makefile to compile the PALM code and utility programs. By default, the name of the makefile is Makefile, and it is expected to be in the folder that is given by variable source_path in the configuration file.'
        )
    argparser.add_argument(
        '-O',
        metavar='<COUNT>',
        dest='threads_per_task',
        type=palm.config.check_arg('-O', 'threads_per_task', int),
        action='store',
        default=argparse.SUPPRESS,
        help='OpenMP threads to be started per MPI task. Environment variable OMP_NUM_THREADS will be set to this value'
        )
    argparser.add_argument(
        '-q',
        metavar='<NAME>',
        dest='defaultqueue',
        type=palm.config.check_arg( '-q', 'defaultqueue', str ),
        action='store',
        default=argparse.SUPPRESS,
        help='name of the job queue to which batch jobs will be submitted. See your batch system documentation about available queues and keep in mind that usually each queue has special limits for requested resources.'
        )
    argparser.add_argument(
        '-r',
        metavar='<NAME>',
        dest='run_identifier',
        type=palm.config.check_arg('-r', 'run_identifier', str),
        action='store',
        default=argparse.SUPPRESS,
        help='The name of the run given by -r tells palmrun to use the NAMELIST file <run_identifier>_p3d from JOBS/<run_identifier>/INPUT. It also determines folders and names of output files generated by PALM using informations from the default file configuration file ..../trunk/SCRIPTS/.palm.iofiles. Chapter PALM iofiles explains the format of this file and how you can modify or extend it.'
        )
    argparser.add_argument(
        '-R',
        metavar='<IP-ADR>',
        dest='return_address',
        type=palm.config.check_arg('-R', 'return_address', str),
        action='store',
        default=argparse.SUPPRESS,
        help='Return address. Tells the remote batch job to which IP-address the PALM output and the job protocol file has to be send, and from which machine automatic restarts have to be generated.'
        )
    argparser.add_argument(
        '-s',
        metavar='<LIST>',
        dest='source_list',
        type=palm.config.check_arg('-s', 'source_list', str),
        action='store',
        default=argparse.SUPPRESS,
        help='List of subroutines (Fortran file names) from the SVN repository (under .../trunk/SOURCES) that shall be compiled for this run. Compiled files will be exclusively used for the run and not be put in the MAKE_DEPOSITORY. In case of -s LM, all files in the repository that have been modified by the used will be compiled.'
        )
    argparser.add_argument(
        '-t',
        metavar='<SEC>',
        dest='cpumax',
        type=palm.config.check_arg( '-t', 'cpumax', int ),
        action='store',
        default=argparse.SUPPRESS,
        help='maximum CPU time (wall clock time) in seconds requested by the batch job. This option is ignored in interactive runs.'
        )
    argparser.add_argument(
        '-T',
        metavar='<COUNT>',
        dest='tasks_per_node',
        type=palm.config.check_arg( '-T', 'tasks_per_node', int ),
        action='store',
        default=argparse.SUPPRESS,
        help='number of MPI tasks to be started on one node of the computer system. Typically, <MPI tasks per node> is chosen as the total number of CPU cores available on one node, e.g. if a node has two CPUs with 12 cores each, then <MPI tasks per node> = 24.'
        )
    argparser.add_argument(
        '-u',
        metavar='<NAME>',
        dest='remote_username',
        type=palm.config.check_arg('-u', 'remote_username', str),
        action='store',
        default=argparse.SUPPRESS,
        help='Username on the remote host as given in the configuration file by variable remote_username'
        )
    argparser.add_argument(
        '-U',
        metavar='<NAME>',
        dest='return_username',
        type=palm.config.check_arg('-u', 'remote_username', str),
        action='store',
        default=argparse.SUPPRESS,
        help='Username on the local host as given in the configuration file by variable local_username'
        )
    argparser.add_argument(
        '-v',
        dest='silent',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Suppresses parts of palmrun\'s terminal output and prevents palmrun queries'
        )
    argparser.add_argument(
        '-V',
        dest='use_existing_sources_folder',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Use existing SOURCES_FOR_RUN_... folder. Prevents palmrun from creating a new SOURCES_FOR_RUN_... folder. Use this option if you do not want the user interface files to be compiled again.'
        )
    argparser.add_argument(
        '-w',
        metavar='<COUNT>',
        dest='maximum_parallel_io_streams',
        type=palm.config.check_arg( '-w', 'maximum_parallel_io_streams', int ),
        action='store',
        default=argparse.SUPPRESS,
        help='as -X    Number of parallel I/O streams to be opened by PALM. In the default case, all MPI processes write at the same time. This may cause file system problems in case of a very large number of cores.'
        )
    argparser.add_argument(
        '-W',
        metavar='<ID>',
        dest='previous_job',
        type=palm.config.check_arg('-W', 'previous_job', str),
        action='store',
        default=argparse.SUPPRESS,
        help='Name (id) of a previous job. Can be used as variable {{previous_job}} as part of job directives in the configuration file, in order to prevent the job to start before the specified previous job has been finished. The job name must be the one that have been given by the batch system.'
        )
    argparser.add_argument(
        '-x',
        dest='do_trace',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Causes palmrun to output excessive debug information for both interactive sessions as well as batch jobs.'
        )
    argparser.add_argument(
        '-X',
        metavar='<MAX>',
        dest='cores',
        type=palm.config.check_arg( '-X', 'cores', int ),
        action='store',
        default=argparse.SUPPRESS,
        help='Total number of cores (not CPUs!) to be used for the run. The argument should not be larger than the maximum number of cores available on your computer (except in case of hyperthreading), because that would usually slow down the performance significantly.'
        )
    argparser.add_argument(
        '-y',
        dest='ocean_file_appendix',
        action='store_true',
        default=argparse.SUPPRESS,
        help='Use file appendix _O for local PALM-I/O files in case of uncoupled ocean runs, e.g. if the run is a precursor run and files shall later be used for coupled atmosphere-ocean runs.'
        )
    argparser.add_argument(
        '-Y',
        metavar='<COUNT>',
        dest='s',
        type=palm.config.check_arg( '-Y', 'coupled_dist', int ),
        action='store',
        default=argparse.SUPPRESS,
        help='In case of a coupled atmosphere-ocean run, the parameter tells PALM how many cores shall be assigned to the atmosphere- and ocean-model, respectively. For example, in case of -X 64 -Y "16 48"" 16 cores are assigned to the atmosphere model, and 48 cores to the ocean model.'
        )
    argparser.add_argument(
        '-z',
        dest='running_in_test_mode',
        action='store_true',
        default=argparse.SUPPRESS,
        help='running in test mode'
        )
    argparser.add_argument(
        '-Z',
        dest='combine_plot_fields',
        action='store_false',
        default=argparse.SUPPRESS,
        help='Do not call combine_plot_fields after PALM has finished. In that case, data output of 2d-cross section or 3d-volumes that has been done be each core into a separate file will not be collected into one file. In order to later process these files, option -B should be set too. -Z might be required for very large jobs in order to reduce computational demands, because combine_plot_fields is running on one core only, so that all other cores will run idle.'
        )

    return argparser


if __name__ == "__main__":
    sys.exit( main() )
