#! /usr/bin/env python

import argparse
import os
import re

# A dictionary of all the bridges, with the register name as key.
# Bridges are dictionaries providing the following keys:
#
# args: the effective arguments (e.g., "aut, i").
# file: the vcsn/algos header that provides the bridge.
# formals: the formals arguments (e.g., "const automaton& aut, unsigned i").
# algo: function name (e.g., multiply).
# reg: register name (e.g., multiply_polynomial).
# return: return type.
bridges = {}

bridge_pattern = re.compile(r'''///\ Bridge(?:\s+\((?P<algo>\w+)\))?.
\s*template\s*<.*?>
(?:\s*inline)?
\s*(?P<return>[\w:&*]+)\s+(?P<reg>\w+)\s*\((?P<formals>.*?)\)''',
                    flags=re.VERBOSE | re.DOTALL)

register = '''  // {reg} ({file}).
  REGISTRY_DECLARE({reg},
    ({formals}) -> {return});

'''

implementation = '''
  // {reg} ({file}).
  REGISTRY_DEFINE({reg});
  {return}
  {algo}({formals})
  {{
    return detail::{reg}_registry().call({args});
  }}

'''

def process_header(fn):
    '''Store in bridges the signatures from header `fn`.'''
    with open(fn, 'r') as f:
        for m in bridge_pattern.finditer(f.read()):
            bridge = m.groupdict()
            bridge['file'] = os.path.basename(fn)
            reg = bridge['reg']
            # Compute the algo name.
            if bridge['algo'] is None:
                bridge['algo'] = reg
            bridge['formals'] = re.sub(r'\s+', ' ', bridge['formals'])
            bridge['args'] = ', '.join(re.sub(r'[^,]* (\w+)',
                                              r'\1',
                                              bridge['formals']).split(','))
            if reg in bridges:
                raise RuntimeError("bridge multiply defined: {}\n"
                                   "  first: {}\n"
                                   "  second: {})"
                                   .format(reg, bridges[reg]['file'], bridge['file']))
            bridges[reg] = bridge

def process_implem(fn):
    '''Find the registries already dealt with in the implementation file `fn`.'''
    with open(fn, 'r') as f:
        for m in re.finditer(r'REGISTRY_DEFINE\((.*?)\);', f.read()):
            bridges[m.group(1)]['by hand'] = True

# Generate the header.
def header():
    res = r'''// Generated, do not edit by hand.

#pragma once

#include <vector>

#include <vcsn/algos/fwd.hh> // letter_class_t
#include <vcsn/dyn/fwd.hh> // dyn::automaton, etc.
#include <vcsn/dyn/name.hh> // integral_constant
#include <vcsn/dyn/types.hh> // identities, etc.
#include <vcsn/misc/export.hh>
#include <vcsn/misc/fwd.hh> // signature

namespace vcsn
{
  class automaton_editor;
}

namespace vcsn { namespace dyn { namespace detail {

#define REGISTRY_DECLARE(Name, Signature)                       \
  using Name ## _t = auto Signature;                            \
  LIBVCSN_API                                                   \
  bool Name ## _register(const signature& sig, Name ## _t fn)

'''
    for f in sorted(bridges):
        res += register.format_map(bridges[f])

    res += '''
#undef REGISTRY_DECLARE
}}}'''
    return res

# Generate the implementation file.
def output():
    res = r'''// Generated, do not edit by hand.

#include <lib/vcsn/algos/registry.hh>
#include <vcsn/dyn/algos.hh>
#include <vcsn/dyn/automaton.hh>
#include <vcsn/dyn/context.hh>
#include <vcsn/dyn/registries.hh>
#include <vcsn/dyn/value.hh>

namespace vcsn { namespace dyn {
'''
    for f in sorted(bridges):
        if 'by hand' not in bridges[f]:
            res += implementation.format_map(bridges[f])

    res += '''
}}'''

    return res


# Generate a function which, given an bridge name, returns the header
# that defined it.
def bridge():
    res = r'''// Generated, do not edit by hand.

#include <unordered_map>

#include <lib/vcsn/dyn/context-printer.hh>

namespace vcsn { namespace ast {

  void context_printer::header_algo(const std::string& algo)
  {
    static auto headers
      = std::unordered_map<std::string, std::string>
        {
'''
    for f in sorted(bridges):
        res += '          {{ "{reg}", "{file}" }},\n'.format_map(bridges[f])

    res += '''\
        };
    headers_late_.insert("vcsn/algos/" + headers.at(algo));
  }
}}'''

    return res




parser = argparse.ArgumentParser(description='Generate registries file.',
                                 formatter_class=argparse.RawDescriptionHelpFormatter,
                                 epilog = \
'''
This tool reads the algorithm header files, looking for entries such as:

  /// Bridge (trie).
  template <typename Context, typename Istream>
  automaton
  trie_stream(const context& ctx, std::istream& is)
  {
    const auto& c = ctx->as<Context>();
    auto ps = make_word_polynomialset(c);
    return trie(ps, is);
  }

It does not care about the part in braces: only the comment and the
signature are read.

Be sure to respect the format of the comment:

  /// Bridge (<ALGO>).
or
  /// Bridge.

in the common case where the register name is the same as the function
name, as exposed in dyn:: to the user.


It also looks in the implementations done by hand by looking for lines
such as:

    REGISTRY_DEFINE(project_context);

in the implementation files (such as lib/vcsn/algos/others.cc).
''')

parser.add_argument('--bridge',
                    type=argparse.FileType('w'), default = '-',
                    help='bridge header finder file to generate')
parser.add_argument('--header',
                    type=argparse.FileType('w'), default = '-',
                    help='header file to generate')
parser.add_argument('--output',
                    type=argparse.FileType('w'), default = '-',
                    help='implementation file to generate')
parser.add_argument('--srcdir', type=str, default='.',
                    help='path to the source tree')
parser.add_argument('--headers', nargs='+', type=str, default=None,
                    help='''headers to process (should contain the
                    `/// Bridge.` lines).''')
parser.add_argument('--implems', nargs='+', type=str, default=None,
                    help='''implementation files to process (should contain
                    REGISTRY_DEFINE uses).''')
args = parser.parse_args()
args.srcdir += '/'

for fn in args.headers:
    process_header(args.srcdir + fn)

for fn in args.implems:
    process_implem(args.srcdir + fn)

print(header(), file=args.header)
print(output(), file=args.output)
print(bridge(), file=args.bridge)
