#! /usr/bin/env python

# This file is part of Better Enums, released under the BSD 2-clause license.
# See LICENSE for details, or visit http://github.com/aantron/better-enums.

# You only need this script if you are developing enum.h, or run into a limit.
#
# This script generates the macros BETTER_ENUMS_PP_MAP and BETTER_ENUMS_ITERATE,
# used internally by enum.h. These are already inlined into enum.h.
#
# BETTER_ENUMS_PP_MAP has a limit, which determines the maximum number of
# constants an enum can have. By default, this limit is 64 constants.
#
# BETTER_ENUMS_ITERATE also has a limit. This one determines the maximum length
# of the name of a constant that is followed by an initializer (" = 2") when
# compiling an enum with constexpr _to_string function (i.e. usually, this limit
# does not apply). By default, the limit is 23 characters (24 with the
# obligatory null terminator).
#
# If either of these limits is inadequate, you can still compile your code
# without changing enum.h. You need to generate an external macro file with
# definitions of these macros with relaxed limits, and tell enum.h to use the
# external macro file. Here is how this is done, supposing you want support for
# 512 constants of length up to 127 (128 with null terminator):
#
# 0. MACRO_FILE is the name of the external macro file. Make sure you put it
#    somewhere in your include path.
# 1. Run python make_macros.py 512 128 > MACRO_FILE
# 2. Build your code with an additional compiler flag:
#    - for gcc and clang, -DBETTER_ENUMS_MACRO_FILE='<MACRO_FILE>'
#    - for VC++, /DBETTER_ENUMS_MACRO_FILE='<MACRO_FILE>'
#    or use any other method of getting these macros defined.
# 3. Compile your code. Your macro file should be included, and enum.h should
#    happily work with whatever limits you chose.

import os
import sys

class MultiLine(object):
    def __init__(self, stream, indent = 4, columns = 80, initial_column = 0):
        self._columns_left = columns - initial_column
        self._indent = indent
        self._columns = columns
        self._stream = stream

    def write(self, token, last = False):
        break_line = False
        if last:
            if len(token) > self._columns_left:
                break_line = True
        else:
            if len(token) > self._columns_left - 1:
                break_line = True

        if break_line:
            print >> self._stream, ' ' * (self._columns_left - 1) + '\\'
            self._stream.write(' ' * self._indent)
            self._columns_left = self._columns - self._indent
            token = token.lstrip()

        self._stream.write(token)
        self._columns_left -= len(token)

def generate(stream, constants, length, script):
    print >> stream, '// This file was automatically generated by ' + script

    print >> stream, ''
    print >> stream, '#pragma once'
    print >> stream, ''
    print >> stream, '#ifndef BETTER_ENUMS_MACRO_FILE_H'
    print >> stream, '#define BETTER_ENUMS_MACRO_FILE_H'

    print >> stream, ''
    print >> stream, '#define BETTER_ENUMS_PP_MAP(macro, data, ...) \\'
    print >> stream, '    BETTER_ENUMS_ID( \\'
    print >> stream, '        BETTER_ENUMS_APPLY( \\'
    print >> stream, '            BETTER_ENUMS_PP_MAP_VAR_COUNT, \\'
    print >> stream, '            BETTER_ENUMS_PP_COUNT(__VA_ARGS__)) \\'
    print >> stream, '        (macro, data, __VA_ARGS__))'

    print >> stream, ''
    print >> stream, '#define BETTER_ENUMS_PP_MAP_VAR_COUNT(count) ' + \
                     'BETTER_ENUMS_M ## count'

    print >> stream, ''
    print >> stream, '#define BETTER_ENUMS_APPLY(macro, ...) ' + \
                      'BETTER_ENUMS_ID(macro(__VA_ARGS__))'

    print >> stream, ''
    print >> stream, '#define BETTER_ENUMS_ID(x) x'

    print >> stream, ''
    print >> stream, '#define BETTER_ENUMS_M1(m, d, x) m(d,0,x)'
    for index in range(2, constants + 1):
        print >> stream, '#define BETTER_ENUMS_M' + str(index) + \
                         '(m,d,x,...) m(d,' + str(index - 1) + ',x) \\'
        print >> stream, '    BETTER_ENUMS_ID(BETTER_ENUMS_M' + \
                         str(index - 1) + '(m,d,__VA_ARGS__))'

    print >> stream, ''
    pp_count_impl_prefix = '#define BETTER_ENUMS_PP_COUNT_IMPL(_1,'
    stream.write(pp_count_impl_prefix)
    pp_count_impl = MultiLine(stream = stream, indent = 4,
                              initial_column = len(pp_count_impl_prefix))
    for index in range(2, constants + 1):
        pp_count_impl.write(' _' + str(index) + ',')
    pp_count_impl.write(' count,')
    pp_count_impl.write(' ...)')
    pp_count_impl.write(' count', last = True)
    print >> stream, ''

    print >> stream, ''
    print >> stream, '#define BETTER_ENUMS_PP_COUNT(...) \\'
    pp_count_prefix = \
        '    BETTER_ENUMS_ID(BETTER_ENUMS_PP_COUNT_IMPL(__VA_ARGS__,'
    stream.write(pp_count_prefix)
    pp_count = MultiLine(stream = stream, indent = 8,
                         initial_column = len(pp_count_prefix))
    for index in range(0, constants - 1):
        pp_count.write(' ' + str(constants - index) + ',')
    pp_count.write(' 1))', last = True)
    print >> stream, ''

    print >> stream, ''
    iterate_prefix = '#define BETTER_ENUMS_ITERATE(X, f, l)'
    stream.write(iterate_prefix)
    iterate = MultiLine(stream = stream, indent = 4,
                        initial_column = len(iterate_prefix))
    for index in range(0, length):
        iterate.write(' X(f, l, %i)' % index)
    print >> stream, ''

    print >> stream, ''
    print >> stream, '#endif // #ifndef BETTER_ENUMS_MACRO_FILE_H'

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print >> sys.stderr, \
            'Usage: ' + sys.argv[0] + ' CONSTANTS LENGTH > FILE'
        print >> sys.stderr, ''
        print >> sys.stderr, 'Prints map macro definition to FILE.'
        print >> sys.stderr, 'CONSTANTS is the number of constants to support.'
        print >> sys.stderr, 'LENGTH is the maximum length of a constant name.'
        sys.exit(1)

    generate(sys.stdout, int(sys.argv[1]), int(sys.argv[2]),
             os.path.basename(sys.argv[0]))

    sys.exit(0)
