#!/usr/bin/env python3

# Note: compatible with Python2.7 and Python3.

from __future__ import print_function
import sys
import string
import argparse

def parse_arguments():
    def append_slash(s):
        return s+'/' if s and not s.endswith('/') else s

    class ConciseHelpFormatter(argparse.HelpFormatter):
        def __init__(self, **kwargs):
            super(ConciseHelpFormatter, self).__init__(max_help_position=20, **kwargs)

        def _format_action_invocation(self, action):
            if not action.option_strings:
                return super(ConciseHelpFormatter, self)._format_action_invocation(action)
            else:
                optstr = ', '.join(action.option_strings)
                if action.nargs==0:
                    return optstr
                else:
                    return optstr+' '+self._format_args(action, action.dest.upper())

    parser = argparse.ArgumentParser(
        description = 'Generate global default catalogue source for Arbor build.',
        usage = '%(prog)s [options] [module...]',
        add_help = False,
        formatter_class = ConciseHelpFormatter)

    parser.add_argument(
        'modules',
        nargs = '*',
        help = argparse.SUPPRESS)

    group = parser.add_argument_group('Options')

    group.add_argument(
        '-I', '--module-prefix',
        default = 'mechanisms',
        metavar = 'PATH',
        dest = 'modpfx',
        type = append_slash,
        help = 'directory prefix for module includes, default "%(default)s"')

    group.add_argument(
        '-A', '--arbor-prefix',
        default = '',
        metavar = 'PATH',
        dest = 'arbpfx',
        type = append_slash,
        help = 'directory prefix for arbor includes, default "%(default)s"')

    group.add_argument(
        '-B', '--backend',
        default = [],
        action = 'append',
        dest = 'backends',
        metavar = 'BACKEND',
        help = 'register implementations for back-end %(metavar)s')

    group.add_argument(
        '-N', '--namespace',
        default = [],
        action = 'append',
        dest = 'namespaces',
        metavar = 'NAMESPACE',
        help = 'add %(metavar)s to list of implicitly included namespaces')

    group.add_argument(
        '-C', '--catalogue',
        default = 'default',
        dest = 'catalogue',
        help = 'catalogue name, default "%(default)s"')

    group.add_argument(
        '-o', '--output',
        default = [],
        dest = 'output',
        metavar = 'FILE',
        help = 'save output to %(metavar)s (default is to print to stdout)')

    group.add_argument(
        '-h', '--help',
        action = 'help',
        help = 'display this help and exit')

    return vars(parser.parse_args())


def generate(catalogue, modpfx='', arbpfx='', modules=[], backends=[], namespaces=[], **rest):
    src = string.Template(\
r'''// Automatically generated by:
// $cmdline

#include <${arbpfx}mechcat.hpp>
#include <${arbpfx}mechanism.hpp>
#include <${arbpfx}mechanism_abi.h>
$backend_includes
$module_includes

namespace arb {

mechanism_catalogue build_${catalogue}_catalogue() {
    mechanism_catalogue cat;

    $add_modules
    $register_modules
    return cat;
}

const mechanism_catalogue& global_${catalogue}_catalogue() {
    static mechanism_catalogue cat = build_${catalogue}_catalogue();
    return cat;
}

} // namespace arb

#ifdef STANDALONE
extern "C" {
    [[gnu::visibility("default")]] const void* get_catalogue() {
        static auto cat = arb::build_${catalogue}_catalogue();
        return (void*)&cat;
    }
}
#endif
''')

    def indent(n, lines):
        return '{{:<{0!s}}}'.format(n+1).format('\n').join(lines)

    # TODO: use the commented include list below when private/public
    # headers are resolved.

    return src.safe_substitute(dict(
        cmdline=" ".join(sys.argv),
        arbpfx=arbpfx,
        catalogue=catalogue,
        backend_includes = indent(0, []),
        module_includes = indent(0,
            ['#include "{}{}.hpp"'.format(modpfx, m) for m in modules]),
        add_modules = indent(4,
            [f'cat.add("{mod}", make_arb_{catalogue}_catalogue_{mod}());' for mod in modules]),
        register_modules = indent(4,
            [f'cat.register_implementation("{mod}", std::make_unique<mechanism>(make_arb_{catalogue}_catalogue_{mod}(), *make_arb_{catalogue}_catalogue_{mod}_interface_{be}()));' for mod in modules for be in backends])
        ))


args = parse_arguments()
code = generate(**args)
if args['output']:
    print(code, file = open(args['output'],'w'))
else:
    print(code)
