#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#define __STDC_FORMAT_MACROS
#endif
#include <assert.h>
#include <inttypes.h>

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

#include "lcmgen.h"

#include "getopt.h"

#ifdef WIN32
#include <lcm/windows/WinPorting.h>
#endif

#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 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
}

void setup_csharp_options(getopt_t *gopt)
{
    getopt_add_string(gopt, 0, "csharp-path", "", "C#.NET file destination directory");
    getopt_add_bool(gopt, 0, "csharp-mkdir", 1, "Make C#.NET source directories automatically");
    getopt_add_bool(gopt, 0, "csharp-strip-dirs", 0,
                    "Do not generate folders for default and root namespace");
    getopt_add_string(gopt, 0, "csharp-decl", ": LCM.LCM.LCMEncodable",
                      "String added to class declarations");
    getopt_add_string(gopt, 0, "csharp-root-nsp", "",
                      "Root C#.NET namespace (wrapper) added before LCM package");
    getopt_add_string(gopt, 0, "csharp-default-nsp", "LCMTypes",
                      "Default C#.NET namespace if LCM type has no package");
}

typedef struct {
    char *storage;
    char *decode;
    char *encode;
} primitive_info_t;

static primitive_info_t *prim(char *storage, char *decode, char *encode)
{
    primitive_info_t *p = (primitive_info_t *) calloc(sizeof(primitive_info_t), 1);
    p->storage = storage;
    p->decode = decode;
    p->encode = encode;

    return p;
}

static int ndefaultpkg_warned = 0;

const char *make_fqn_csharp(lcmgen_t *lcm, const char *type_name)
{
    char *root_nsp = getopt_get_string(lcm->gopt, "csharp-root-nsp");

    if (strchr(type_name, '.') != NULL)
        return g_strdup_printf("%s%s%s", root_nsp, (root_nsp[0] == 0 ? "" : "."), type_name);

    if (!ndefaultpkg_warned && !getopt_was_specified(lcm->gopt, "csharp-default-nsp")) {
        printf("Notice: enclosing LCM types without package into C#.NET namespace '%s'.\n",
               getopt_get_string(lcm->gopt, "csharp-default-nsp"));
        ndefaultpkg_warned = 1;
    }

    char *def_nsp = getopt_get_string(lcm->gopt, "csharp-default-nsp");
    if (strlen(def_nsp) > 0)
        return g_strdup_printf("%s%s%s.%s", root_nsp, (root_nsp[0] == 0 ? "" : "."),
                               getopt_get_string(lcm->gopt, "csharp-default-nsp"), type_name);
    else
        return g_strdup_printf("%s.%s", root_nsp, type_name);
}

/** # -> replace1
    @ -> replace2
**/
static void freplace(FILE *f, const char *haystack, const char *replace1)
{
    int len = strlen(haystack);

    for (int pos = 0; pos < len; pos++) {
        if (haystack[pos] == '#')
            fprintf(f, "%s", replace1);
        else
            fprintf(f, "%c", haystack[pos]);
    }
}

static void make_accessor(lcm_member_t *lm, const char *obj, char *s)
{
    int ndim = g_ptr_array_size(lm->dimensions);
    int pos = 0;
    s[0] = 0;

    pos += sprintf(s, "%s%s%s", obj, obj[0] == 0 ? "" : ".", lm->membername);
    if (ndim > 0) {
        pos += sprintf(&s[pos], "[");
        for (int d = 0; d < ndim; d++) {
            pos += sprintf(&s[pos], "%c", 'a' + d);
            if (d < (ndim - 1))
                pos += sprintf(&s[pos], ",");
        }
        pos += sprintf(&s[pos], "]");
    }
}

static int struct_has_string_member(lcm_struct_t *lr)
{
    for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
        lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
        if (!strcmp("string", lm->type->lctypename))
            return 1;
    }
    return 0;
}

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.";
}

int emit_csharp(lcmgen_t *lcm)
{
    GHashTable *type_table = g_hash_table_new(g_str_hash, g_str_equal);

    g_hash_table_insert(type_table, "byte", prim("byte", "# = ins.ReadByte();", "outs.Write(#);"));
    g_hash_table_insert(type_table, "int8_t",
                        prim("sbyte", "# = ins.ReadSByte();", "outs.Write(#);"));
    g_hash_table_insert(type_table, "int16_t",
                        prim("short", "# = ins.ReadInt16();", "outs.Write(#);"));
    g_hash_table_insert(type_table, "int32_t",
                        prim("int", "# = ins.ReadInt32();", "outs.Write(#);"));
    g_hash_table_insert(type_table, "int64_t",
                        prim("long", "# = ins.ReadInt64();", "outs.Write(#);"));
    g_hash_table_insert(
        type_table, "string",
        prim("String",
             "__strbuf = new byte[ins.ReadInt32()-1]; ins.ReadFully(__strbuf); ins.ReadByte(); # = "
             "System.Text.Encoding.GetEncoding(\"US-ASCII\").GetString(__strbuf);",
             "__strbuf = System.Text.Encoding.GetEncoding(\"US-ASCII\").GetBytes(#); "
             "outs.Write(__strbuf.Length+1); outs.Write(__strbuf, 0, __strbuf.Length); "
             "outs.Write((byte) 0);"));
    g_hash_table_insert(type_table, "boolean",
                        prim("bool", "# = ins.ReadBoolean();", "outs.Write(#);"));
    g_hash_table_insert(type_table, "float",
                        prim("float", "# = ins.ReadSingle();", "outs.Write(#);"));
    g_hash_table_insert(type_table, "double",
                        prim("double", "# = ins.ReadDouble();", "outs.Write(#);"));

    //////////////////////////////////////////////////////////////
    // ENUMS
    for (unsigned int en = 0; en < g_ptr_array_size(lcm->enums); en++) {
        lcm_enum_t *le = (lcm_enum_t *) g_ptr_array_index(lcm->enums, en);

        const char *classname = make_fqn_csharp(lcm, le->enumname->lctypename);
        char *path = g_strdup_printf(
            "%s%s%s.cs", getopt_get_string(lcm->gopt, "csharp-path"),
            strlen(getopt_get_string(lcm->gopt, "csharp-path")) > 0 ? G_DIR_SEPARATOR_S : "",
            dots_to_slashes((getopt_get_bool(lcm->gopt, "csharp-strip-dirs")
                                 ? le->enumname->lctypename
                                 : classname)));

        if (!lcm_needs_generation(lcm, le->lcmfile, path))
            continue;

        if (getopt_get_bool(lcm->gopt, "csharp-mkdir"))
            make_dirs_for_file(path);

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

        emit(0, "using System;");
        emit(0, "using System.Collections.Generic;");
        emit(0, "using System.IO;");
        emit(0, "using LCM.LCM;");
        emit(0, " ");

        char *root_nsp = getopt_get_string(lcm->gopt, "csharp-root-nsp");
        if (strlen(le->enumname->package) > 0)
            emit(0, "namespace %s%s%s", root_nsp, (root_nsp[0] == 0 ? "" : "."),
                 le->enumname->package);
        else {
            char *def_nsp = getopt_get_string(lcm->gopt, "csharp-default-nsp");
            if (strlen(def_nsp) > 0)
                emit(0, "namespace %s%s%s", root_nsp, (root_nsp[0] == 0 ? "" : "."), def_nsp);
            else
                emit(0, "namespace %s", root_nsp);
        }

        emit(0, "{");
        emit(1, "public sealed class %s %s", le->enumname->shortname,
             getopt_get_string(lcm->gopt, "csharp-decl"));

        emit(1, "{");
        emit(2, "public int value;");
        emit(0, " ");

        for (unsigned int v = 0; v < g_ptr_array_size(le->values); v++) {
            lcm_enum_value_t *lev = (lcm_enum_value_t *) g_ptr_array_index(le->values, v);
            emit(2, "public const int %-16s = %i;", lev->valuename, lev->value);
        }
        emit(0, " ");

        // clang-format off
        emit(2, "public %s(int value) { this.value = value; }",
             le->enumname->shortname);
        emit(0, " ");

        emit(2, "public int getValue() { return value; }");
        emit(0, " ");

        emit(2, "public void _encodeRecursive(LCMDataOutputStream outs)");
        emit(2, "{");
        emit(3,     "outs.WriteInt(this.value);");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public void Encode(LCMDataOutputStream outs)");
        emit(2, "{");
        emit(3,     "outs.Write((long) LCM_FINGERPRINT);");
        emit(3,     "_encodeRecursive(outs);");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public static %s _decodeRecursiveFactory(LCMDataInputStream ins)",
             make_fqn_csharp(lcm, le->enumname->lctypename));
        emit(2, "{");
        emit(3,     "%s o = new %s(0);",
             make_fqn_csharp(lcm, le->enumname->lctypename),
             make_fqn_csharp(lcm, le->enumname->lctypename));
        emit(3,     "o._decodeRecursive(ins);");
        emit(3,     "return o;");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public void _decodeRecursive(LCMDataInputStream ins)");
        emit(2, "{");
        emit(3,     "this.value = ins.ReadInt();");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public %s(LCMDataInputStream ins)", le->enumname->shortname);
        emit(2, "{");
        emit(3,     "ulong hash = (ulong) ins.ReadInt64();");
        emit(3,     "if (hash != LCM_FINGERPRINT)");
        emit(4,         "throw new System.IO.IOException(\"LCM Decode error: bad fingerprint\");");
        emit(3,     "_decodeRecursive(ins);");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public %s Copy()", classname);
        emit(2, "{");
        emit(3,     "return new %s(this.value);", classname);
        emit(2, "}");
        emit(0, " ");

        emit(2, "public const ulong _hashRecursive(List<String> clss)");
        emit(2, "{");
        emit(3,     "return LCM_FINGERPRINT;");
        emit(2, "}");
        emit(0, " ");
        emit(2, "public static const ulong LCM_FINGERPRINT = 0x%016"PRIx64"L;", le->hash);
        emit(1, "}");
        emit(0, "}");
        // clang-format on

        fclose(f);
    }

    for (unsigned int st = 0; st < g_ptr_array_size(lcm->structs); st++) {
        lcm_struct_t *lr = (lcm_struct_t *) g_ptr_array_index(lcm->structs, st);

        const char *classname = make_fqn_csharp(lcm, lr->structname->lctypename);
        char *path =
            g_strdup_printf("%s%s%s.cs", getopt_get_string(lcm->gopt, "csharp-path"),
                            strlen(getopt_get_string(lcm->gopt, "csharp-path")) > 0 ? "/" : "",
                            dots_to_slashes((getopt_get_bool(lcm->gopt, "csharp-strip-dirs")
                                                 ? lr->structname->lctypename
                                                 : classname)));

        if (!lcm_needs_generation(lcm, lr->lcmfile, path))
            continue;

        if (getopt_get_bool(lcm->gopt, "csharp-mkdir"))
            make_dirs_for_file(path);

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

        emit(0,
             "/* LCM type definition class file\n"
             " * This file was automatically generated by lcm-gen\n"
             " * DO NOT MODIFY BY HAND!!!!\n"
             " */\n");

        emit(0, "using System;");
        emit(0, "using System.Collections.Generic;");
        emit(0, "using System.IO;");
        emit(0, "using LCM.LCM;");
        emit(0, " ");

        char *root_nsp = getopt_get_string(lcm->gopt, "csharp-root-nsp");
        if (strlen(lr->structname->package) > 0)
            emit(0, "namespace %s%s%s", root_nsp, (root_nsp[0] == 0 ? "" : "."),
                 lr->structname->package);
        else {
            char *def_nsp = getopt_get_string(lcm->gopt, "csharp-default-nsp");
            if (strlen(def_nsp) > 0)
                emit(0, "namespace %s%s%s", root_nsp, (root_nsp[0] == 0 ? "" : "."), def_nsp);
            else
                emit(0, "namespace %s", root_nsp);
        }

        emit(0, "{");
        emit(1, "public sealed class %s %s", lr->structname->shortname,
             getopt_get_string(lcm->gopt, "csharp-decl"));
        emit(1, "{");

        for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
            primitive_info_t *pinfo =
                (primitive_info_t *) g_hash_table_lookup(type_table, lm->type->lctypename);

            emit_start(2, "public ");

            if (pinfo == NULL) {
                emit_continue("%s", make_fqn_csharp(lcm, lm->type->lctypename));
            } else {
                emit_continue("%s", pinfo->storage);
            }

            if (g_ptr_array_size(lm->dimensions) > 0) {
                emit_continue("[");
                for (unsigned int i = 0; i < (g_ptr_array_size(lm->dimensions) - 1); i++)
                    emit_continue(",");
                emit_continue("]");
            }
            emit_continue(" %s", lm->membername);
            emit_end(";");
        }
        emit(0, " ");

        // public constructor
        emit(2, "public %s()", lr->structname->shortname);
        emit(2, "{");

        // pre-allocate any fixed-size arrays.
        for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
            primitive_info_t *pinfo =
                (primitive_info_t *) g_hash_table_lookup(type_table, lm->type->lctypename);

            if (g_ptr_array_size(lm->dimensions) == 0 || !lcm_is_constant_size_array(lm))
                continue;

            emit_start(3, "%s = new ", lm->membername);
            if (pinfo != NULL)
                emit_continue("%s", pinfo->storage);
            else
                emit_continue("%s", make_fqn_csharp(lcm, lm->type->lctypename));

            emit_continue("[");
            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, i);
                emit_continue("%s", dim->size);
                if (i < (g_ptr_array_size(lm->dimensions) - 1))
                    emit_continue(",");
            }
            emit_end("];");
        }
        emit(2, "}");
        emit(0, " ");

        emit(2, "public static readonly ulong LCM_FINGERPRINT;");
        emit(2, "public static readonly ulong LCM_FINGERPRINT_BASE = 0x%016" PRIx64 "L;", lr->hash);
        emit(0, " ");

        //////////////////////////////////////////////////////////////
        // CONSTANTS
        for (unsigned int cn = 0; cn < g_ptr_array_size(lr->constants); cn++) {
            lcm_constant_t *lc = (lcm_constant_t *) g_ptr_array_index(lr->constants, cn);
            assert(lcm_is_legal_const_type(lc->lctypename));

            if (!strcmp(lc->lctypename, "int8_t") || !strcmp(lc->lctypename, "int16_t") ||
                !strcmp(lc->lctypename, "int32_t")) {
                emit(2, "public const int %s = %s;", lc->membername, lc->val_str);
            } else if (!strcmp(lc->lctypename, "int64_t")) {
                emit(2, "public const long %s = %sL;", lc->membername, lc->val_str);
            } else if (!strcmp(lc->lctypename, "float")) {
                emit(2, "public const float %s = %sf;", lc->membername, lc->val_str);
            } else if (!strcmp(lc->lctypename, "double")) {
                emit(2, "public const double %s = %s;", lc->membername, lc->val_str);
            } else {
                assert(0);
            }
        }
        if (g_ptr_array_size(lr->constants) > 0)
            emit(0, "");

        ///////////////// encode //////////////////

        // clang-format off
        emit(2, "static %s()",  lr->structname->shortname);
        emit(2, "{");
        emit(3,     "LCM_FINGERPRINT = _hashRecursive(new List<String>());");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public static ulong _hashRecursive(List<String> classes)");
        emit(2, "{");
        emit(3,     "if (classes.Contains(\"%s\"))",
             make_fqn_csharp(lcm, lr->structname->lctypename));
        emit(4,         "return 0L;");
        emit(0, " ");
        emit(3,     "classes.Add(\"%s\");", make_fqn_csharp(lcm, lr->structname->lctypename));
        emit(3,     "ulong hash = LCM_FINGERPRINT_BASE");
        // clang-format on

        for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
            primitive_info_t *pinfo =
                (primitive_info_t *) g_hash_table_lookup(type_table, lm->type->lctypename);

            if (pinfo)
                continue;

            emit(4, " + %s._hashRecursive(classes)", make_fqn_csharp(lcm, lm->type->lctypename));
        }
        emit(4, ";");

        // clang-format off
        emit(3,     "classes.RemoveAt(classes.Count - 1);");
        emit(3,     "return (hash<<1) + ((hash>>63)&1);");
        emit(2, "}");
        emit(0, " ");
        // clang-format on

        ///////////////// encode //////////////////

        // clang-format off
        emit(2, "public void Encode(LCMDataOutputStream outs)");
        emit(2, "{");
        emit(3,     "outs.Write((long) LCM_FINGERPRINT);");
        emit(3,     "_encodeRecursive(outs);");
        emit(2, "}");
        emit(0, " ");
        // clang-format on

        emit(2, "public void _encodeRecursive(LCMDataOutputStream outs)");
        emit(2, "{");

        if (struct_has_string_member(lr))
            emit(3, "byte[] __strbuf = null;");
        char accessor[1024];

        for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
            primitive_info_t *pinfo =
                (primitive_info_t *) g_hash_table_lookup(type_table, lm->type->lctypename);
            make_accessor(lm, "this", accessor);

            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, i);
                emit(3 + i, "for (int %c = 0; %c < %s%s; %c++) {", 'a' + i, 'a' + i,
                     dim_size_prefix(dim->size), dim->size, 'a' + i);
            }

            emit_start(3 + g_ptr_array_size(lm->dimensions), "");
            if (pinfo != NULL)
                freplace(f, pinfo->encode, accessor);
            else
                freplace(f, "#._encodeRecursive(outs);", accessor);
            emit_end(" ");

            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                emit(3 + g_ptr_array_size(lm->dimensions) - i - 1, "}");
            }
            emit(0, " ");
        }

        emit(2, "}");
        emit(0, " ");

        ///////////////// decode //////////////////

        // clang-format off
        emit(2, "public %s(byte[] data) : this(new LCMDataInputStream(data))",
             lr->structname->shortname);
        emit(2, "{");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public %s(LCMDataInputStream ins)", lr->structname->shortname);
        emit(2, "{");
        emit(3,     "if ((ulong) ins.ReadInt64() != LCM_FINGERPRINT)");
        emit(4,         "throw new System.IO.IOException(\"LCM Decode error: bad fingerprint\");");
        emit(0, " ");
        emit(3,     "_decodeRecursive(ins);");
        emit(2, "}");
        emit(0, " ");

        emit(2, "public static %s _decodeRecursiveFactory(LCMDataInputStream ins)",
             make_fqn_csharp(lcm, lr->structname->lctypename));
        emit(2, "{");
        emit(3,     "%s o = new %s();",
             make_fqn_csharp(lcm, lr->structname->lctypename),
             make_fqn_csharp(lcm, lr->structname->lctypename));
        emit(3,     "o._decodeRecursive(ins);");
        emit(3,     "return o;");
        emit(2, "}");
        emit(0, " ");
        // clang-format on

        emit(2, "public void _decodeRecursive(LCMDataInputStream ins)");
        emit(2, "{");

        if (struct_has_string_member(lr))
            emit(3, "byte[] __strbuf = null;");
        for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
            primitive_info_t *pinfo =
                (primitive_info_t *) g_hash_table_lookup(type_table, lm->type->lctypename);

            make_accessor(lm, "this", accessor);

            // allocate an array if necessary
            if (g_ptr_array_size(lm->dimensions) > 0) {
                emit_start(3, "this.%s = new ", lm->membername);

                if (pinfo != NULL)
                    emit_continue("%s", pinfo->storage);
                else
                    emit_continue("%s", make_fqn_csharp(lcm, lm->type->lctypename));

                if (g_ptr_array_size(lm->dimensions) > 0) {
                    emit_continue("[");
                    for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                        lcm_dimension_t *dim =
                            (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, i);
                        emit_continue("(int) %s", dim->size);
                        if (i < (g_ptr_array_size(lm->dimensions) - 1))
                            emit_continue(",");
                    }
                    emit_continue("]");
                }
                emit_end(";");
            }

            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, i);
                emit(3 + i, "for (int %c = 0; %c < %s%s; %c++) {", 'a' + i, 'a' + i,
                     dim_size_prefix(dim->size), dim->size, 'a' + i);
            }

            emit_start(3 + g_ptr_array_size(lm->dimensions), "");
            if (pinfo != NULL)
                freplace(f, pinfo->decode, accessor);
            else {
                emit_continue("%s = %s._decodeRecursiveFactory(ins);", accessor,
                              make_fqn_csharp(lcm, lm->type->lctypename));
            }
            emit_end("");

            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                emit(3 + g_ptr_array_size(lm->dimensions) - i - 1, "}");
            }

            emit(0, " ");
        }

        emit(2, "}");
        emit(0, " ");

        ///////////////// copy //////////////////

        emit(2, "public %s Copy()", classname);
        emit(2, "{");
        emit(3, "%s outobj = new %s();", classname, classname);

        for (unsigned int member = 0; member < g_ptr_array_size(lr->members); member++) {
            lcm_member_t *lm = (lcm_member_t *) g_ptr_array_index(lr->members, member);
            primitive_info_t *pinfo =
                (primitive_info_t *) g_hash_table_lookup(type_table, lm->type->lctypename);
            make_accessor(lm, "", accessor);

            // allocate an array if necessary
            if (g_ptr_array_size(lm->dimensions) > 0) {
                emit_start(3, "outobj.%s = new ", lm->membername);

                if (pinfo != NULL)
                    emit_continue("%s", pinfo->storage);
                else
                    emit_continue("%s", make_fqn_csharp(lcm, lm->type->lctypename));

                if (g_ptr_array_size(lm->dimensions) > 0) {
                    emit_continue("[");
                    for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                        lcm_dimension_t *dim =
                            (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, i);
                        emit_continue("(int) %s", dim->size);
                        if (i < (g_ptr_array_size(lm->dimensions) - 1))
                            emit_continue(",");
                    }
                    emit_continue("]");
                }
                emit_end(";");
            }

            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                lcm_dimension_t *dim = (lcm_dimension_t *) g_ptr_array_index(lm->dimensions, i);
                emit(3 + i, "for (int %c = 0; %c < %s%s; %c++) {", 'a' + i, 'a' + i,
                     dim_size_prefix(dim->size), dim->size, 'a' + i);
            }

            if (pinfo != NULL) {
                emit_start(3 + g_ptr_array_size(lm->dimensions), "outobj.%s", lm->membername);
                if (g_ptr_array_size(lm->dimensions) > 0) {
                    emit_continue("[");
                    for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                        emit_continue("%c", 'a' + i);
                        if (i < (g_ptr_array_size(lm->dimensions) - 1))
                            emit_continue(",");
                    }
                    emit_continue("]");
                }

                emit_continue(" = this.%s", lm->membername);
                if (g_ptr_array_size(lm->dimensions) > 0) {
                    emit_continue("[");
                    for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                        emit_continue("%c", 'a' + i);
                        if (i < (g_ptr_array_size(lm->dimensions) - 1))
                            emit_continue(",");
                    }
                    emit_continue("]");
                }

                emit_end(";");

            } else {
                emit(3 + g_ptr_array_size(lm->dimensions), "outobj.%s = this.%s.Copy();", accessor,
                     accessor);
            }

            for (unsigned int i = 0; i < g_ptr_array_size(lm->dimensions); i++) {
                emit(3 + g_ptr_array_size(lm->dimensions) - i - 1, "}");
            }

            emit(0, " ");
        }

        emit(3, "return outobj;");
        emit(2, "}");

        ////////
        emit(1, "}");
        emit(0, "}\n");
        fclose(f);
    }

    return 0;
}
