from __future__ import print_function

import sys

tocnames = {}
tocnames['BOOL'] = 'bool'
tocnames['INT'] = 'int'
tocnames['U8'] = 'u8'
tocnames['CST'] = 'const char *'
tocnames['ENM'] = 'int'
tocnames['DICE'] = 'DICE'

def replacetext(text, replace):
    """ does a replacement of each key/value of replace into text
    """
    for key, value in iter(replace.items()):
        text = text.replace(key, value)
    return text


def error(text):
    """ Reports an error
    """
    print(text)
    sys.exit()

def pushtoken(token, text):
    """ returns text with token prefixed
    """
    return token + ' ' + text

def validatetype(itemttype, text):
    """ errors if itemtype isn't valid
    """

    itemlist = [ 'BOOL', 'U8', 'CST', 'INT', 'ENM', 'DICE' ]

    if itemtype not in itemlist:
        error('Invalid item type ' + itemtype + ' at' + text)


def processdice(dice):
    """ Returns a {} initializer for given dice text
    """

    dice = dice.lower()

    pieces = dice.split('d')
    if len(pieces) == 1:
        return '{ 0, 0, ' + pieces[0] + ' }'
    numdie = pieces[0]

    if len(pieces) != 2:
        error("Malformed dice " + dice)

    neg = ''
    tail = pieces[1].split('+')
    if len(tail) == 1:
        neg = '-'
        tail = pieces[1].split('-')

    if len(tail) == 1:
        return '{ ' + pieces[0] + ', ' + pieces[1] + ', 0 }'

    return '{ ' + pieces[0] + ', ' + tail[0] + ', ' + neg + tail[1] + ' }'
        

def readtoken(text):
    """ returns (token, text) for next token from stream
    """

    if len(text) == 0:
        return ('', '')

    nc = text[0]
    if nc.isspace():
        return readtoken(text[1:])

    if nc == "'":
        result = nc
        result += text[1]
        if text[1] == '\\':
            result += text[2]
            if text[3] != "'":
                error('Unterminated quote' + text)
            result += text[3]
            return (result, text[4:])
        if text[2] != "'":
            error('Unterminated quote' + text)
        result += text[2]
        return (result, text[3:])

    if nc == '"':
        idx = 1
        result = nc
        while text[idx] != '"':
            if text[idx] == '\\':
                result += text[idx]
                idx +=1
            result += text[idx]
            idx += 1
        result += text[idx]
        return (result, text[idx+1:])

    result = nc
    idx = 1
    while not text[idx].isspace() and idx < len(text):
        result += text[idx]
        idx += 1

    return (result, text[idx:])

def lookup_item(defn, name):
    """ Returns (type, enum) of the given name of a definition.
        (None, None) if not found.
    """
    for items in defn:
        if items[1] == name:
            return (items[0], items[2])
    return (None, None)

glbAdhocIndex = 0

def build_adhoc_name():
    """ Returns a unique adhoc name
    """
    global glbAdhocIndex

    glbAdhocIndex += 1
    return "ADHOC" + str(glbAdhocIndex)

def append_instantiation(defns, type, name, forceindex, overrides):
    """ Appends an instantiation to the definition list defns
    """
    deflist = []
    if type in defns:
        deflist = defns[type]
    deflist += ((name, forceindex, overrides), )
    defns[type] = deflist

def process_override_block(type, defn, defns, text):
    """ Returns (overrides, text) where overrides is the set of overrides
        read from the text streams {} block.  The { is already parsed.
        defn and defns is used to recurse, defns may gain some additional
        definitions as a result of this
    """
    overrides = {}
    while True:
        (overname, text) = readtoken(text)

        if overname == '}':
            break

        # Wrong for enumlist
        if overname in overrides:
            error("Repeated definition " + overname + ' in ' + text)

        if type not in defn:
            error("Cannot find " + type + ' at ' + text)
        (overtype, overenum) = lookup_item(defn[type], overname)
        if overtype is None:
            error("Override of non-existent member variable " + overname + ' in ' + text)

        (overval, text) = readtoken(text)

        # We may be defining an adhoc type. This is the case
        # if the override is itself an enum.
        if overval == '{':
            if overtype != 'ENM' or overenum is None:
                error("Adhoc block of " + overname + " fails, only works with ENM, at " + text)

            (childoverrides, text) = process_override_block(overenum, defn, defns, text)
            adhocname = build_adhoc_name()
            append_instantiation(defns, overenum, adhocname, None, childoverrides)
            overval = adhocname

        overrides[overname] = overval

    return (overrides, text)

sf = open('source.txt', 'rt')

ohf = open('glbdef.h', 'wt')
ocf = open('glbdef.cpp', 'wt')

text = sf.read()


typeorder = []

# defn stores all the definition templates
defn = {}
# defns stores all the actuual instantiated definitions.
defns = {}

while len(text):
    (token, text) = readtoken(text)

    if token == '':
        break
    elif token == 'COMMENT':
        (token, text) = readtoken(text)
        if token != '{':
            error('Missing start brace in comment ' + text)
        bracecount = 1
        while bracecount > 0 and len(text):
            (token, text) = readtoken(text)
            if token == '}':
                bracecount -= 1
            if token == '{':
                bracecount += 1

    elif token == 'DEFINE':
        (type, text) = readtoken(text)

        if type in defn:
            error("Redefinition of " + type + " at " + text)

        (token, text) = readtoken(text)
        if token != '{':
            error('Missing start brace in define ' + text)

        itemlist = []
        while True:
            (itemtype, text) = readtoken(text)
            if itemtype == '}':
                break

            itemtype = itemtype.upper()
            validatetype(itemtype, text)

            (itemname, text) = readtoken(text)
            itemenum = None
            if itemtype == 'ENM':
                (itemenum, text) = readtoken(text)
            elif itemtype == 'ENMLIST':
                (itemenum, text) = readtoken(text)
            (itemdef, text) = readtoken(text)

            itemlist += ((itemtype, itemname, itemenum, itemdef),)

        typeorder += (type, )
        defn[type] = itemlist
    else:
        # We require this to be a pre-existing token!
        if token not in defn:
            error('Unknown token ' +token + ' at ' + text)
        type = token

        (name, text) = readtoken(text)

        (open, text) = readtoken(text)

        # It could be this is { starting a block.
        # or a number forcing a specific enum value
        # or something else, suggesting we just are a throw-away enum.
        overrides = {}

        forceindex = None
        if open.isdigit():
            forceindex = int(open)
            (open, text) = readtoken(text)

        if open != '{':
            # throw away
            text = pushtoken(open, text)
        else:
            # {} block
            (overrides, text) = process_override_block(type, defn, defns, text)

        # Store our result.
        append_instantiation(defns, type, name, forceindex, overrides)

# Okay we've parsed into defn all the defintions
# and into defns all the items
# Let us build the C++ code

ohf.write(
"""// Automatically generated by enummaker.py.
//DO NOT EDIT THIS FILE (Yes, I mean you!)
#ifndef __glbdef_h__
#define __glbdef_h__

#include "mygba.h"
#include "rand.h"
#include "buf.h"

inline BUF lcl_savedata(int data)
{
    BUF buf;
    buf.sprintf("%d", data);
    return buf;
}

inline BUF lcl_savedata(u8 data)
{
    BUF buf;
    buf.sprintf("%d", data);
    return buf;
}

inline BUF lcl_savedata(bool data)
{
    BUF buf;
    buf.sprintf("%s", data ? "true" : "false");
    return buf;
}

inline BUF lcl_savedata(const char *data)
{
    BUF buf;
    buf.reference(data);
    buf = buf.protectWithQuotes();
    return buf;
}

inline BUF lcl_savedata(const DICE &dice)
{
    BUF buf;
    buf.sprintf("%dd%d%+d", dice.myNumDie, dice.mySides, dice.myBonus);
    return buf;
}

inline void lcl_loaddata(BUF buf, int &data)
{
    data = atoi(buf.buffer());
}

inline void lcl_loaddata(BUF buf, u8 &data)
{
    if (buf.startsWith("'"))
    {
        data = buf.buffer()[1];
    }
    else
        data = atoi(buf.buffer());
}

inline void lcl_loaddata(BUF buf, bool &data)
{
    if (!buf.strcasecmp("true"))
        data = true;
    else if (!buf.strcasecmp("false"))
        data = false;
    else data = atoi(buf.buffer()) != 0;
}

inline void lcl_loaddata(BUF buf, DICE &data)
{
    data.scanf(buf);
}

inline void lcl_loaddata(BUF buf, const char *&data)
{
    // Leak!  Joyous Leaks!
    data = buf.strdup();
}

""")

structuredefn = ''

for key in typeorder:
    value = defn[key]
    replace = {}
    replace['$$TYPE$$'] = key
    replace['$$type$$'] = key.lower()
    instances =[]
    if key in defns:
        instances = defns[key]
    enumbody = ''
    for instance in instances:
        enumbody += '    '
        enumbody += key
        enumbody += '_'
        enumbody += instance[0]
        if instance[1] is not None:
            enumbody += ' = '
            enumbody += str(instance[1])
        enumbody += ',\n'
    replace['$$ENUMBODY$$'] = enumbody

    structbody = ''
    resetbody = ''
    loadbody = ''
    savebody = ''

    for member in value:
        (itemtype, itemname, itemenum, itemdef) = member
        structbody += '    '
        if itemtype == 'ENM':
            structbody += itemenum + '_NAMES'
        else:
            structbody += tocnames[itemtype]
        structbody += ' '
        structbody += itemname
        structbody += ';\n'

        savebody += '        '
        savebody += 'os << "    ' + itemname + ' " '
        if itemtype == 'ENM':
            savebody += '<< lcl_savedata((int)' + itemname + ').buffer() '
        else:
            savebody += '<< lcl_savedata(' + itemname + ').buffer() '
        savebody += '<< "\\n";\n'

        resetbody += '        '
        if itemtype == 'ENM':
            resetbody += itemname + ' = ' + itemenum + '_' + itemdef + ';\n'
        elif itemtype == 'DICE':
            resetbody += itemname + ' = DICE' + processdice(itemdef) + ';\n'
        else:
            resetbody += itemname + ' = ' + itemdef + ';\n'

        loadbody += '                '
        loadbody += 'if (!_name.strcmp("' + itemname + '"))\n'
        loadbody += '                    '
        if itemtype == 'ENM':
            loadbody += '{ int tmp; lcl_loaddata(_value, tmp); ' + itemname + ' = (' + itemenum + '_NAMES) tmp; }\n'
        else:
            loadbody += 'lcl_loaddata(_value, ' + itemname + ');\n'


    replace['$$STRUCTBODY$$'] = structbody
    replace['$$RESETBODY$$'] = resetbody
    replace['$$SAVEBODY$$'] = savebody
    replace['$$LOADBODY$$'] = loadbody

    ohf.write(replacetext(
"""enum $$TYPE$$_NAMES
{
$$ENUMBODY$$
    NUM_$$TYPE$$S
};

#define FOREACH_$$TYPE$$(x) \\
    for ((x) = ($$TYPE$$_NAMES) 0; \\
         (x) < NUM_$$TYPE$$S; \\
         (x) = ($$TYPE$$_NAMES) ((int)(x)+1))
""", replace))

    structuredefn += replacetext(
"""struct $$TYPE$$_DEF
{
$$STRUCTBODY$$

    void reset()
    {
$$RESETBODY$$
    }

    void saveData(ostream &os) const
    {
$$SAVEBODY$$
    }

    void loadData(istream &is)
    {
        reset();
        BUF     _name, _value;

        while (1)
        {
            _name = BUF::readtoken(is);
            if (_name.isstring() && _name.strcmp("}"))
            {
                _value = BUF::readtoken(is);
$$LOADBODY$$
            }
            else
                break;
        }
    }


};

extern const $$TYPE$$_DEF glb_$$type$$defs[];

""", replace)


ohf.write(structuredefn)

ohf.write("""
#endif
""")


ocf.write(
"""// Automatically generated by enummaker.py.
//DO NOT EDIT THIS FILE (Yes, I mean you!)

#include "mygba.h"
#include "glbdef.h"

""")

for key in typeorder:
    value = defn[key]
    replace = {}
    replace['$$TYPE$$'] = key
    replace['$$type$$'] = key.lower()
    instances =[]
    if key in defns:
        instances = defns[key]

    initbody = ''
    for instance in instances:
        initbody += '  {\n'

        overrides = instance[2]

        validlist = {}

        for member in value:
            (itemtype, itemname, itemenum, itemdef) = member
            if itemname in overrides:
                itemdef = overrides[itemname]
            if itemenum is not None:
                itemdef = itemenum + '_' + itemdef
            if itemtype == 'DICE':
                itemdef = processdice(itemdef)
            initbody += '    '
            initbody += itemdef
            initbody += ',\n'

            validlist[itemname] = True

        # Verify there is no unknown elements
        for itemname in overrides:
            if itemname not in validlist:
                error("Invalid member " + itemname + ' in ' + key + '_' + instance[0])

        enumbody += key
        enumbody += '_'
        enumbody += instance[0]
        if instance[1] is not None:
            enumbody += ' = '
            enumbody += str(instance[1])
        initbody += '  },\n'
    replace['$$INITBODY$$'] = initbody

    ocf.write(replacetext(
"""// Definitions for $$TYPE$$
const $$TYPE$$_DEF
glb_$$type$$defs[NUM_$$TYPE$$S] =
{
$$INITBODY$$
};
""", replace))

