/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ApiGen.h"
#include "EntryPoint.h"
#include <stdio.h>
#include <stdlib.h>
#include "strUtils.h"
#include <errno.h>
#include <sys/types.h>

/* Define this to 1 to enable support for the 'isLarge' variable flag
 * that instructs the encoder to send large data buffers by a direct
 * write through the pipe (i.e. without copying it into a temporary
 * buffer. This has definite performance benefits when using a QEMU Pipe.
 *
 * Set to 0 otherwise.
 */
#define WITH_LARGE_SUPPORT  1

// Set to 1 to ensure buffers passed to/from EGL/GL are properly aligned.
// This prevents crashes with certain backends (e.g. OSMesa).
#define USE_ALIGNED_BUFFERS 1

EntryPoint * ApiGen::findEntryByName(const std::string & name)
{
    EntryPoint * entry = NULL;

    size_t n = this->size();
    for (size_t i = 0; i < n; i++) {
        if (at(i).name() == name) {
            entry = &(at(i));
            break;
        }
    }
    return entry;
}

void ApiGen::printHeader(FILE *fp) const
{
    fprintf(fp, "// Generated Code - DO NOT EDIT !!\n");
    fprintf(fp, "// generated by 'emugen'\n");
}

int ApiGen::genProcTypes(const std::string &filename, SideType side)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }
    printHeader(fp);

    const char* basename = m_basename.c_str();

    fprintf(fp, "#ifndef __%s_%s_proc_t_h\n", basename, sideString(side));
    fprintf(fp, "#define __%s_%s_proc_t_h\n", basename, sideString(side));
    fprintf(fp, "\n\n");
    fprintf(fp, "\n#include \"%s_types.h\"\n",basename);
    fprintf(fp, "#ifndef %s_APIENTRY\n",basename);
    fprintf(fp, "#define %s_APIENTRY \n",basename);
    fprintf(fp, "#endif\n");


    for (size_t i = 0; i < size(); i++) {
        EntryPoint *e = &at(i);

        fprintf(fp, "typedef ");
        e->retval().printType(fp);
        fprintf(fp, " (%s_APIENTRY *%s_%s_proc_t) (", basename, e->name().c_str(), sideString(side));
        if (side == CLIENT_SIDE) { fprintf(fp, "void * ctx"); }
        if (e->customDecoder() && side == SERVER_SIDE) { fprintf(fp, "void *ctx"); }

        VarsArray & evars = e->vars();
        size_t n = evars.size();

        for (size_t j = 0; j < n; j++) {
            if (!evars[j].isVoid()) {
                if (j != 0 || side == CLIENT_SIDE || (side == SERVER_SIDE && e->customDecoder())) fprintf(fp, ", ");
                evars[j].printType(fp);
            }
        }
        fprintf(fp, ");\n");
    }
    fprintf(fp, "\n\n#endif\n");
    return 0;
}

int ApiGen::genFuncTable(const std::string &filename, SideType side)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }
    printHeader(fp);

    fprintf(fp, "#ifndef __%s_%s_ftable_t_h\n", m_basename.c_str(), sideString(side));
    fprintf(fp, "#define __%s_%s_ftable_t_h\n", m_basename.c_str(), sideString(side));
    fprintf(fp, "\n\n");
    fprintf(fp, "static const struct _%s_funcs_by_name {\n", m_basename.c_str());
    fprintf(fp,
            "\tconst char *name;\n" \
            "\tvoid *proc;\n" \
            "} %s_funcs_by_name[] = {\n", m_basename.c_str());


    for (size_t i = 0; i < size(); i++) {
        EntryPoint *e = &at(i);
        if (e->notApi()) continue;
        fprintf(fp, "\t{\"%s\", (void*)%s},\n", e->name().c_str(), e->name().c_str());
    }
    fprintf(fp, "};\n");
    fprintf(fp, "static const int %s_num_funcs = sizeof(%s_funcs_by_name) / sizeof(struct _%s_funcs_by_name);\n",
            m_basename.c_str(), m_basename.c_str(), m_basename.c_str());
    fprintf(fp, "\n\n#endif\n");
    return 0;
}

int ApiGen::genContext(const std::string & filename, SideType side)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }
    printHeader(fp);

    fprintf(fp, "#ifndef __%s_%s_context_t_h\n", m_basename.c_str(), sideString(side));
    fprintf(fp, "#define __%s_%s_context_t_h\n", m_basename.c_str(), sideString(side));

    fprintf(fp, "\n#include \"%s_%s_proc.h\"\n",
            m_basename.c_str(),
            side == CLIENT_SIDE ? "client" : "server");
    fprintf(fp, "\n#include \"%s_types.h\"\n", m_basename.c_str());

    StringVec & contextHeaders = side == CLIENT_SIDE ? m_clientContextHeaders : m_serverContextHeaders;
    for (size_t i = 0; i < contextHeaders.size(); i++) {
        fprintf(fp, "#include %s\n", contextHeaders[i].c_str());
    }
    fprintf(fp, "\n");

    fprintf(fp, "\nstruct %s_%s_context_t {\n\n",
            m_basename.c_str(), sideString(side));

    // API entry points
    for (size_t i = 0; i < size(); i++) {
        EntryPoint *e = &at(i);
        fprintf(fp, "\t%s_%s_proc_t %s;\n", e->name().c_str(), sideString(side), e->name().c_str());
    }

    // virtual destructor
    fprintf(fp, "\t virtual ~%s_%s_context_t() {}\n", m_basename.c_str(), sideString(side));
    // accessor
    if (side == CLIENT_SIDE || side == WRAPPER_SIDE) {
        fprintf(fp, "\n\ttypedef %s_%s_context_t *CONTEXT_ACCESSOR_TYPE(void);\n",
                m_basename.c_str(), sideString(side));
        fprintf(fp, "\tstatic void setContextAccessor(CONTEXT_ACCESSOR_TYPE *f);\n");
    }

    // init function
    fprintf(fp, "\tint initDispatchByName( void *(*getProc)(const char *name, void *userData), void *userData);\n");

    //client site set error virtual func
    if (side == CLIENT_SIDE) {
        fprintf(fp, "\tvirtual void setError(unsigned int  error){ (void)error; };\n");
        fprintf(fp, "\tvirtual unsigned int getError(){ return 0; };\n");
    }

    fprintf(fp, "};\n");

    fprintf(fp, "\n#endif\n");
    fclose(fp);
    return 0;
}

int ApiGen::genEntryPoints(const std::string & filename, SideType side)
{

    if (side != CLIENT_SIDE && side != WRAPPER_SIDE) {
        fprintf(stderr, "Entry points are only defined for Client and Wrapper components\n");
        return -999;
    }


    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return errno;
    }

    printHeader(fp);
    fprintf(fp, "#include <stdio.h>\n");
    fprintf(fp, "#include <stdlib.h>\n");
    fprintf(fp, "#include \"%s_%s_context.h\"\n", m_basename.c_str(), sideString(side));
    fprintf(fp, "\n");

    fprintf(fp, "#ifndef GL_TRUE\n");
    fprintf(fp, "extern \"C\" {\n");

    for (size_t i = 0; i < size(); i++) {
        fprintf(fp, "\t"); at(i).print(fp, false); fprintf(fp, ";\n");
    }
    fprintf(fp, "};\n\n");
    fprintf(fp, "#endif\n");

    fprintf(fp, "#ifndef GET_CONTEXT\n");
    fprintf(fp, "static %s_%s_context_t::CONTEXT_ACCESSOR_TYPE *getCurrentContext = NULL;\n",
            m_basename.c_str(), sideString(side));

    fprintf(fp,
            "void %s_%s_context_t::setContextAccessor(CONTEXT_ACCESSOR_TYPE *f) { getCurrentContext = f; }\n",
            m_basename.c_str(), sideString(side));
    fprintf(fp, "#define GET_CONTEXT %s_%s_context_t * ctx = getCurrentContext()\n",
                m_basename.c_str(), sideString(side));
    fprintf(fp, "#endif\n\n");


    for (size_t i = 0; i < size(); i++) {
        EntryPoint *e = &at(i);
        e->print(fp);
        fprintf(fp, "{\n");
        fprintf(fp, "\tGET_CONTEXT;\n");

        bool shouldReturn = !e->retval().isVoid();
        bool shouldCallWithContext = (side == CLIENT_SIDE);
        //param check
        if (shouldCallWithContext) {
            for (size_t j=0; j<e->vars().size(); j++) {
                if (e->vars()[j].paramCheckExpression() != "")
                    fprintf(fp, "\t%s\n", e->vars()[j].paramCheckExpression().c_str());
            }
        }
        fprintf(fp, "\t%sctx->%s(%s",
                shouldReturn ? "return " : "",
                e->name().c_str(),
                shouldCallWithContext ? "ctx" : "");
        size_t nvars = e->vars().size();

        for (size_t j = 0; j < nvars; j++) {
            if (!e->vars()[j].isVoid()) {
                fprintf(fp, "%s %s",
                        j != 0 || shouldCallWithContext ? "," : "",
                        e->vars()[j].name().c_str());
            }
        }
        fprintf(fp, ");\n");
        fprintf(fp, "}\n\n");
    }
    fclose(fp);
    return 0;
}


int ApiGen::genOpcodes(const std::string &filename)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return errno;
    }

    printHeader(fp);
    fprintf(fp, "#ifndef __GUARD_%s_opcodes_h_\n", m_basename.c_str());
    fprintf(fp, "#define __GUARD_%s_opcodes_h_\n\n", m_basename.c_str());
    for (size_t i = 0; i < size(); i++) {
        fprintf(fp, "#define OP_%s \t\t\t\t\t%u\n", at(i).name().c_str(), (unsigned int)i + m_baseOpcode);
    }
    fprintf(fp, "#define OP_last \t\t\t\t\t%u\n", (unsigned int)size() + m_baseOpcode);
    fprintf(fp,"\n\n#endif\n");
    fclose(fp);
    return 0;

}
int ApiGen::genAttributesTemplate(const std::string &filename )
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }

    for (size_t i = 0; i < size(); i++) {
        if (at(i).hasPointers()) {
            fprintf(fp, "#");
            at(i).print(fp);
            fprintf(fp, "%s\n\n", at(i).name().c_str());
        }
    }
    fclose(fp);
    return 0;
}

int ApiGen::genEncoderHeader(const std::string &filename)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }

    printHeader(fp);
    std::string classname = m_basename + "_encoder_context_t";

    fprintf(fp, "\n#ifndef GUARD_%s\n", classname.c_str());
    fprintf(fp, "#define GUARD_%s\n\n", classname.c_str());

    fprintf(fp, "#include \"IOStream.h\"\n");
    fprintf(fp, "#include \"ChecksumCalculator.h\"\n");
    fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(CLIENT_SIDE));

    for (size_t i = 0; i < m_encoderHeaders.size(); i++) {
        fprintf(fp, "#include %s\n", m_encoderHeaders[i].c_str());
    }
    fprintf(fp, "\n");

    fprintf(fp, "struct %s : public %s_%s_context_t {\n\n",
            classname.c_str(), m_basename.c_str(), sideString(CLIENT_SIDE));
    fprintf(fp, "\tIOStream *m_stream;\n");
    fprintf(fp, "\tChecksumCalculator *m_checksumCalculator;\n\n");

    fprintf(fp, "\t%s(IOStream *stream, ChecksumCalculator *checksumCalculator);\n", classname.c_str());
    fprintf(fp, "};\n\n");

    fprintf(fp, "#endif  // GUARD_%s", classname.c_str());

    fclose(fp);
    return 0;
}

// Format the byte length expression for a given variable into a user-provided buffer
// If the variable type is not a pointer, this is simply its size as a decimal constant
// If the variable is a pointer, this will be an expression provided by the .attrib file
// through the 'len' attribute.
//
// Returns 1 if the variable is a pointer, 0 otherwise
//
static int getVarEncodingSizeExpression(Var&  var, EntryPoint* e, char* buff, size_t bufflen)
{
    int ret = 0;
    if (!var.isPointer()) {
        snprintf(buff, bufflen, "%u", (unsigned int) var.type()->bytes());
    } else {
        ret = 1;
        const char* lenExpr = var.lenExpression().c_str();
        const char* varname = var.name().c_str();
        if (e != NULL && lenExpr[0] == '\0') {
            fprintf(stderr, "%s: data len is undefined for '%s'\n",
                    e->name().c_str(), varname);
        }
        if (var.nullAllowed()) {
            snprintf(buff, bufflen, "((%s != NULL) ? %s : 0)", varname, lenExpr);
        } else {
            snprintf(buff, bufflen, "%s", lenExpr);
        }
    }
    return ret;
}

static int writeVarEncodingSize(Var& var, FILE* fp)
{
    int ret = 0;
    if (!var.isPointer()) {
        fprintf(fp, "%u", (unsigned int) var.type()->bytes());
    } else {
        ret = 1;
        fprintf(fp, "__size_%s", var.name().c_str());
    }
    return ret;
}



static void writeVarEncodingExpression(Var& var, FILE* fp)
{
    const char* varname = var.name().c_str();

    if (var.isPointer()) {
        // encode a pointer header
        fprintf(fp, "\t*(unsigned int *)(ptr) = __size_%s; ptr += 4;\n", varname);

        Var::PointerDir dir = var.pointerDir();
        if (dir == Var::POINTER_INOUT || dir == Var::POINTER_IN) {
            if (var.nullAllowed()) {
                fprintf(fp, "\tif (%s != NULL) ", varname);
            } else {
                fprintf(fp, "\t");
            }

            if (var.packExpression().size() != 0) {
                fprintf(fp, "%s;", var.packExpression().c_str());
            } else {
                fprintf(fp, "memcpy(ptr, %s, __size_%s);",
                        varname, varname);
            }

            fprintf(fp, "ptr += __size_%s;\n", varname);
        }
    } else {
        // encode a non pointer variable
        if (!var.isVoid()) {
            fprintf(fp, "\t\tmemcpy(ptr, &%s, %u); ptr += %u;\n",
                    varname,
                    (unsigned) var.type()->bytes(),
                    (unsigned) var.type()->bytes());
        }
    }
}

#if WITH_LARGE_SUPPORT
static void writeVarLargeEncodingExpression(Var& var, FILE* fp)
{
    const char* varname = var.name().c_str();

    fprintf(fp, "\tstream->writeFully(&__size_%s,4);\n", varname);
    fprintf(fp, "\tif (useChecksum) checksumCalculator->addBuffer(&__size_%s,4);\n", varname);
    if (var.nullAllowed()) {
        fprintf(fp, "\tif (%s != NULL) {\n", varname);
    }
    if (var.writeExpression() != "") {
        fprintf(fp, "%s", var.writeExpression().c_str());
    } else {
        fprintf(fp, "\t\tstream->writeFully(%s, __size_%s);\n", varname, varname);
        fprintf(fp, "\t\tif (useChecksum) checksumCalculator->addBuffer(%s, __size_%s);\n", varname, varname);
    }
    if (var.nullAllowed()) fprintf(fp, "\t}\n");
}
#endif /* WITH_LARGE_SUPPORT */

static void writeEncodingChecksumValidatorOnReturn(const char* funcName, FILE* fp) {
    fprintf(fp, "\tif (useChecksum) {\n"
                "\t\tstd::unique_ptr<unsigned char[]> checksumBuf(new unsigned char[checksumSize]);\n"
                "\t\tstream->readback(checksumBuf.get(), checksumSize);\n"
                "\t\tif (!checksumCalculator->validate(checksumBuf.get(), checksumSize)) {\n"
                "\t\t\tALOGE(\"%s: GL communication error, please report this issue to b.android.com.\\n\");\n"
                "\t\t\tabort();\n"
                "\t\t}\n"
                "\t}\n",
            funcName
    );
}

int ApiGen::genEncoderImpl(const std::string &filename)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }

    printHeader(fp);
    fprintf(fp, "\n\n");
    fprintf(fp, "#include <memory>\n");
    fprintf(fp, "#include <string.h>\n");
    fprintf(fp, "#include \"%s_opcodes.h\"\n\n", m_basename.c_str());
    fprintf(fp, "#include \"%s_enc.h\"\n\n\n", m_basename.c_str());
    fprintf(fp, "#include <stdio.h>\n\n");
    fprintf(fp, "namespace {\n\n");

    // unsupport printout
    fprintf(fp,
            "void enc_unsupported()\n"
            "{\n"
            "\tALOGE(\"Function is unsupported\\n\");\n"
            "}\n\n");

    // entry points;
    std::string classname = m_basename + "_encoder_context_t";

    size_t n = size();
    for (size_t i = 0; i < n; i++) {
        EntryPoint *e = &at(i);

        if (e->unsupported()) continue;


        e->print(fp, true, "_enc", /* classname + "::" */"", "void *self");
        fprintf(fp, "{\n");

//      fprintf(fp, "\n\tDBG(\">>>> %s\\n\");\n", e->name().c_str());
        fprintf(fp, "\n\t%s *ctx = (%s *)self;\n",
                classname.c_str(),
                classname.c_str());
        fprintf(fp, "\tIOStream *stream = ctx->m_stream;\n"
                    "\tChecksumCalculator *checksumCalculator = ctx->m_checksumCalculator;\n"
                    "\tbool useChecksum = checksumCalculator->getVersion() > 0;\n\n");
        VarsArray & evars = e->vars();
        size_t  maxvars = evars.size();
        size_t  j;

        char    buff[256];

        // Define the __size_XXX variables that contain the size of data
        // associated with pointers.
        for (j = 0; j < maxvars; j++) {
            Var& var = evars[j];

            if (!var.isPointer())
                continue;

            const char* varname = var.name().c_str();
            fprintf(fp, "\tconst unsigned int __size_%s = ", varname);

            getVarEncodingSizeExpression(var, e, buff, sizeof(buff));
            fprintf(fp, "%s;\n", buff);
        }

        bool hasLargeFields = false;
#if WITH_LARGE_SUPPORT
        // We need to take care of 'isLarge' variable in a special way
        // Anything before an isLarge variable can be packed into a single
        // buffer, which is then commited. Each isLarge variable is a pointer
        // to data that can be written to directly through the pipe, which
        // will be instant when using a QEMU pipe

        size_t  nvars   = 0;
        size_t  npointers = 0;

        // First, compute the total size, 8 bytes for the opcode + payload size (without checksum)
        fprintf(fp, "\t unsigned char *ptr;\n");
        fprintf(fp, "\t unsigned char *buf;\n");
        fprintf(fp, "\t const size_t sizeWithoutChecksum = 8");

        for (j = 0; j < maxvars; j++) {
            fprintf(fp, " + ");
            npointers += writeVarEncodingSize(evars[j], fp);
        }
        if (npointers > 0) {
            fprintf(fp, " + %zu*4", npointers);
        }
        fprintf(fp, ";\n");

        // Then, size of the checksum string
        fprintf(fp, "\t const size_t checksumSize = checksumCalculator->checksumByteSize();\n");

        // And, size of the whole thing
        fprintf(fp, "\t const size_t totalSize = sizeWithoutChecksum + checksumSize;\n");

        // We need to divide the packet into fragments. Each fragment contains
        // either copied arguments to a temporary buffer, or direct writes for
        // large variables.
        //
        // The first fragment must also contain the opcode+payload_size+checksum_size
        //
        nvars = 0;
        while (nvars < maxvars || maxvars == 0) {

            // Skip over non-large fields
            for (j = nvars; j < maxvars; j++) {
                if (evars[j].isLarge())
                    break;
            }

            // Write a fragment if needed.
            if (nvars == 0 || j > nvars) {
                const char* plus = "";

                if (nvars == 0 && j == maxvars) {
                    // Simple shortcut for the common case where we don't have large variables;
                    fprintf(fp, "\tbuf = stream->alloc(totalSize);\n");

                } else {
                    hasLargeFields = true;
                    // allocate buffer from the stream until the first large variable
                    fprintf(fp, "\tbuf = stream->alloc(");
                    plus = "";

                    if (nvars == 0) {
                        fprintf(fp,"8"); plus = " + ";
                    }
                    if (j > nvars) {
                        npointers = 0;
                        for (j = nvars; j < maxvars && !evars[j].isLarge(); j++) {
                            fprintf(fp, "%s", plus); plus = " + ";
                            npointers += writeVarEncodingSize(evars[j], fp);
                        }
                        if (npointers > 0) {
                            fprintf(fp, "%s%zu*4", plus, npointers); plus = " + ";
                        }
                    }
                    fprintf(fp,");\n");
                }
                fprintf(fp, "\tptr = buf;\n");

                // encode packet header if needed.
                if (nvars == 0) {
                    fprintf(fp, "\tint tmp = OP_%s;memcpy(ptr, &tmp, 4); ptr += 4;\n",  e->name().c_str());
                    fprintf(fp, "\tmemcpy(ptr, &totalSize, 4);  ptr += 4;\n\n");
                }

                if (maxvars == 0) {
                    fprintf(fp, "\n\tif (useChecksum) checksumCalculator->addBuffer(buf, ptr-buf);\n");
                    break;
                }

                // encode non-large fields in this fragment
                for (j = nvars; j < maxvars && !evars[j].isLarge(); j++) {
                    writeVarEncodingExpression(evars[j],fp);
                }

                fprintf(fp, "\n\tif (useChecksum) checksumCalculator->addBuffer(buf, ptr-buf);\n");
                // Ensure the fragment is commited if it is followed by a large variable
                if (j < maxvars) {
                    fprintf(fp, "\tstream->flush();\n");
                }
            }

            // If we have one or more large variables, write them directly.
            // As size + data
            for ( ; j < maxvars && evars[j].isLarge(); j++) {
                writeVarLargeEncodingExpression(evars[j], fp);
            }

            nvars = j;
        }

#else /* !WITH_LARGE_SUPPORT */
        size_t nvars = evars.size();
        size_t npointers = 0;
        fprintf(fp, "\t const size_t sizeWithoutChecksum = 8");
        for (size_t j = 0; j < nvars; j++) {
            npointers += getVarEncodingSizeExpression(evars[j],e,buff,sizeof(buff));
            fprintf(fp, " + %s", buff);
        }
        fprintf(fp, " + %u * 4;\n", (unsigned int) npointers);
        // Size of checksum
        fprintf(fp, "\t const size_t checksumSize = checksumCalculator->checksumByteSize();\n");
        // Size of the whole thing
        fprintf(fp, "\t const size_t totalSize = sizeWithoutChecksum + checksumSize;\n");

        // allocate buffer from the stream;
        fprintf(fp, "\t unsigned char *ptr = stream->alloc(sizeWithoutChecksum);\n\n");

        // encode into the stream;
        fprintf(fp, "\tint tmp = OP_%s; memcpy(ptr, &tmp, 4); ptr += 4;\n",  e->name().c_str());
        fprintf(fp, "\tmemcpy(ptr, &sizeWithoutChecksum, 4);  ptr += 4;\n\n");

        // out variables
        for (size_t j = 0; j < nvars; j++) {
            writeVarEncodingExpression(evars[j], fp);
        }
#endif /* !WITH_LARGE_SUPPORT */

        // checksum
        if (hasLargeFields) {
            fprintf(fp, "\tbuf = stream->alloc(checksumSize);\n");
            fprintf(fp, "\tif (useChecksum) checksumCalculator->writeChecksum(buf, checksumSize);\n\n");
        } else {
            fprintf(fp, "\tif (useChecksum) checksumCalculator->writeChecksum(ptr, checksumSize); ptr += checksumSize;\n\n");
        }

        // in variables;
        bool hasReadbackChecksum = false;
        for (size_t j = 0; j < nvars; j++) {
            if (evars[j].isPointer()) {
                Var::PointerDir dir = evars[j].pointerDir();
                if (dir == Var::POINTER_INOUT || dir == Var::POINTER_OUT) {
                    const char* varname = evars[j].name().c_str();
                    const char* indent = "\t";
                    if (evars[j].nullAllowed()) {
                        fprintf(fp, "\tif (%s != NULL) {\n",varname);
                        indent = "\t\t";
                    }
                    fprintf(fp, "%sstream->readback(%s, __size_%s);\n",
                            indent, varname, varname);
                    fprintf(fp, "%sif (useChecksum) checksumCalculator->addBuffer(%s, __size_%s);\n",
                            indent, varname, varname);
                    if (evars[j].nullAllowed()) {
                        fprintf(fp, "\t}\n");
                    }
                    hasReadbackChecksum = true;
                }
            }
        }
//XXX       fprintf(fp, "\n\tDBG(\"<<<< %s\\n\");\n", e->name().c_str());

        // todo - return value for pointers
        if (e->retval().isPointer()) {
            fprintf(stderr, "WARNING: %s : return value of pointer is unsupported\n",
                    e->name().c_str());
            if (e->flushOnEncode()) {
                fprintf(fp, "\tstream->flush();\n");
            }
            fprintf(fp, "\t return NULL;\n");
        } else if (e->retval().type()->name() != "void") {
            fprintf(fp, "\n\t%s retval;\n", e->retval().type()->name().c_str());
            fprintf(fp, "\tstream->readback(&retval, %u);\n",(unsigned) e->retval().type()->bytes());
            fprintf(fp, "\tif (useChecksum) checksumCalculator->addBuffer(&retval, %u);\n",
                    (unsigned) e->retval().type()->bytes());
            writeEncodingChecksumValidatorOnReturn(e->name().c_str(), fp);
            fprintf(fp, "\treturn retval;\n");
        } else {
            if (e->flushOnEncode()) fprintf(fp, "\tstream->flush();\n");
            if (hasReadbackChecksum) writeEncodingChecksumValidatorOnReturn(e->name().c_str(), fp);
        }
        fprintf(fp, "}\n\n");
    }

    fprintf(fp, "}  // namespace\n\n");

    // constructor
    fprintf(fp, "%s::%s(IOStream *stream, ChecksumCalculator *checksumCalculator)\n{\n", classname.c_str(), classname.c_str());
    fprintf(fp, "\tm_stream = stream;\n");
    fprintf(fp, "\tm_checksumCalculator = checksumCalculator;\n\n");

    for (size_t i = 0; i < n; i++) {
        EntryPoint *e = &at(i);
        if (e->unsupported()) {
            fprintf(fp, 
                    "\tthis->%s = (%s_%s_proc_t) &enc_unsupported;\n",
                    e->name().c_str(),
                    e->name().c_str(),
                    sideString(CLIENT_SIDE));
        } else {
            fprintf(fp,
                    "\tthis->%s = &%s_enc;\n",
                    e->name().c_str(),
                    e->name().c_str());
        }
    }
    fprintf(fp, "}\n\n");

    fclose(fp);
    return 0;
}


int ApiGen::genDecoderHeader(const std::string &filename)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }

    printHeader(fp);
    std::string classname = m_basename + "_decoder_context_t";

    fprintf(fp, "\n#ifndef GUARD_%s\n", classname.c_str());
    fprintf(fp, "#define GUARD_%s\n\n", classname.c_str());

    fprintf(fp, "#include \"IOStream.h\" \n");
    fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(SERVER_SIDE));
    if (strcmp(classname.c_str(), "gles2_decoder_context_t") == 0) {
        fprintf(fp, "\n#include <map>\n");
        fprintf(fp, "\n#include <mutex>\n");
    }
    fprintf(fp, "\n#include \"emugl/common/logging.h\"\n");

    for (size_t i = 0; i < m_decoderHeaders.size(); i++) {
        fprintf(fp, "#include %s\n", m_decoderHeaders[i].c_str());
    }
    fprintf(fp, "\n");

    fprintf(fp, "struct %s : public %s_%s_context_t {\n\n",
            classname.c_str(), m_basename.c_str(), sideString(SERVER_SIDE));
    fprintf(fp, "\tsize_t decode(void *buf, size_t bufsize, IOStream *stream);\n");
    if (strcmp(classname.c_str(), "gles2_decoder_context_t") == 0){ 
	fprintf(fp, 
			"\tvoid freeShader(); \n\
			\tvoid freeProgram(); \n\
			\tstd::map<GLuint, GLuint> m_programs; \n\
			\tstd::map<GLuint, GLuint> m_shaders; \n\
			\tstd::mutex m_lock; \n\
		");


    }
    fprintf(fp, "\n};\n\n");
    fprintf(fp, "#endif  // GUARD_%s\n", classname.c_str());

    fclose(fp);
    return 0;
}

int ApiGen::genContextImpl(const std::string &filename, SideType side)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }
    printHeader(fp);

    std::string classname = m_basename + "_" + sideString(side) + "_context_t";
    size_t n = size();
    fprintf(fp, "\n\n#include <string.h>\n");
    fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(side));
    fprintf(fp, "#include <stdio.h>\n\n");

    fprintf(fp, "int %s::initDispatchByName(void *(*getProc)(const char *, void *userData), void *userData)\n{\n", classname.c_str());
    for (size_t i = 0; i < n; i++) {
        EntryPoint *e = &at(i);
        fprintf(fp, "\t%s = (%s_%s_proc_t) getProc(\"%s\", userData);\n",
                e->name().c_str(),
                e->name().c_str(),
                sideString(side),
                e->name().c_str());
    }
    fprintf(fp, "\treturn 0;\n");
    fprintf(fp, "}\n\n");
    fclose(fp);
    return 0;
}

int ApiGen::genDecoderImpl(const std::string &filename)
{
    FILE *fp = fopen(filename.c_str(), "wt");
    if (fp == NULL) {
        perror(filename.c_str());
        return -1;
    }

    printHeader(fp);

    std::string classname = m_basename + "_decoder_context_t";

    size_t n = size();

    fprintf(fp, "\n\n#include <string.h>\n");
    fprintf(fp, "#include \"%s_opcodes.h\"\n\n", m_basename.c_str());
    fprintf(fp, "#include \"%s_dec.h\"\n\n\n", m_basename.c_str());
    fprintf(fp, "#include \"ProtocolUtils.h\"\n\n");
    fprintf(fp, "#include \"ChecksumCalculatorThreadInfo.h\"\n\n");
    fprintf(fp, "#include <stdio.h>\n\n");
    fprintf(fp, "typedef unsigned int tsize_t; // Target \"size_t\", which is 32-bit for now. It may or may not be the same as host's size_t when emugen is compiled.\n\n");

    // helper macros
    fprintf(fp, "#  define DEBUG(...) do { if (emugl_cxt_logger) { emugl_cxt_logger(LogLevel::TRACE, __VA_ARGS__); } } while(0)\n\n");

    fprintf(fp,
            "#ifdef CHECK_GLERROR\n"
            "#  define SET_LASTCALL(name)  sprintf(lastCall, #name)\n"
            "#else\n"
            "#  define SET_LASTCALL(name)  ((void)0)\n"
            "#endif\n\n");

    // helper templates
    fprintf(fp, "using namespace emugl;\n\n");

    // glsl shader/program free;
    if (strcmp(classname.c_str(), "gles2_decoder_context_t") == 0) {
        fprintf(fp, "void %s::freeShader(){\n", classname.c_str());
	fprintf(fp, 
		"                       \n\
\tauto it = m_shaders.begin();\n\
\tm_lock.lock();\n\
\twhile(it != m_shaders.end()) \n\
\t{\n\
\t\tthis->glDeleteShader(it->first);\n\
\t\tit++;\n\
\t}\n\
\tm_lock.unlock();\n\
}\n\n");

        fprintf(fp, "void %s::freeProgram(){\n", classname.c_str());
	fprintf(fp, 
		"		\n\
\tauto it = m_programs.begin(); \n\
\tm_lock.lock();\n\
\twhile(it != m_programs.end()) \n\
\t{\n\
\t\tthis->glDeleteProgram(it->first);\n\
\t\tit++;\n\
\t}\n\
\tm_lock.unlock();\n\
}\n\n");
    }

    // decoder switch;
    fprintf(fp, "size_t %s::decode(void *buf, size_t len, IOStream *stream)\n{\n", classname.c_str());
    fprintf(fp,
            "                           \n\
\tsize_t pos = 0;\n\
\tif (len < 8) return pos; \n\
\tunsigned char *ptr = (unsigned char *)buf;\n\
\tbool unknownOpcode = false;  \n\
#ifdef CHECK_GL_ERROR \n\
\tchar lastCall[256] = {0}; \n\
#endif \n\
\twhile ((len - pos >= 8) && !unknownOpcode) {   \n\
\t\tuint32_t opcode = *(uint32_t *)ptr;   \n\
\t\tsize_t packetLen = *(uint32_t *)(ptr + 4);\n\
\t\tif (len - pos < packetLen)  return pos; \n\
\t\tbool useChecksum = ChecksumCalculatorThreadInfo::getVersion() > 0;\n\
\t\tsize_t checksumSize = 0;\n\
\t\tif (useChecksum) {\n\
\t\t\tchecksumSize = ChecksumCalculatorThreadInfo::checksumByteSize();\n\
\t\t}\n\
\t\tswitch(opcode) {\n");

    for (size_t f = 0; f < n; f++) {
        enum Pass_t {
            PASS_FIRST = 0,
            PASS_VariableDeclarations = PASS_FIRST,
            PASS_Protocol,
            PASS_TmpBuffAlloc,
            PASS_MemAlloc,
            PASS_DebugPrint,
            PASS_FunctionCall,
            PASS_FlushOutput,
            PASS_Epilog,
            PASS_LAST };
        EntryPoint *e = &at(f);

        // construct a printout string;
        std::string printString = "";
        for (size_t i = 0; i < e->vars().size(); i++) {
            Var *v = &e->vars()[i];
            if (!v->isVoid())  printString += (v->isPointer() ? "%p(%u)" : v->type()->printFormat()) + " ";
        }
        printString += "";
        // TODO - add for return value;

        fprintf(fp, "\t\tcase OP_%s: {\n", e->name().c_str());

        bool totalTmpBuffExist = false;
        std::string totalTmpBuffOffset = "0";
        std::string *tmpBufOffset = new std::string[e->vars().size()];

        // construct retval type string
        std::string retvalType;
        if (!e->retval().isVoid()) {
            retvalType = e->retval().type()->name();
        }

        for (int pass = PASS_FIRST; pass < PASS_LAST; pass++) {
            if (pass == PASS_FunctionCall &&
                !e->retval().isVoid() &&
                !e->retval().isPointer()) {
                fprintf(fp, "\t\t\t*(%s *)(&tmpBuf[%s]) = ", retvalType.c_str(),
                        totalTmpBuffOffset.c_str());
            }


            if (pass == PASS_FunctionCall) {
                fprintf(fp, "\t\t\tthis->%s(", e->name().c_str());
                if (e->customDecoder()) {
                    fprintf(fp, "this"); // add a context to the call
                }
            } else if (pass == PASS_DebugPrint) {
                fprintf(fp,
                        "\t\t\tDEBUG(\"%s(%%p): %s(%s)\", stream",
                        m_basename.c_str(),
                        e->name().c_str(),
                        printString.c_str());
                if (e->vars().size() > 0 && !e->vars()[0].isVoid()) {
                    fprintf(fp, ",");
                }
            }

            std::string varoffset = "8"; // skip the header
            VarsArray & evars = e->vars();
            // allocate memory for out pointers;
            for (size_t j = 0; j < evars.size(); j++) {
                Var *v = & evars[j];
                if (v->isVoid()) {
                    continue;
                }
                const char* var_name = v->name().c_str();
                const char* var_type_name = v->type()->name().c_str();
                const unsigned var_type_bytes = v->type()->bytes();

                if ((pass == PASS_FunctionCall) &&
                    (j != 0 || e->customDecoder())) {
                    fprintf(fp, ", ");
                }
                if (pass == PASS_DebugPrint && j != 0) {
                    fprintf(fp, ", ");
                }

                if (!v->isPointer()) {
                    if (pass == PASS_VariableDeclarations) {
                        fprintf(fp,
                                "\t\t\t%s var_%s = Unpack<%s,uint%u_t>(ptr + %s);\n",
                                var_type_name,
                                var_name,
                                var_type_name,
                                var_type_bytes * 8U,
                                varoffset.c_str());
                    }

                    if (pass == PASS_FunctionCall ||
                        pass == PASS_DebugPrint) {
                        fprintf(fp, "var_%s", var_name);
                    }
                    varoffset += " + " + toString(var_type_bytes);
                    continue;
                }

                if (pass == PASS_VariableDeclarations) {
                    fprintf(fp,
                            "\t\t\tuint32_t size_%s __attribute__((unused)) = Unpack<uint32_t,uint32_t>(ptr + %s);\n",
                            var_name,
                            varoffset.c_str());
                }

                if (v->pointerDir() == Var::POINTER_IN ||
                    v->pointerDir() == Var::POINTER_INOUT) {
                    if (pass == PASS_VariableDeclarations) {
#if USE_ALIGNED_BUFFERS
                        fprintf(fp,
                                "\t\t\tInputBuffer inptr_%s(ptr + %s + 4, size_%s);\n",
                                var_name,
                                varoffset.c_str(),
                                var_name);
                    }
                    if (pass == PASS_FunctionCall) {
                        if (v->nullAllowed()) {
                            fprintf(fp,
                                    "size_%s == 0 ? NULL : (%s)(inptr_%s.get())",
                                    var_name,
                                    var_type_name,
                                    var_name);
                        } else {
                            fprintf(fp,
                                    "(%s)(inptr_%s.get())",
                                    var_type_name,
                                    var_name);
                        }
                    } else if (pass == PASS_DebugPrint) {
                        fprintf(fp,
                                "(%s)(inptr_%s.get()), size_%s",
                                var_type_name,
                                var_name,
                                var_name);
                    }
#else  // !USE_ALIGNED_BUFFERS
                        fprintf(fp,
                                "unsigned char *inptr_%s = (ptr + %s + 4);\n",
                                var_name,
                                varoffset.c_str());
                    }
                    if (pass == PASS_FunctionCall) {
                        if (v->nullAllowed()) {
                            fprintf(fp,
                                    "size_%s == 0 ? NULL : (%s)(inptr_%s)",
                                    var_name,
                                    var_type_name,
                                    var_name);
                        } else {
                            fprintf(fp,
                                    "(%s)(inptr_%s)",
                                    var_type_name,
                                    var_name);
                        }
                    } else if (pass == PASS_DebugPrint) {
                        fprintf(fp,
                                "(%s)(inptr_%s), size_%s",
                                var_type_name,
                                var_name,
                                var_name);
                    }
#endif  // !USE_ALIGNED_BUFFERS
                    varoffset += " + 4 + size_";
                    varoffset += var_name;
                } else { // out pointer;
                    if (pass == PASS_TmpBuffAlloc) {
                        if (!totalTmpBuffExist) {
                            fprintf(fp,
                                    "\t\t\tsize_t totalTmpSize = size_%s;\n",
                                    var_name);
                        } else {
                            fprintf(fp,
                                    "\t\t\ttotalTmpSize += size_%s;\n",
                                    var_name);
                        }
                        tmpBufOffset[j] = totalTmpBuffOffset;
                        totalTmpBuffOffset += " + size_";
                        totalTmpBuffOffset += var_name;
                        totalTmpBuffExist = true;
                    } else if (pass == PASS_MemAlloc) {
#if USE_ALIGNED_BUFFERS
                        fprintf(fp,
                                "\t\t\tOutputBuffer outptr_%s(&tmpBuf[%s], size_%s);\n",
                                var_name,
                                tmpBufOffset[j].c_str(),
                                var_name);
                    } else if (pass == PASS_FunctionCall) {
                        if (v->nullAllowed()) {
                            fprintf(fp,
                                    "size_%s == 0 ? NULL : (%s)(outptr_%s.get())",
                                    var_name,
                                    var_type_name,
                                    var_name);
                        } else {
                            fprintf(fp,
                                    "(%s)(outptr_%s.get())",
                                    var_type_name,
                                    var_name);
                        }
                    } else if (pass == PASS_DebugPrint) {
                        fprintf(fp,
                                "(%s)(outptr_%s.get()), size_%s",
                                var_type_name,
                                var_name,
                                var_name);
                    }
                    if (pass == PASS_FlushOutput) {
                        fprintf(fp,
                                "\t\t\toutptr_%s.flush();\n",
                                var_name);
                    }
#else  // !USE_ALIGNED_BUFFERS
                        fprintf(fp,
                                "\t\t\tunsigned char *outptr_%s = &tmpBuf[%s];\n",
                                var_name,
                                tmpBufOffset[j].c_str());
                        fprintf(fp,
                                "\t\t\tmemset(outptr_%s, 0, %s);\n",
                                var_name,
                                toString(v->type()->bytes()).c_str());
                    } else if (pass == PASS_FunctionCall) {
                        if (v->nullAllowed()) {
                            fprintf(fp,
                                    "size_%s == 0 ? NULL : (%s)(outptr_%s)",
                                    var_name,
                                    var_type_name,
                                    var_name);
                        } else {
                            fprintf(fp,
                                    "(%s)(outptr_%s)",
                                    var_type_name,
                                    var_name);
                        }
                    } else if (pass == PASS_DebugPrint) {
                        fprintf(fp,
                                "(%s)(outptr_%s), size_%s",
                                var_type_name,
                                var_name,
                                varoffset.c_str());
                    }
#endif  // !USE_ALIGNED_BUFFERS
                    varoffset += " + 4";
                }
            }

            if (pass == PASS_Protocol) {
                fprintf(fp,
                        "\t\t\tif (useChecksum) {\n"
                        "\t\t\t\tChecksumCalculatorThreadInfo::validOrDie(ptr, %s, "
                        "ptr + %s, checksumSize, "
                        "\n\t\t\t\t\t\"%s::decode,"
                        " OP_%s: GL checksumCalculator failure\\n\");\n"
                        "\t\t\t}\n",
                        varoffset.c_str(),
                        varoffset.c_str(),
                        varoffset.c_str(),
                        classname.c_str()
                        );

                varoffset += " + 4";
            }

            if (pass == PASS_FunctionCall ||
                pass == PASS_DebugPrint) {
                fprintf(fp, ");\n");
            }

            if (pass == PASS_TmpBuffAlloc) {
                if (!e->retval().isVoid() && !e->retval().isPointer()) {
                    if (!totalTmpBuffExist)
                        fprintf(fp,
                                "\t\t\tsize_t totalTmpSize = sizeof(%s);\n",
                                retvalType.c_str());
                    else
                        fprintf(fp,
                                "\t\t\ttotalTmpSize += sizeof(%s);\n",
                                retvalType.c_str());

                    totalTmpBuffExist = true;
                }
                if (totalTmpBuffExist) {
                    fprintf(fp,
                            "\t\t\ttotalTmpSize += checksumSize;\n"
                            "\t\t\tunsigned char *tmpBuf = stream->alloc(totalTmpSize);\n");
                }
            }

            if (pass == PASS_Epilog) {
                // send back out pointers data as well as retval
                if (totalTmpBuffExist) {
                    fprintf(fp,
                            "\t\t\tif (useChecksum) {\n"
                            "\t\t\t\tChecksumCalculatorThreadInfo::writeChecksum("
                            "&tmpBuf[0], totalTmpSize - checksumSize, "
                            "&tmpBuf[totalTmpSize - checksumSize], checksumSize);\n"
                            "\t\t\t}\n"
                            "\t\t\tstream->flush();\n");
                }
            }

        } // pass;
        fprintf(fp, "\t\t\tSET_LASTCALL(\"%s\");\n", e->name().c_str());
	if (strcmp(m_basename.c_str(), "gles2") == 0) {
	    if (strcmp(e->name().c_str(), "glAttachShader") == 0){
		fprintf(fp, "\n\
			\t\t\tm_lock.lock();\n\
			m_shaders.insert({var_shader, 1});\n\
			m_lock.unlock();\n");
	    } else if(strcmp(e->name().c_str(), "glDeleteProgram") == 0){
		fprintf(fp, 
			"\t\t\tm_lock.lock(); \n"
			"\t\t\tauto pro = m_programs.find(var_program); \n"
			"\t\t\tif (pro != m_programs.end()) \n"
			"\t\t\t{ \n"
			"\t\t\t\tm_programs.erase(pro); \n"
			"\t\t\t}\n"
			"\t\t\tm_lock.unlock();\n");
            } else if(strcmp(e->name().c_str(), "glDeleteShader") == 0){
		fprintf(fp, 
			"\t\t\tm_lock.lock(); \n\
			\t\t\tauto shader = m_shaders.find(var_shader); \n\
			\t\t\tif (shader != m_shaders.end()) \n\
			\t\t\t{ \n\
			\t\t\t\tm_shaders.erase(shader); \n\
			\t\t\t} \n\
			\t\t\tm_lock.unlock(); \n");
	    } else if(strcmp(e->name().c_str(), "glLinkProgram") == 0){
		fprintf(fp, "	\n\
			\t\t\tm_lock.lock();\n\
			\t\t\tm_programs.insert({var_program, 1});\n\
			\t\t\tm_lock.unlock();\n");
	}
	}
        fprintf(fp, "\t\t\tbreak;\n");
        fprintf(fp, "\t\t}\n");

        delete [] tmpBufOffset;
    }
    fprintf(fp, "\t\t\tdefault:\n");
    fprintf(fp, "\t\t\t\tunknownOpcode = true;\n");
    fprintf(fp, "\t\t} //switch\n");
    if (strstr(m_basename.c_str(), "gl")) {
        fprintf(fp, "#ifdef CHECK_GL_ERROR\n");
        fprintf(fp, "\tint err = lastCall[0] ? this->glGetError() : GL_NO_ERROR;\n");
        fprintf(fp, "\tif (err) fprintf(stderr, \"%s Error: 0x%%X in %%s\\n\", err, lastCall);\n", m_basename.c_str());
        fprintf(fp, "#endif\n");
    }

    fprintf(fp, "\t\tif (!unknownOpcode) {\n");
    fprintf(fp, "\t\t\tpos += packetLen;\n");
    fprintf(fp, "\t\t\tptr += packetLen;\n");
    fprintf(fp, "\t\t}\n");
    fprintf(fp, "\t} // while\n");
    fprintf(fp, "\treturn pos;\n");
    fprintf(fp, "}\n");

    fclose(fp);
    return 0;
}

int ApiGen::readSpec(const std::string & filename)
{
    FILE *specfp = fopen(filename.c_str(), "rt");
    if (specfp == NULL) {
        return -1;
    }

    char line[1000];
    unsigned int lc = 0;
    while (fgets(line, sizeof(line), specfp) != NULL) {
        lc++;
        EntryPoint ref;
        if (ref.parse(lc, std::string(line))) {
            push_back(ref);
            updateMaxEntryPointsParams(ref.vars().size());
        }
    }
    fclose(specfp);
    return 0;
}

int ApiGen::readAttributes(const std::string & attribFilename)
{
    enum { ST_NAME, ST_ATT } state;

    FILE *fp = fopen(attribFilename.c_str(), "rt");
    if (fp == NULL) {
        perror(attribFilename.c_str());
        return -1;
    }
    char buf[1000];

    state = ST_NAME;
    EntryPoint *currentEntry = NULL;
    size_t lc = 0;
    bool globalAttributes = false;
    while (fgets(buf, sizeof(buf), fp) != NULL) {
        lc++;
        std::string line(buf);
        if (line.size() == 0) continue; // could that happen?

        if (line.at(0) == '#') continue; // comment

        size_t first = line.find_first_not_of(" \t\n");
        if (state == ST_ATT && (first == std::string::npos || first == 0)) state = ST_NAME;

        line = trim(line);
        if (line.size() == 0 || line.at(0) == '#') continue;

        switch(state) {
        case ST_NAME:
            if (line == "GLOBAL") {
                globalAttributes = true;
            } else {
                globalAttributes = false;
                currentEntry = findEntryByName(line);
                if (currentEntry == NULL) {
                    fprintf(stderr, "WARNING: %u: attribute of non existant entry point %s\n", (unsigned int)lc, line.c_str());
                }
            }
            state = ST_ATT;
            break;
        case ST_ATT:
            if (globalAttributes) {
                setGlobalAttribute(line, lc);
            } else  if (currentEntry != NULL) {
                currentEntry->setAttribute(line, lc);
            }
            break;
        }
    }
    return 0;
}


int ApiGen::setGlobalAttribute(const std::string & line, size_t lc)
{
    size_t pos = 0;
    size_t last;
    std::string token = getNextToken(line, pos, &last, WHITESPACE);
    pos = last;

    if (token == "base_opcode") {
        std::string str = getNextToken(line, pos, &last, WHITESPACE);
        if (str.size() == 0) {
            fprintf(stderr, "line %u: missing value for base_opcode\n", (unsigned) lc);
        } else {
            setBaseOpcode(atoi(str.c_str()));
        }
    } else  if (token == "encoder_headers") {
        std::string str = getNextToken(line, pos, &last, WHITESPACE);
        pos = last;
        while (str.size() != 0) {
            encoderHeaders().push_back(str);
            str = getNextToken(line, pos, &last, WHITESPACE);
            pos = last;
        }
    } else if (token == "client_context_headers") {
        std::string str = getNextToken(line, pos, &last, WHITESPACE);
        pos = last;
        while (str.size() != 0) {
            clientContextHeaders().push_back(str);
            str = getNextToken(line, pos, &last, WHITESPACE);
            pos = last;
        }
    } else if (token == "server_context_headers") {
        std::string str = getNextToken(line, pos, &last, WHITESPACE);
        pos = last;
        while (str.size() != 0) {
            serverContextHeaders().push_back(str);
            str = getNextToken(line, pos, &last, WHITESPACE);
            pos = last;
        }
    } else if (token == "decoder_headers") {
        std::string str = getNextToken(line, pos, &last, WHITESPACE);
        pos = last;
        while (str.size() != 0) {
            decoderHeaders().push_back(str);
            str = getNextToken(line, pos, &last, WHITESPACE);
            pos = last;
        }
    }
    else {
        fprintf(stderr, "WARNING: %u : unknown global attribute %s\n", (unsigned int)lc, line.c_str());
    }

    return 0;
}
