#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#define __STDC_FORMAT_MACROS  // Enable integer types
#else
#include <unistd.h> /* _exit */
#endif
#include <inttypes.h>

#include <sys/stat.h>
#include <sys/types.h>

#include "lcmgen.h"

#define INDENT(n) (4 * (n))

#define emit_start(n, ...)                \
    do {                                  \
        fprintf(f, "%*s", INDENT(n), ""); \
        fprintf(f, __VA_ARGS__);          \
    } while (0)
#define emit_continue(...)       \
    do {                         \
        fprintf(f, __VA_ARGS__); \
    } while (0)
#define emit_end(...)            \
    do {                         \
        fprintf(f, __VA_ARGS__); \
        fprintf(f, "\n");        \
    } while (0)
#define emit(n, ...)                      \
    do {                                  \
        fprintf(f, "%*s", INDENT(n), ""); \
        fprintf(f, __VA_ARGS__);          \
        fprintf(f, "\n");                 \
    } while (0)

static inline int imax(int a, int b)
{
    return (a > b) ? a : b;
}

static char *dots_to_underscores(const char *s)
{
    char *p = strdup(s);

    for (char *t = p; *t != 0; t++)
        if (*t == '.')
            *t = '_';

    return p;
}

static char *dots_to_double_colons(const char *s)
{
    // allocate the maximum possible amount of space needed
    char *p = (char *) calloc(1, 2 * strlen(s) + 1);
    char *q = p;

    for (const char *t = s; *t != 0; t++) {
        if (*t == '.') {
            *q = ':';
            q++;
            *q = ':';
        } else
            *q = *t;
        q++;
    }

    return p;
}

static char *dots_to_slashes(const char *s)
{
    char *p = strdup(s);
    for (char *t = p; *t != 0; t++)
        if (*t == '.')
            *t = G_DIR_SEPARATOR;
    return p;
}

static void make_dirs_for_file(const char *path)
{
#ifdef WIN32
    char *dirname = g_path_get_dirname(path);
    g_mkdir_with_parents(dirname, 0755);
    g_free(dirname);
#else
    int len = strlen(path);
    for (int i = 0; i < len; i++) {
        if (path[i] == '/') {
            char *dirpath = (char *) malloc(i + 1);
            strncpy(dirpath, path, i);
            dirpath[i] = 0;

            mkdir(dirpath, 0755);
            free(dirpath);

            i++;  // skip the '/'
        }
    }
#endif
}

static const char *dim_size_prefix(const char *dim_size)
{
    char *eptr = NULL;
    long asdf = strtol(dim_size, &eptr, 0);
    (void) asdf;  // suppress compiler warnings
    if (*eptr == '\0')
        return "";
    else
        return "this->";
}

static int is_dim_size_fixed(const char *dim_size)
{
    char *eptr = NULL;
    long asdf = strtol(dim_size, &eptr, 0);
    (void) asdf;  // suppress compiler warnings
    return (*eptr == '\0');
}

// Some types do not have a 1:1 mapping from lcm types to native C
// storage types. Do not free the string pointers returned by this
// function.
static char *map_type_name(const char *t)
{
    if (!strcmp(t, "boolean"))
        return strdup("int8_t");

    if (!strcmp(t, "string"))
        return strdup("std::string");

    if (!strcmp(t, "byte"))
        return strdup("uint8_t");

    return dots_to_double_colons(t);
}

void setup_cpp_options(getopt_t *gopt)
{
    getopt_add_string(gopt, 0, "cpp-std", "c++98", "C++ standard(c++98, c++11)");
    getopt_add_string(gopt, 0, "cpp-hpath", ".", "Location for .hpp files");
    getopt_add_string(gopt, 0, "cpp-include", "", "Generated #include lines reference this folder");
}

static void emit_auto_generated_warning(FILE *f)
{
    fprintf(f,
            "/** THIS IS AN AUTOMATICALLY GENERATED FILE.  DO NOT MODIFY\n"
            " * BY HAND!!\n"
            " *\n"
            " * Generated by lcm-gen\n"
            " **/\n\n");
}

static void emit_comment(FILE *f, int indent, const char *comment)
{
    if (!comment)
        return;

    gchar **lines = g_strsplit(comment, "\n", 0);
    int num_lines = 0;
    for (num_lines = 0; lines[num_lines]; num_lines++) {
    }

    if (num_lines == 1) {
        emit(indent, "/// %s", lines[0]);
    } else {
        emit(indent, "/**");
        for (int line_ind = 0; lines[line_ind]; line_ind++) {
            if (strlen(lines[line_ind])) {
                emit(indent, " * %s", lines[line_ind]);
            } else {
                emit(indent, " *");
            }
        }
        emit(indent, " */");
    }
    g_strfreev(lines);
}

static void emit_package_namespace_start(lcmgen_t *lcmgen, FILE *f, lcm_struct_t *ls)
{
    // output namespace declaration
    char **namespaces = g_strsplit(ls->structname->lctypename, ".", 0);
    for (int nsind = 0; namespaces[nsind] && namespaces[nsind + 1]; nsind++) {
        emit(0, "namespace %s\n{", namespaces[nsind]);
    }
    g_strfreev(namespaces);
}

static void emit_package_namespace_close(lcmgen_t *lcmgen, FILE *f, lcm_struct_t *ls)
{
    char **namespaces = g_strsplit(ls->structname->lctypename, ".", 0);
    for (int nsind = 0; namespaces[nsind] && namespaces[nsind + 1]; nsind++) {
        emit(0, "}\n");
    }
    g_strfreev(namespaces);
}

/** Emit header file **/
static void emit_header_start(lcmgen_t *lcmgen, FILE *f, lcm_struct_t *ls)
{
    char *tn = ls->structname->lctypename;
    char *sn = ls->structname->shortname;
    char *tn_ = dots_to_underscores(tn);

    emit_auto_generated_warning(f);

    fprintf(f, "#ifndef __%s_hpp__\n", tn_);
    fprintf(f, "#define __%s_hpp__\n", tn_);
    fprintf(f, "\n");
    if (getopt_get_bool(lcmgen->gopt, "use-quotes-for-includes"))
        fprintf(f, "#include \"lcm/lcm_coretypes.h\"\n");
    else
        fprintf(f, "#include <lcm/lcm_coretypes.h>\n");
    fprintf(f, "\n");

    // do we need to #include <vector> and/or <string>?
    int emit_include_vector = 0;
    int emit_include_string = 0;
    for (unsigned int mind = 0; mind < g_ptr_array_size(ls->members); mind++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, mind);
        if (g_ptr_array_size(lm->dimensions) != 0 && !lcm_is_constant_size_array(lm) &&
            !emit_include_vector) {
            emit(0, "#include <vector>");
            emit_include_vector = 1;
        }
        if (!emit_include_string && !strcmp(lm->type->lctypename, "string")) {
            emit(0, "#include <string>");
            emit_include_string = 1;
        }
    }

    // include header files for other LCM types
    for (unsigned int mind = 0; mind < g_ptr_array_size(ls->members); mind++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, mind);

        if (!lcm_is_primitive_type(lm->type->lctypename) &&
            strcmp(lm->type->lctypename, ls->structname->lctypename)) {
            char *other_tn = dots_to_slashes(lm->type->lctypename);
            emit(
                0, "#include \"%s%s%s.hpp\"", getopt_get_string(lcmgen->gopt, "cpp-include"),
                strlen(getopt_get_string(lcmgen->gopt, "cpp-include")) > 0 ? G_DIR_SEPARATOR_S : "",
                other_tn);
            free(other_tn);
        }
    }

    fprintf(f, "\n");
    emit_package_namespace_start(lcmgen, f, ls);

    // define the class
    emit(0, "");
    emit_comment(f, 0, ls->comment);
    emit(0, "class %s", sn);
    emit(0, "{");

    // data members
    if (g_ptr_array_size(ls->members)) {
        emit(1, "public:");
        for (unsigned int mind = 0; mind < g_ptr_array_size(ls->members); mind++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, mind);

            emit_comment(f, 2, lm->comment);
            char *mapped_typename = map_type_name(lm->type->lctypename);
            int ndim = g_ptr_array_size(lm->dimensions);
            if (ndim == 0) {
                emit(2, "%-10s %s;", mapped_typename, lm->membername);
            } else {
                if (lcm_is_constant_size_array(lm)) {
                    emit_start(2, "%-10s %s", mapped_typename, lm->membername);
                    for (unsigned int d = 0; d < ndim; d++) {
                        lcm_dimension_t *ld =
                            (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, d);
                        emit_continue("[%s]", ld->size);
                    }
                    emit_end(";");
                } else {
                    emit_start(2, "");
                    for (unsigned int d = 0; d < ndim; d++)
                        emit_continue("std::vector< ");
                    emit_continue("%s", mapped_typename);
                    for (unsigned int d = 0; d < ndim; d++)
                        emit_continue(" >");
                    emit_end(" %s;", lm->membername);
                }
            }
            free(mapped_typename);
            if (mind < g_ptr_array_size(ls->members) - 1) {
                emit(0, "");
            }
        }
        emit(0, "");
    }

    // constants
    if (g_ptr_array_size(ls->constants) > 0) {
        emit(1, "public:");
        for (unsigned int i = 0; i < g_ptr_array_size(ls->constants); i++) {
            lcm_constant_t *lc = (lcm_constant_t *) g_ptr_array_index(ls->constants, i);
            assert(lcm_is_legal_const_type(lc->lctypename));

            emit_comment(f, 2, lc->comment);
            // For int32_t only, we emit enums instead of static const
            // values because the former can be passed by reference while
            // the latter cannot.
            if (!strcmp(lc->lctypename, "int32_t")) {
                emit(2, "enum { %s = %s };", lc->membername, lc->val_str);
            } else {
                const char *suffix = "";
                if (!strcmp(lc->lctypename, "int64_t"))
                    suffix = "LL";
                char *mapped_typename = map_type_name(lc->lctypename);
                char *cpp_std = getopt_get_string(lcmgen->gopt, "cpp-std");
                if (strcmp("c++98", cpp_std) && strcmp("c++11", cpp_std)) {
                    printf(
                        "%s is not a valid cpp_std. Use --cpp-std=c++98 or --cpp-std=c++11 "
                        "instead\n\n",
                        cpp_std);
                    fflush(stdout);
                    _exit(1);
                }

                if (!strcmp(cpp_std, "c++11")) {
                    emit(2, "static constexpr %-8s %s = %s%s;", mapped_typename, lc->membername,
                         lc->val_str, suffix);
                } else {
                    emit(2, "// If you're using C++11 and are getting compiler errors saying");
                    emit(2, "// things like ‘constexpr’ needed for in-class initialization of");
                    emit(2, "// static data member then re-run lcm-gen with '--cpp-std=c++11'");
                    emit(2, "// to generate code that is compliant with C++11");
                    emit(2, "static const %-8s %s = %s%s;", mapped_typename, lc->membername,
                         lc->val_str, suffix);
                }
                free(mapped_typename);
            }
        }
        emit(0, "");
    }

    emit(1, "public:");
    emit(2, "/**");
    emit(2, " * Encode a message into binary form.");
    emit(2, " *");
    emit(2, " * @param buf The output buffer.");
    emit(2, " * @param offset Encoding starts at thie byte offset into @p buf.");
    emit(2, " * @param maxlen Maximum number of bytes to write.  This should generally be");
    emit(2, " *  equal to getEncodedSize().");
    emit(2, " * @return The number of bytes encoded, or <0 on error.");
    emit(2, " */");
    emit(2, "inline int encode(void *buf, int offset, int maxlen) const;");
    emit(0, "");
    emit(2, "/**");
    emit(2, " * Check how many bytes are required to encode this message.");
    emit(2, " */");
    emit(2, "inline int getEncodedSize() const;");
    emit(0, "");
    emit(2, "/**");
    emit(2, " * Decode a message from binary form into this instance.");
    emit(2, " *");
    emit(2, " * @param buf The buffer containing the encoded message.");
    emit(2, " * @param offset The byte offset into @p buf where the encoded message starts.");
    emit(2, " * @param maxlen The maximum number of bytes to read while decoding.");
    emit(2, " * @return The number of bytes decoded, or <0 if an error occured.");
    emit(2, " */");
    emit(2, "inline int decode(const void *buf, int offset, int maxlen);");
    emit(0, "");
    emit(2, "/**");
    emit(2, " * Retrieve the 64-bit fingerprint identifying the structure of the message.");
    emit(2, " * Note that the fingerprint is the same for all instances of the same");
    emit(2, " * message type, and is a fingerprint on the message type definition, not on");
    emit(2, " * the message contents.");
    emit(2, " */");
    emit(2, "inline static int64_t getHash();");
    emit(0, "");
    emit(2, "/**");
    emit(2, " * Returns \"%s\"", ls->structname->shortname);
    emit(2, " */");
    emit(2, "inline static const char* getTypeName();");

    emit(0, "");
    emit(2, "// LCM support functions. Users should not call these");
    emit(2, "inline int _encodeNoHash(void *buf, int offset, int maxlen) const;");
    emit(2, "inline int _getEncodedSizeNoHash() const;");
    emit(2, "inline int _decodeNoHash(const void *buf, int offset, int maxlen);");
    emit(2, "inline static uint64_t _computeHash(const __lcm_hash_ptr *p);");
    emit(0, "};");
    emit(0, "");

    free(tn_);
}

static void emit_encode(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    // clang-format off
    emit(0, "int %s::encode(void *buf, int offset, int maxlen) const", sn);
    emit(0, "{");
    emit(1,     "int pos = 0, tlen;");
    emit(1,     "int64_t hash = getHash();");
    emit(0, "");
    emit(1,     "tlen = __int64_t_encode_array(buf, offset + pos, maxlen - pos, &hash, 1);");
    emit(1,     "if(tlen < 0) return tlen; else pos += tlen;");
    emit(0, "");
    emit(1,     "tlen = this->_encodeNoHash(buf, offset + pos, maxlen - pos);");
    emit(1,     "if (tlen < 0) return tlen; else pos += tlen;");
    emit(0, "");
    emit(1,     "return pos;");
    emit(0, "}");
    emit(0, "");
    // clang-format on
}

static void emit_encoded_size(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    // clang-format off
    emit(0, "int %s::getEncodedSize() const", sn);
    emit(0, "{");
    emit(1,     "return 8 + _getEncodedSizeNoHash();");
    emit(0, "}");
    emit(0, "");
    // clang-format on
}

static void emit_decode(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    // clang-format off
    emit(0, "int %s::decode(const void *buf, int offset, int maxlen)", sn);
    emit(0, "{");
    emit(1,     "int pos = 0, thislen;");
    emit(0, "");
    emit(1,     "int64_t msg_hash;");
    emit(1,     "thislen = __int64_t_decode_array(buf, offset + pos, maxlen - pos, &msg_hash, 1);");
    emit(1,     "if (thislen < 0) return thislen; else pos += thislen;");
    emit(1,     "if (msg_hash != getHash()) return -1;");
    emit(0, "");
    emit(1,     "thislen = this->_decodeNoHash(buf, offset + pos, maxlen - pos);");
    emit(1,     "if (thislen < 0) return thislen; else pos += thislen;");
    emit(0, "");
    emit(1,  "return pos;");
    emit(0, "}");
    emit(0, "");
    // clang-format on
}

static void emit_get_hash(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    // clang-format off
    emit(0, "int64_t %s::getHash()", sn);
    emit(0, "{");
    emit(1,     "static int64_t hash = static_cast<int64_t>(_computeHash(NULL));");
    emit(1,     "return hash;");
    emit(0, "}");
    emit(0, "");
    // clang-format off
}

static void emit_compute_hash(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn  = ls->structname->shortname;
    int last_complex_member = -1;
    for (unsigned int m = 0; m < g_ptr_array_size(ls->members); m++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, m);
        if(!lcm_is_primitive_type(lm->type->lctypename))
            last_complex_member = m;
    }

    if(last_complex_member >= 0) {
        // clang-format off
        emit(0, "uint64_t %s::_computeHash(const __lcm_hash_ptr *p)", sn);
        emit(0, "{");
        emit(1,     "const __lcm_hash_ptr *fp;");
        emit(1,     "for(fp = p; fp != NULL; fp = fp->parent)");
        emit(2,         "if(fp->v == %s::getHash)", sn);
        emit(3,              "return 0;");
        // clang-format on
        if (g_ptr_array_size(ls->members)) {
            emit(1, "const __lcm_hash_ptr cp = { p, %s::getHash };", sn);
        }
        emit(0, "");
        emit(1, "uint64_t hash = 0x%016" PRIx64 "LL +", ls->hash);

        for (unsigned int m = 0; m <= last_complex_member; m++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, m);
            char *lm_tnc = dots_to_double_colons(lm->type->lctypename);
            if (!lcm_is_primitive_type(lm->type->lctypename)) {
                emit(2, " %s::_computeHash(&cp)%s", lm_tnc,
                     (m == last_complex_member) ? ";" : " +");
            }
            free(lm_tnc);
        }
        emit(0, "");
    } else {
        // clang-format off
        emit(0, "uint64_t %s::_computeHash(const __lcm_hash_ptr *)", sn);
        emit(0, "{");
        emit(1,     "uint64_t hash = 0x%016"PRIx64"LL;", ls->hash);
        // clang-format on
    }

    // clang-format off
    emit(1,     "return (hash<<1) + ((hash>>63)&1);");
    emit(0, "}");
    emit(0, "");
    // clang-format on
}

static void _encode_recursive(lcmgen_t *lcm, FILE *f, lcm_member_t *lm, int depth, int extra_indent)
{
    int indent = extra_indent + 1 + depth;
    // primitive array
    if (depth + 1 == g_ptr_array_size(lm->dimensions) &&
        lcm_is_primitive_type(lm->type->lctypename) && strcmp(lm->type->lctypename, "string")) {
        lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, depth);
        emit_start(indent, "tlen = __%s_encode_array(buf, offset + pos, maxlen - pos, &this->%s",
                   lm->type->lctypename, lm->membername);
        for (int i = 0; i < depth; i++)
            emit_continue("[a%d]", i);
        emit_end("[0], %s%s);", dim_size_prefix(dim->size), dim->size);

        emit(indent, "if(tlen < 0) return tlen; else pos += tlen;");
        return;
    }
    //
    if (depth == g_ptr_array_size(lm->dimensions)) {
        if (!strcmp(lm->type->lctypename, "string")) {
            emit_start(indent, "char* __cstr = const_cast<char*>(this->%s", lm->membername);
            for (int i = 0; i < depth; i++)
                emit_continue("[a%d]", i);
            emit_end(".c_str());");
            emit(indent, "tlen = __string_encode_array(");
            emit(indent, "    buf, offset + pos, maxlen - pos, &__cstr, 1);");
        } else {
            emit_start(indent, "tlen = this->%s", lm->membername);
            for (int i = 0; i < depth; i++)
                emit_continue("[a%d]", i);
            emit_end("._encodeNoHash(buf, offset + pos, maxlen - pos);");
        }
        emit(indent, "if(tlen < 0) return tlen; else pos += tlen;");
        return;
    }

    lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, depth);

    emit(indent, "for (int a%d = 0; a%d < %s%s; a%d++) {", depth, depth, dim_size_prefix(dim->size),
         dim->size, depth);

    _encode_recursive(lcm, f, lm, depth + 1, extra_indent);

    emit(indent, "}");
}

static void emit_encode_nohash(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    if (0 == g_ptr_array_size(ls->members)) {
        // clang-format off
        emit(0, "int %s::_encodeNoHash(void *, int, int) const", sn);
        emit(0, "{");
        emit(1,     "return 0;");
        emit(0, "}");
        emit(0, "");
        // clang-format on
        return;
    }

    // clang-format off
    emit(0, "int %s::_encodeNoHash(void *buf, int offset, int maxlen) const", sn);
    emit(0, "{");
    emit(1,     "int pos = 0, tlen;");
    emit(0, "");
    // clang-format on
    for (unsigned int m = 0; m < g_ptr_array_size(ls->members); m++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, m);
        int num_dims = g_ptr_array_size(lm->dimensions);

        if (0 == num_dims) {
            if (lcm_is_primitive_type(lm->type->lctypename)) {
                if (!strcmp(lm->type->lctypename, "string")) {
                    emit(1, "char* %s_cstr = const_cast<char*>(this->%s.c_str());", lm->membername,
                         lm->membername);
                    emit(1, "tlen = __string_encode_array(");
                    emit(1, "    buf, offset + pos, maxlen - pos, &%s_cstr, 1);", lm->membername);
                } else {
                    emit(1,
                         "tlen = __%s_encode_array(buf, offset + pos, maxlen - pos, &this->%s, 1);",
                         lm->type->lctypename, lm->membername);
                }
                emit(1, "if(tlen < 0) return tlen; else pos += tlen;");
            } else {
                _encode_recursive(lcm, f, lm, 0, 0);
            }
        } else {
            lcm_dimension_t *last_dim =
                (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, num_dims - 1);

            // for non-string primitive types with variable size final
            // dimension, add an optimization to only call the primitive encode
            // functions only if the final dimension size is non-zero.
            if (lcm_is_primitive_type(lm->type->lctypename) &&
                strcmp(lm->type->lctypename, "string") && !is_dim_size_fixed(last_dim->size)) {
                emit(1, "if(%s%s > 0) {", dim_size_prefix(last_dim->size), last_dim->size);
                _encode_recursive(lcm, f, lm, 0, 1);
                emit(1, "}");
            } else {
                _encode_recursive(lcm, f, lm, 0, 0);
            }
        }

        emit(0, "");
    }
    emit(1, "return pos;");
    emit(0, "}");
    emit(0, "");
}

static void emit_encoded_size_nohash(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    emit(0, "int %s::_getEncodedSizeNoHash() const", sn);
    emit(0, "{");
    if (0 == g_ptr_array_size(ls->members)) {
        emit(1, "return 0;");
        emit(0, "}");
        emit(0, "");
        return;
    }
    emit(1, "int enc_size = 0;");
    for (unsigned int m = 0; m < g_ptr_array_size(ls->members); m++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, m);
        int ndim = g_ptr_array_size(lm->dimensions);

        if (lcm_is_primitive_type(lm->type->lctypename) && strcmp(lm->type->lctypename, "string")) {
            emit_start(1, "enc_size += ");
            for (int n = 0; n < ndim - 1; n++) {
                lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, n);
                emit_continue("%s%s * ", dim_size_prefix(dim->size), dim->size);
            }
            if (ndim > 0) {
                lcm_dimension_t *dim =
                    (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, ndim - 1);
                emit_end("__%s_encoded_array_size(NULL, %s%s);", lm->type->lctypename,
                         dim_size_prefix(dim->size), dim->size);
            } else {
                emit_end("__%s_encoded_array_size(NULL, 1);", lm->type->lctypename);
            }
        } else {
            for (int n = 0; n < ndim; n++) {
                lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, n);
                emit(1 + n, "for (int a%d = 0; a%d < %s%s; a%d++) {", n, n,
                     dim_size_prefix(dim->size), dim->size, n);
            }
            emit_start(ndim + 1, "enc_size += this->%s", lm->membername);
            for (int i = 0; i < ndim; i++)
                emit_continue("[a%d]", i);
            if (!strcmp(lm->type->lctypename, "string")) {
                emit_end(".size() + 4 + 1;");
            } else {
                emit_end("._getEncodedSizeNoHash();");
            }
            for (int n = ndim - 1; n >= 0; n--) {
                emit(1 + n, "}");
            }
        }
    }
    emit(1, "return enc_size;");
    emit(0, "}");
    emit(0, "");
}

static void _decode_recursive(lcmgen_t *lcm, FILE *f, lcm_member_t *lm, int depth)
{
    // primitive array
    if (depth + 1 == g_ptr_array_size(lm->dimensions) &&
        lcm_is_primitive_type(lm->type->lctypename) && strcmp(lm->type->lctypename, "string")) {
        lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, depth);

        int decode_indent = 1 + depth;
        if (!lcm_is_constant_size_array(lm)) {
            emit(1 + depth, "if(%s%s) {", dim_size_prefix(dim->size), dim->size);
            emit_start(2 + depth, "this->%s", lm->membername);
            for (int i = 0; i < depth; i++)
                emit_continue("[a%d]", i);
            emit_end(".resize(%s%s);", dim_size_prefix(dim->size), dim->size);
            decode_indent++;
        }

        emit_start(decode_indent,
                   "tlen = __%s_decode_array(buf, offset + pos, maxlen - pos, &this->%s",
                   lm->type->lctypename, lm->membername);
        for (int i = 0; i < depth; i++)
            emit_continue("[a%d]", i);
        emit_end("[0], %s%s);", dim_size_prefix(dim->size), dim->size);
        emit(decode_indent, "if(tlen < 0) return tlen; else pos += tlen;");
        if (!lcm_is_constant_size_array(lm)) {
            emit(1 + depth, "}");
        }
    } else if (depth == g_ptr_array_size(lm->dimensions)) {
        if (!strcmp(lm->type->lctypename, "string")) {
            emit(1 + depth, "int32_t __elem_len;");
            emit(1 + depth, "tlen = __int32_t_decode_array(");
            emit(1 + depth, "    buf, offset + pos, maxlen - pos, &__elem_len, 1);");
            emit(1 + depth, "if(tlen < 0) return tlen; else pos += tlen;");
            emit(1 + depth, "if(__elem_len > maxlen - pos) return -1;");
            emit_start(1 + depth, "this->%s", lm->membername);
            for (int i = 0; i < depth; i++)
                emit_continue("[a%d]", i);
            emit_end(".assign(static_cast<const char*>(buf) + offset + pos, __elem_len -  1);");
            emit(1 + depth, "pos += __elem_len;");
        } else {
            emit_start(1 + depth, "tlen = this->%s", lm->membername);
            for (int i = 0; i < depth; i++)
                emit_continue("[a%d]", i);
            emit_end("._decodeNoHash(buf, offset + pos, maxlen - pos);");
            emit(1 + depth, "if(tlen < 0) return tlen; else pos += tlen;");
        }
    } else {
        lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, depth);

        if (!lcm_is_constant_size_array(lm)) {
            emit(1 + depth, "try {");
            emit_start(2 + depth, "this->%s", lm->membername);
            for (int i = 0; i < depth; i++) {
                emit_continue("[a%d]", i);
            }
            emit_end(".resize(%s%s);", dim_size_prefix(dim->size), dim->size);
            emit(1 + depth, "} catch (...) {");
            emit(2 + depth, "return -1;");
            emit(1 + depth, "}");
        }
        emit(1 + depth, "for (int a%d = 0; a%d < %s%s; a%d++) {", depth, depth,
             dim_size_prefix(dim->size), dim->size, depth);

        _decode_recursive(lcm, f, lm, depth + 1);

        emit(1 + depth, "}");
    }
}

static void emit_decode_nohash(lcmgen_t *lcm, FILE *f, lcm_struct_t *ls)
{
    const char *sn = ls->structname->shortname;
    if (0 == g_ptr_array_size(ls->members)) {
        // clang-format off
        emit(0, "int %s::_decodeNoHash(const void *, int, int)", sn);
        emit(0, "{");
        emit(1,     "return 0;");
        emit(0, "}");
        emit(0, "");
        // clang-format on
        return;
    }

    // clang-format off
    emit(0, "int %s::_decodeNoHash(const void *buf, int offset, int maxlen)", sn);
    emit(0, "{");
    emit(1,     "int pos = 0, tlen;");
    emit(0, "");
    // clang-format on
    for (unsigned int m = 0; m < g_ptr_array_size(ls->members); m++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(ls->members, m);

        if (0 == g_ptr_array_size(lm->dimensions) && lcm_is_primitive_type(lm->type->lctypename)) {
            if (!strcmp(lm->type->lctypename, "string")) {
                emit(1, "int32_t __%s_len__;", lm->membername);
                emit(1, "tlen = __int32_t_decode_array(");
                emit(1, "    buf, offset + pos, maxlen - pos, &__%s_len__, 1);", lm->membername);
                emit(1, "if(tlen < 0) return tlen; else pos += tlen;");
                emit(1, "if(__%s_len__ > maxlen - pos) return -1;", lm->membername);
                emit(1, "this->%s.assign(", lm->membername);
                emit(1, "    static_cast<const char*>(buf) + offset + pos, __%s_len__ - 1);",
                     lm->membername);
                emit(1, "pos += __%s_len__;", lm->membername);
            } else {
                emit(1, "tlen = __%s_decode_array(buf, offset + pos, maxlen - pos, &this->%s, 1);",
                     lm->type->lctypename, lm->membername);
                emit(1, "if(tlen < 0) return tlen; else pos += tlen;");
            }
        } else {
            _decode_recursive(lcm, f, lm, 0);
        }

        emit(0, "");
    }
    emit(1, "return pos;");
    emit(0, "}");
    emit(0, "");
}

int emit_cpp(lcmgen_t *lcmgen)
{
    // iterate through all defined message types
    for (unsigned int i = 0; i < g_ptr_array_size(lcmgen->structs); i++) {
        lcm_struct_t *lr = (lcm_struct_t *) g_ptr_array_index(lcmgen->structs, i);

        const char *tn = lr->structname->lctypename;
        char *tn_ = dots_to_slashes(tn);

        // compute the target filename
        char *header_name = g_strdup_printf(
            "%s%s%s.hpp", getopt_get_string(lcmgen->gopt, "cpp-hpath"),
            strlen(getopt_get_string(lcmgen->gopt, "cpp-hpath")) > 0 ? G_DIR_SEPARATOR_S : "", tn_);

        // generate code if needed
        if (lcm_needs_generation(lcmgen, lr->lcmfile, header_name)) {
            make_dirs_for_file(header_name);

            FILE *f = fopen(header_name, "w");
            if (f == NULL)
                return -1;

            emit_header_start(lcmgen, f, lr);
            emit_encode(lcmgen, f, lr);
            emit_decode(lcmgen, f, lr);
            emit_encoded_size(lcmgen, f, lr);
            emit_get_hash(lcmgen, f, lr);

            // clang-format off
            emit(0, "const char* %s::getTypeName()", lr->structname->shortname);
            emit(0, "{");
            emit(1,     "return \"%s\";", lr->structname->shortname);
            emit(0, "}");
            emit(0, "");
            // clang-format on

            emit_encode_nohash(lcmgen, f, lr);
            emit_decode_nohash(lcmgen, f, lr);
            emit_encoded_size_nohash(lcmgen, f, lr);
            emit_compute_hash(lcmgen, f, lr);

            emit_package_namespace_close(lcmgen, f, lr);
            emit(0, "#endif");

            fclose(f);
        }
        g_free(header_name);
        free(tn_);
    }

    return 0;
}
