#!/usr/bin/env python3
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:

DESCRIP = 'Run and log examples'
EPILOG = \
""" Run examples in directory

Typical usage is:

run_log_examples.py nipy/examples --log-path=~/tmp/eg_logs

to run the examples and log the result, or

run_log_examples.py nipy/examples/some_example.py

to run a single example.
"""

import os
import re
import sys
from argparse import ArgumentParser, RawDescriptionHelpFormatter
from os.path import abspath, dirname, expanduser, isfile
from os.path import join as pjoin
from os.path import sep as psep
from subprocess import PIPE, Popen

PYTHON=sys.executable
NEED_SHELL = True

class ProcLogger:
    def __init__(self, log_path, working_path):
        self.log_path = log_path
        self.working_path = working_path
        self._names = []

    def cmd_str_maker(self, cmd, args):
        return " ".join([cmd] + list(args))

    def __call__(self, cmd_name, cmd, args=(), cwd=None):
        # Mqke log files
        if cmd_name in self._names:
            raise ValueError(f'Command name {cmd_name} not unique')
        self._names.append(cmd_name)
        if cwd is None:
            cwd = self.working_path
        cmd_out_path = pjoin(self.log_path, cmd_name)
        stdout_log = open(cmd_out_path + '.stdout', "w")
        stderr_log = open(cmd_out_path + '.stderr', "w")
        try:
            # Start subprocess
            cmd_str = self.cmd_str_maker(cmd, args)
            proc = Popen(cmd_str,
                        cwd = cwd,
                        stdout = stdout_log,
                        stderr = stderr_log,
                        shell = NEED_SHELL)
            # Execute
            retcode = proc.wait()
        finally:
            if proc.poll() is None: # In case we get killed
                proc.terminate()
            stdout_log.close()
            stderr_log.close()
        return retcode

    def run_pipes(self, cmd, args=(), cwd=None):
        if cwd is None:
            cwd = self.working_path
        try:
            # Start subprocess
            cmd_str = self.cmd_str_maker(cmd, args)
            proc = Popen(cmd_str,
                         cwd = cwd,
                         stdout = PIPE,
                         stderr = PIPE,
                         shell = NEED_SHELL)
            # Execute
            stdout, stderr = proc.communicate()
        finally:
            if proc.poll() is None: # In case we get killed
                proc.terminate()
        return stdout.decode(), stderr.decode(), proc.returncode


class PyProcLogger(ProcLogger):
    def cmd_str_maker(self, cmd, args):
        """ Execute python script `cmd`

        Reject any `args` because we're using ``exec`` to execute the script.

        Prepend some matplotlib setup to suppress figures
        """
        if len(args) != 0:
            raise ValueError(f"Cannot use args with {self.__class__}")
        return(f"""{PYTHON} -c "import matplotlib as mpl; mpl.use('agg'); """
               f"""exec(open('{cmd}', 'rt').read())" """)


def _record(result, fname, fileobj):
    print(result)
    fileobj.write(f'{fname}: {result}\n')


def main():
    parser = ArgumentParser(description=DESCRIP,
                            epilog=EPILOG,
                            formatter_class=RawDescriptionHelpFormatter)
    parser.add_argument('examples_path', type=str,
                        help='filename of example or directory containing '
                             'examples to run')
    parser.add_argument('--log-path', type=str, default='',
                        help='path for output logs (default is cwd)')
    parser.add_argument('--excludex', type=str, action='append', default=[],
                        help='regex for files to exclude (add more than one '
                        '--excludex option for more than one regex filter')
    args = parser.parse_args()
    # Proc runner
    eg_path = abspath(expanduser(args.examples_path))
    if args.log_path == '':
        log_path = abspath(os.getcwd())
    else:
        log_path = abspath(expanduser(args.log_path))
    excludexes = [re.compile(s) for s in args.excludex]
    if isfile(eg_path): # example was a file
        proc_logger = PyProcLogger(log_path=log_path,
                                   working_path=dirname(eg_path))
        print("Running " + eg_path)
        stdout, stderr, code = proc_logger.run_pipes(eg_path)
        print('==== Stdout ====')
        print(stdout)
        print('==== Stderr ====')
        print(stderr)
        sys.exit(code)
    # Multi-run with logging to file
    proc_logger = PyProcLogger(log_path=log_path,
                               working_path=eg_path)
    fails = 0
    with open(pjoin(log_path, 'summary.txt'), "w") as f:
        for dirpath, dirnames, filenames in os.walk(eg_path):
            for fname in filenames:
                full_fname = pjoin(dirpath, fname)
                if fname.endswith(".py"):
                    print(fname, end=': ')
                    sys.stdout.flush()
                    for excludex in excludexes:
                        if excludex.search(fname):
                            _record('SKIP', fname, f)
                            break
                    else: # run test
                        cmd_name = full_fname.replace(eg_path + psep, '')
                        cmd_name = cmd_name.replace(psep, '-')
                        code = proc_logger(cmd_name, full_fname, cwd=dirpath)
                        if code == 0:
                            _record('OK', fname, f)
                        else:
                            fails += 1
                            _record('FAIL', fname, f)
    sys.exit(min(255, fails))


if __name__ == '__main__':
    main()
