#!/usr/bin/env python

import argparse
import io
import os
import re

re_enum_decl = re.compile(r'enum class (\w+)( *// *<(\w+)>)?')
re_enum_value = re.compile(r'(\w+)(?= *([,=]|//|$))')
re_values = re.compile(r'UNC_OPTVALS\((\w+)\)')
re_aliases = re.compile(r'UNC_OPTVAL_ALIAS\(([^)]+)\)')
enums = {}
values = {}

root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
script = os.path.relpath(__file__, root)


# =============================================================================
class Enumeration(object):
    # -------------------------------------------------------------------------
    def __init__(self, name, prefix, f):
        self.name = name
        self.prefix = prefix

        self.values = []
        self.value_aliases = {}

        self.convert_internal = False

        for line in iter(f.readline, ''):
            line = line.strip()

            if line.startswith('{'):
                for line in iter(f.readline, ''):
                    line = line.strip()
                    if line.startswith('};'):
                        return

                    if 'UNC_INTERNAL' in line:
                        return

                    if 'UNC_CONVERT_INTERNAL' in line:
                        self.convert_internal = True
                        continue

                    mv = re_enum_value.match(line)
                    if mv is not None:
                        v = mv.group(1)
                        self.values.append(v)
                        self.value_aliases[v] = [v.lower()]

    # -------------------------------------------------------------------------
    def add_aliases(self, value, *args):
        aliases = [x[1:-1] for x in args]  # strip quotes
        self.value_aliases[value] += aliases

# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


# -----------------------------------------------------------------------------
def enum_value(enum, value):
    if enum.prefix is not None:
        return u'{}_{}'.format(enum.prefix, value)
    return value

# -----------------------------------------------------------------------------
def write_banner(out, args):
    out.write(
        u'/**\n'
        u' * @file {out_name}\n'
        u' * Helpers for option enumerators.\n'
        u' * Automatically generated by <code>{script}</code>\n'
        u' * from {in_name}.\n'
        u' */\n'
        u'\n'.format(
            in_name=os.path.basename(args.header),
            out_name=os.path.basename(args.output),
            script=script))


# -----------------------------------------------------------------------------
def write_value_strings(out, args):
    for vn, vs in values.items():
        out.write(u'const char *const {}_values[] = {{\n'.format(vn))
        out.write(u'{}\n   nullptr\n}};\n\n'.format(
            u'\n'.join([u'   "{}",'.format(x.lower()) for x in vs])))


# -----------------------------------------------------------------------------
def write_aliases(out, args):
    for enum in enums.values():
        if enum.prefix is None:
            continue

        for v in enum.values:
            out.write(u'constexpr auto {p}_{v} = {n}::{v};\n'.format(
                p=enum.prefix, n=enum.name, v=v))

        out.write(u'\n')


# -----------------------------------------------------------------------------
def write_conversions(out, args):
    header = u'\n//{}\n'.format('-' * 77)

    for enum in enums.values():
        if enum.convert_internal:
            continue

        out.write(header)
        out.write(
            u'bool convert_string(const char *in, {} &out)\n'.format(
                enum.name))
        out.write(
            u'{\n'
            u'   if (false)\n'
            u'   {\n'
            u'   }\n')

        for v in enum.values:
            for a in enum.value_aliases[v]:
                out.write(
                    u'   else if (strcasecmp(in, "{}") == 0)\n'
                    u'   {{\n'
                    u'      out = {};\n'
                    u'      return(true);\n'
                    u'   }}\n'.format(a, enum_value(enum, v)))

        out.write(
            u'   else\n'
            u'   {\n'
            u'      return(false);\n'
            u'   }\n'
            u'}\n\n')

    for enum in enums.values():
        out.write(header)
        out.write(u'const char *to_string({} val)\n'.format(enum.name))
        out.write(u'{\n'
                  u'   switch (val)\n'
                  u'   {\n')

        for v in enum.values:
            vs = v if enum.convert_internal else v.lower()
            out.write(
                u'   case {}:\n'
                u'      return "{}";\n\n'.format(
                    enum_value(enum, v), vs))

        out.write(
            u'   default:\n'
            u'      fprintf(stderr, "%s: Unknown {} \'%d\'\\n",\n'
            u'              __func__, static_cast<int>(val));\n'
            u'      log_flush(true);\n'
            u'      exit(EX_SOFTWARE);\n'
            u'   }}\n'
            u'}}\n\n'.format(enum.name))


# -----------------------------------------------------------------------------
def main():
    parser = argparse.ArgumentParser(description='Generate options.cpp')
    parser.add_argument('output', type=str,
                        help='location of options.cpp to write')
    parser.add_argument('header', type=str,
                        help='location of options.h to read')
    parser.add_argument('template', type=str,
                        help='location of option_enum.cpp.in '
                             'to use as template')
    args = parser.parse_args()

    with io.open(args.header, 'rt', encoding='utf-8') as f:
        for line in iter(f.readline, ''):
            line = line.strip()

            me = re_enum_decl.match(line)
            if me is not None:
                e = Enumeration(me.group(1), me.group(3), f)
                enums[e.name] = e
                continue

            mv = re_values.match(line)
            if mv is not None:
                enum_name = mv.group(1)
                enum = enums['{}_e'.format(enum_name)]
                values[enum_name] = enum.values

            ma = re_aliases.match(line)
            if ma is not None:
                alias_args = [x.strip() for x in ma.group(1).split(',')]
                enum = enums[alias_args[0]]
                enum.add_aliases(*alias_args[1:])

    replacements = {
        u'##BANNER##': write_banner,
        u'##VALUE_STRINGS##': write_value_strings,
        u'##ALIASES##': write_aliases,
        u'##CONVERSIONS##': write_conversions,
    }

    with io.open(args.output, 'wt', encoding='utf-8') as out:
        with io.open(args.template, 'rt', encoding='utf-8') as t:
            for line in t:
                directive = line.strip()
                if directive in replacements:
                    replacements[directive](out, args)
                else:
                    out.write(line)


# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

if __name__ == '__main__':
    main()
