/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 "charmony.h"

#include <string.h>
#include <stdio.h>

#define CFC_NEED_BASE_STRUCT_DEF
#include "CFCBase.h"
#include "CFCBindCore.h"
#include "CFCBindClass.h"
#include "CFCBindFile.h"
#include "CFCBindSpecs.h"
#include "CFCClass.h"
#include "CFCFile.h"
#include "CFCHierarchy.h"
#include "CFCParcel.h"
#include "CFCUtil.h"
#include "CFCVersion.h"

#define STRING(s)  #s
#define XSTRING(s) STRING(s)

struct CFCBindCore {
    CFCBase base;
    CFCHierarchy *hierarchy;
    char         *c_header;
    char         *c_footer;
};

/* Write the "parcel.h" header file, which contains common symbols needed by
 * all classes, plus typedefs for all class structs.
 */
static void
S_write_parcel_h(CFCBindCore *self, CFCParcel *parcel);

/* Write the "parcel.c" file containing autogenerated implementation code.
 */
static void
S_write_parcel_c(CFCBindCore *self, CFCParcel *parcel);

/* Write the "cfish_platform.h" header file, which contains platform-specific
 * definitions.
 */
static void
S_write_platform_h(CFCBindCore *self);

static char*
S_charmony_feature_defines(void);

static char*
S_charmony_string_defines(void);

static char*
S_charmony_stdbool_defines(void);

static char*
S_charmony_stdint_defines(void);

static char*
S_charmony_alloca_defines(void);

static void
S_write_host_data_json(CFCBindCore *self, CFCParcel *parcel,
                       const char *dest_dir, const char *host_lang);

static const CFCMeta CFCBINDCORE_META = {
    "Clownfish::CFC::Binding::Core",
    sizeof(CFCBindCore),
    (CFCBase_destroy_t)CFCBindCore_destroy
};

CFCBindCore*
CFCBindCore_new(CFCHierarchy *hierarchy, const char *header,
                const char *footer) {
    CFCBindCore *self = (CFCBindCore*)CFCBase_allocate(&CFCBINDCORE_META);
    return CFCBindCore_init(self, hierarchy, header, footer);
}

CFCBindCore*
CFCBindCore_init(CFCBindCore *self, CFCHierarchy *hierarchy,
                 const char *header, const char *footer) {
    CFCUTIL_NULL_CHECK(hierarchy);
    CFCUTIL_NULL_CHECK(header);
    CFCUTIL_NULL_CHECK(footer);
    self->hierarchy = (CFCHierarchy*)CFCBase_incref((CFCBase*)hierarchy);
    self->c_header  = CFCUtil_make_c_comment(header);
    self->c_footer  = CFCUtil_make_c_comment(footer);
    return self;
}

void
CFCBindCore_destroy(CFCBindCore *self) {
    CFCBase_decref((CFCBase*)self->hierarchy);
    FREEMEM(self->c_header);
    FREEMEM(self->c_footer);
    CFCBase_destroy((CFCBase*)self);
}

int
CFCBindCore_write_all_modified(CFCBindCore *self, int modified) {
    CFCHierarchy *hierarchy = self->hierarchy;
    const char   *header    = self->c_header;
    const char   *footer    = self->c_footer;

    // Discover whether files need to be regenerated.
    modified = CFCHierarchy_propagate_modified(hierarchy, modified);

    // Iterate over all File objects, writing out those which don't have
    // up-to-date auto-generated files.
    const char *inc_dest = CFCHierarchy_get_include_dest(hierarchy);
    CFCFile **files = CFCHierarchy_files(hierarchy);
    for (int i = 0; files[i] != NULL; i++) {
        if (CFCFile_get_modified(files[i])) {
            CFCBindFile_write_h(files[i], inc_dest, header, footer);
        }
    }

    // If any class definition has changed, rewrite the parcel.h and parcel.c
    // files.
    if (modified) {
        S_write_platform_h(self);

        CFCParcel **parcels = CFCParcel_all_parcels();
        for (size_t i = 0; parcels[i]; ++i) {
            CFCParcel *parcel = parcels[i];
            S_write_parcel_h(self, parcel);
            if (!CFCParcel_included(parcel)) {
                S_write_parcel_c(self, parcel);
            }
        }
    }

    return modified;
}

/* Write the "parcel.h" header file, which contains common symbols needed by
 * all classes, plus typedefs for all class structs.
 */
static void
S_write_parcel_h(CFCBindCore *self, CFCParcel *parcel) {
    CFCHierarchy *hierarchy   = self->hierarchy;
    const char   *prefix      = CFCParcel_get_prefix(parcel);
    const char   *PREFIX      = CFCParcel_get_PREFIX(parcel);
    const char   *privacy_sym = CFCParcel_get_privacy_sym(parcel);

    // Declare object structs and class singletons for all instantiable
    // classes.
    char *typedefs    = CFCUtil_strdup("");
    char *class_decls = CFCUtil_strdup("");
    CFCClass **ordered = CFCHierarchy_ordered_classes(hierarchy);
    for (int i = 0; ordered[i] != NULL; i++) {
        CFCClass *klass = ordered[i];
        const char *class_prefix = CFCClass_get_prefix(klass);
        if (strcmp(class_prefix, prefix) != 0) { continue; }

        if (!CFCClass_inert(klass)) {
            const char *full_struct = CFCClass_full_struct_sym(klass);
            typedefs = CFCUtil_cat(typedefs, "typedef struct ", full_struct,
                                   " ", full_struct, ";\n", NULL);
            const char *class_var = CFCClass_full_class_var(klass);
            class_decls = CFCUtil_cat(class_decls, "extern ", PREFIX,
                                      "VISIBLE cfish_Class *", class_var,
                                      ";\n", NULL);
        }
    }
    FREEMEM(ordered);

    // Special includes and macros for Clownfish parcel.
    const char *cfish_includes =
        "#include <stdarg.h>\n"
        "#include <stddef.h>\n"
        "\n"
        "#include \"cfish_platform.h\"\n"
        "#include \"cfish_hostdefs.h\"\n";

    // Special definitions for Clownfish parcel.
    const char *cfish_defs_1 =
        "#define CFISH_UNUSED_VAR(var) ((void)var)\n"
        "#define CFISH_UNREACHABLE_RETURN(type) return (type)0\n"
        "\n"
        "/* Generic method pointer.\n"
        " */\n"
        "typedef void\n"
        "(*cfish_method_t)(const void *vself);\n"
        "\n"
        "/* Access the function pointer for a given method from the class.\n"
        " */\n"
        "#define CFISH_METHOD_PTR(_class, _full_meth) \\\n"
        "     ((_full_meth ## _t)cfish_method(_class, _full_meth ## _OFFSET))\n"
        "\n"
        "static CFISH_INLINE cfish_method_t\n"
        "cfish_method(const void *klass, uint32_t offset) {\n"
        "    union { char *cptr; cfish_method_t *fptr; } ptr;\n"
        "    ptr.cptr = (char*)klass + offset;\n"
        "    return ptr.fptr[0];\n"
        "}\n"
        "\n"
        "typedef struct cfish_Dummy {\n"
        "   CFISH_OBJ_HEAD\n"
        "   void *klass;\n"
        "} cfish_Dummy;\n"
        "\n"
        "/* Access the function pointer for a given method from the object.\n"
        " */\n"
        "static CFISH_INLINE cfish_method_t\n"
        "cfish_obj_method(const void *object, uint32_t offset) {\n"
        "    cfish_Dummy *dummy = (cfish_Dummy*)object;\n"
        "    return cfish_method(dummy->klass, offset);\n"
        "}\n"
        "\n"
        "/* Access the function pointer for the given method in the\n"
        " * superclass. */\n"
        "#define CFISH_SUPER_METHOD_PTR(_class, _full_meth) \\\n"
        "     ((_full_meth ## _t)cfish_super_method(_class, \\\n"
        "                                           _full_meth ## _OFFSET))\n"
        "\n"
        "extern CFISH_VISIBLE uint32_t cfish_Class_offset_of_parent;\n"
        "static CFISH_INLINE cfish_method_t\n"
        "cfish_super_method(const void *klass, uint32_t offset) {\n"
        "    char *class_as_char = (char*)klass;\n"
        "    cfish_Class **parent_ptr\n"
        "        = (cfish_Class**)(class_as_char + cfish_Class_offset_of_parent);\n"
        "    return cfish_method(*parent_ptr, offset);\n"
        "}\n"
        "\n"
        "typedef void\n"
        "(*cfish_destroy_t)(void *vself);\n"
        "extern CFISH_VISIBLE uint32_t CFISH_Obj_Destroy_OFFSET;\n"
        "\n"
        "/** Invoke the [](.Destroy) method found in `klass` on\n"
        " * `self`.\n"
        " *\n"
        " * TODO: Eliminate this function if we can arrive at a proper SUPER syntax.\n"
        " */\n"
        "static CFISH_INLINE void\n"
        "cfish_super_destroy(void *vself, cfish_Class *klass) {\n"
        "    cfish_Obj *self = (cfish_Obj*)vself;\n"
        "    if (self != NULL) {\n"
        "        cfish_destroy_t super_destroy\n"
        "            = (cfish_destroy_t)cfish_super_method(klass, CFISH_Obj_Destroy_OFFSET);\n"
        "        super_destroy(self);\n"
        "    }\n"
        "}\n"
        "\n"
        "#define CFISH_SUPER_DESTROY(_self, _class) \\\n"
        "    cfish_super_destroy(_self, _class)\n"
        "\n"
        "extern CFISH_VISIBLE cfish_Obj*\n"
        "cfish_inc_refcount(void *vself);\n"
        "\n"
        "/** NULL-safe invocation invocation of `cfish_inc_refcount`.\n"
        " *\n"
        " * @return NULL if `self` is NULL, otherwise the return value\n"
        " * of `cfish_inc_refcount`.\n"
        " */\n"
        "static CFISH_INLINE cfish_Obj*\n"
        "cfish_incref(void *vself) {\n"
        "    if (vself != NULL) { return cfish_inc_refcount(vself); }\n"
        "    else { return NULL; }\n"
        "}\n"
        "\n"
        "#define CFISH_INCREF(_self) cfish_incref(_self)\n"
        "#define CFISH_INCREF_NN(_self) cfish_inc_refcount(_self)\n"
        "\n"
        "extern CFISH_VISIBLE uint32_t\n"
        "cfish_dec_refcount(void *vself);\n"
        "\n"
        "/** NULL-safe invocation of `cfish_dec_refcount`.\n"
        " *\n"
        " * @return NULL if `self` is NULL, otherwise the return value\n"
        " * of `cfish_dec_refcount`.\n"
        " */\n"
        "static CFISH_INLINE uint32_t\n"
        "cfish_decref(void *vself) {\n"
        "    if (vself != NULL) { return cfish_dec_refcount(vself); }\n"
        "    else { return 0; }\n"
        "}\n"
        "\n"
        "#define CFISH_DECREF(_self) cfish_decref(_self)\n"
        "#define CFISH_DECREF_NN(_self) cfish_dec_refcount(_self)\n"
        "\n"
        "extern CFISH_VISIBLE uint32_t\n"
        "cfish_get_refcount(void *vself);\n"
        "\n"
        "#define CFISH_REFCOUNT_NN(_self) \\\n"
        "    cfish_get_refcount(_self)\n"
        "\n"
        "/* Flags for internal use. */\n"
        "#define CFISH_fREFCOUNTSPECIAL 0x00000001\n"
        "#define CFISH_fFINAL           0x00000002\n"
        ;
    const char *cfish_defs_2 =
        "#ifdef CFISH_USE_SHORT_NAMES\n"
        "  #define UNUSED_VAR               CFISH_UNUSED_VAR\n"
        "  #define UNREACHABLE_RETURN       CFISH_UNREACHABLE_RETURN\n"
        "  #define METHOD_PTR               CFISH_METHOD_PTR\n"
        "  #define SUPER_METHOD_PTR         CFISH_SUPER_METHOD_PTR\n"
        "  #define SUPER_DESTROY(_self, _class) CFISH_SUPER_DESTROY(_self, _class)\n"
        "  #define INCREF(_self)                CFISH_INCREF(_self)\n"
        "  #define INCREF_NN(_self)             CFISH_INCREF_NN(_self)\n"
        "  #define DECREF(_self)                CFISH_DECREF(_self)\n"
        "  #define DECREF_NN(_self)             CFISH_DECREF_NN(_self)\n"
        "  #define REFCOUNT_NN(_self)           CFISH_REFCOUNT_NN(_self)\n"
        "#endif\n"
        "\n";

    char *extra_defs;
    char *extra_includes;
    if (CFCParcel_is_cfish(parcel)) {
        const char *spec_typedefs = CFCBindSpecs_get_typedefs();
        extra_defs = CFCUtil_sprintf("%s%s%s", cfish_defs_1, spec_typedefs,
                                     cfish_defs_2);
        extra_includes = CFCUtil_strdup(cfish_includes);
    }
    else {
        extra_defs = CFCUtil_strdup("");
        extra_includes = CFCUtil_strdup("");

        // Include parcel.h of prerequisite parcels.
        CFCParcel **prereq_parcels = CFCParcel_prereq_parcels(parcel);
        for (size_t i = 0; prereq_parcels[i]; ++i) {
            const char *prereq_prefix
                = CFCParcel_get_prefix(prereq_parcels[i]);
            extra_includes = CFCUtil_cat(extra_includes, "#include \"",
                                         prereq_prefix, "parcel.h\"\n", NULL);
        }
        FREEMEM(prereq_parcels);
    }

    const char pattern[] =
        "%s\n"
        "#ifndef CFISH_%sPARCEL_H\n"
        "#define CFISH_%sPARCEL_H 1\n"
        "\n"
        "#ifdef __cplusplus\n"
        "extern \"C\" {\n"
        "#endif\n"
        "\n"
        "%s" // Extra includes.
        "\n"
        "#ifdef %s\n"
        "  #define %sVISIBLE CFISH_EXPORT\n"
        "#else\n"
        "  #define %sVISIBLE CFISH_IMPORT\n"
        "#endif\n"
        "\n"
        "%s" // Typedefs.
        "\n"
        "%s" // Class singletons.
        "\n"
        "%s" // Extra definitions.
        "%sVISIBLE void\n"
        "%sbootstrap_internal(int force);\n"
        "\n"
        "%sVISIBLE void\n"
        "%sbootstrap_parcel(void);\n"
        "\n"
        "void\n"
        "%sinit_parcel(void);\n"
        "\n"
        "#ifdef __cplusplus\n"
        "}\n"
        "#endif\n"
        "\n"
        "#endif /* CFISH_%sPARCEL_H */\n"
        "\n"
        "%s\n"
        "\n";
    char *file_content
        = CFCUtil_sprintf(pattern, self->c_header, PREFIX, PREFIX,
                          extra_includes, privacy_sym, PREFIX, PREFIX,
                          typedefs, class_decls, extra_defs, PREFIX, prefix,
                          PREFIX, prefix, prefix, PREFIX, self->c_footer);

    // Unlink then write file.
    const char *inc_dest = CFCHierarchy_get_include_dest(hierarchy);
    char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "%sparcel.h", inc_dest,
                                     prefix);
    remove(filepath);
    CFCUtil_write_file(filepath, file_content, strlen(file_content));
    FREEMEM(filepath);

    FREEMEM(typedefs);
    FREEMEM(class_decls);
    FREEMEM(extra_defs);
    FREEMEM(extra_includes);
    FREEMEM(file_content);
}

static void
S_write_parcel_c(CFCBindCore *self, CFCParcel *parcel) {
    CFCHierarchy *hierarchy = self->hierarchy;
    const char   *prefix    = CFCParcel_get_prefix(parcel);

    // Aggregate C code for the parcel.
    char *privacy_syms = CFCUtil_strdup("");
    char *includes     = CFCUtil_strdup("");
    char *c_data       = CFCUtil_strdup("");
    CFCBindSpecs *specs = CFCBindSpecs_new();
    CFCClass **ordered = CFCHierarchy_ordered_classes(hierarchy);

    for (int i = 0; ordered[i] != NULL; i++) {
        CFCClass *klass = ordered[i];
        const char *class_prefix = CFCClass_get_prefix(klass);
        if (strcmp(class_prefix, prefix) != 0) { continue; }

        const char *include_h = CFCClass_include_h(klass);
        includes = CFCUtil_cat(includes, "#include \"", include_h,
                               "\"\n", NULL);

        CFCBindClass *class_binding = CFCBindClass_new(klass);

        char *class_c_data = CFCBindClass_to_c_data(class_binding);
        c_data = CFCUtil_cat(c_data, class_c_data, "\n", NULL);
        FREEMEM(class_c_data);

        CFCBindSpecs_add_class(specs, klass);

        const char *privacy_sym = CFCClass_privacy_symbol(klass);
        privacy_syms = CFCUtil_cat(privacy_syms, "#define ",
                                   privacy_sym, "\n", NULL);

        CFCBase_decref((CFCBase*)class_binding);
    }

    char *spec_defs      = CFCBindSpecs_defs(specs);
    char *spec_init_func = CFCBindSpecs_init_func_def(specs);
    FREEMEM(ordered);

    char *prereq_bootstrap = CFCUtil_strdup("");
    CFCParcel **prereq_parcels = CFCParcel_prereq_parcels(parcel);
    for (size_t i = 0; prereq_parcels[i]; ++i) {
        const char *prereq_prefix = CFCParcel_get_prefix(prereq_parcels[i]);
        prereq_bootstrap = CFCUtil_cat(prereq_bootstrap, "    ", prereq_prefix,
                                       "bootstrap_internal(0);\n", NULL);
    }
    FREEMEM(prereq_parcels);

    char pattern[] =
        "%s\n"
        "\n"
        "#include <stdio.h>\n"
        "#include <stdlib.h>\n"
        "\n"
        "%s"
        "\n"
        "#include \"Clownfish/Class.h\"\n"   // Needed for bootstrap.
        "#include \"Clownfish/Err.h\"\n"     // Needed for abstract methods.
        "%s\n"
        "\n"
        "%s\n"
        "\n"
        "/* ClassSpec and MethSpec structs for initialization.\n"
        " */\n"
        "\n"
        "%s" // spec_defs
        "\n"
        "/* Code to initialize ClassSpec and MethSpec structs.\n"
        " */\n"
        "\n"
        "%s" // spec_init_func
        "\n"
        "void\n"
        "%sbootstrap_internal(int force) {\n"
        "    static int bootstrapped = 0;\n"
        "    if (bootstrapped && !force) { return; }\n"
        "    S_bootstrap_specs();\n"
        "    %sinit_parcel();\n"
        "    bootstrapped = 1;\n"
        "}\n"
        "\n"
        "void\n"
        "%sbootstrap_parcel(void) {\n"
        "%s" // Bootstrap prerequisite parcels.
        "    %sbootstrap_internal(0);\n"
        "}\n"
        "\n"
        "%s\n";
    char *file_content
        = CFCUtil_sprintf(pattern, self->c_header, privacy_syms, includes,
                          c_data, spec_defs, spec_init_func, prefix, prefix,
                          prefix, prereq_bootstrap, prefix, self->c_footer);

    // Unlink then open file.
    const char *src_dest = CFCHierarchy_get_source_dest(hierarchy);
    char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "%sparcel.c", src_dest,
                                     prefix);
    remove(filepath);
    CFCUtil_write_file(filepath, file_content, strlen(file_content));
    FREEMEM(filepath);

    CFCBase_decref((CFCBase*)specs);
    FREEMEM(privacy_syms);
    FREEMEM(includes);
    FREEMEM(c_data);
    FREEMEM(spec_defs);
    FREEMEM(spec_init_func);
    FREEMEM(prereq_bootstrap);
    FREEMEM(file_content);
}

/* Write the "cfish_platform.h" header file, which contains platform-specific
 * definitions.
 */
static void
S_write_platform_h(CFCBindCore *self) {
    char *feature_defs = S_charmony_feature_defines();
    char *string_defs  = S_charmony_string_defines();
    char *stdbool_defs = S_charmony_stdbool_defines();
    char *stdint_defs  = S_charmony_stdint_defines();
    char *alloca_defs  = S_charmony_alloca_defines();

    const char pattern[] =
        "%s"
        "\n"
        "#ifndef CFISH_PLATFORM_H\n"
        "#define CFISH_PLATFORM_H 1\n"
        "\n"
        "#ifdef __cplusplus\n"
        "extern \"C\" {\n"
        "#endif\n"
        "\n"
        "%s"
        "%s"
        "\n"
        "%s"
        "%s"
        "\n"
        "%s"
        "\n"
        "#ifdef __cplusplus\n"
        "}\n"
        "#endif\n"
        "\n"
        "#endif /* CFISH_PLATFORM_H */\n"
        "\n"
        "%s"
        "\n";
    char *file_content
        = CFCUtil_sprintf(pattern, self->c_header, feature_defs, string_defs,
                          stdbool_defs, stdint_defs, alloca_defs,
                          self->c_footer);

    // Unlink then write file.
    const char *inc_dest = CFCHierarchy_get_include_dest(self->hierarchy);
    char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "cfish_platform.h",
                                     inc_dest);
    remove(filepath);
    CFCUtil_write_file(filepath, file_content, strlen(file_content));
    FREEMEM(filepath);

    FREEMEM(feature_defs);
    FREEMEM(string_defs);
    FREEMEM(stdbool_defs);
    FREEMEM(stdint_defs);
    FREEMEM(alloca_defs);
    FREEMEM(file_content);
}

static char*
S_charmony_feature_defines(void) {
    char *defines = CFCUtil_strdup("");

#ifdef CHY_LITTLE_END
    // Needed by NumberUtils.cfh.
    defines = CFCUtil_cat(defines, "#define CFISH_LITTLE_END\n", NULL);
#endif
#ifdef CHY_BIG_END
    // Needed by NumberUtils.cfh.
    defines = CFCUtil_cat(defines, "#define CFISH_BIG_END\n", NULL);
#endif
#ifdef CHY_HAS_FUNC_MACRO
    // Needed by Err.cfh.
    defines = CFCUtil_cat(defines, "#define CFISH_HAS_FUNC_MACRO\n", NULL);
#endif
#ifdef CHY_HAS_VARIADIC_MACROS
    // Needed by Err.cfh.
    defines = CFCUtil_cat(defines, "#define CFISH_HAS_VARIADIC_MACROS\n",
                          NULL);
#endif
#ifdef CHY_HAS_ISO_VARIADIC_MACROS
    // Needed by Err.cfh.
    defines = CFCUtil_cat(defines, "#define CFISH_HAS_ISO_VARIADIC_MACROS\n",
                          NULL);
#endif
#ifdef CHY_HAS_GNUC_VARIADIC_MACROS
    // Needed by Err.cfh.
    defines = CFCUtil_cat(defines, "#define CFISH_HAS_GNUC_VARIADIC_MACROS\n",
                          NULL);
#endif

    return defines;
}

static char*
S_charmony_string_defines(void) {
    const char *pattern =
        "#define CFISH_INLINE %s\n"
        "#define CFISH_EXPORT %s\n"
        "#define CFISH_IMPORT %s\n"
        "#define CFISH_SIZEOF_CHAR %s\n"
        "#define CFISH_SIZEOF_SHORT %s\n"
        "#define CFISH_SIZEOF_INT %s\n"
        "#define CFISH_SIZEOF_LONG %s\n"
        "#define CFISH_SIZEOF_SIZE_T %s\n"
        "#define CFISH_FUNC_MACRO %s\n"
        "#define CFISH_U64_TO_DOUBLE(x) %s\n";
    char *defines
        = CFCUtil_sprintf(pattern,
                          XSTRING(CHY_INLINE),
                          XSTRING(CHY_EXPORT),
                          XSTRING(CHY_IMPORT),
                          XSTRING(CHY_SIZEOF_CHAR),
                          XSTRING(CHY_SIZEOF_SHORT),
                          XSTRING(CHY_SIZEOF_INT),
                          XSTRING(CHY_SIZEOF_LONG),
                          XSTRING(CHY_SIZEOF_SIZE_T),
                          XSTRING(CHY_FUNC_MACRO),
                          XSTRING(CHY_U64_TO_DOUBLE(x)));

    return defines;
}

static char*
S_charmony_stdbool_defines(void) {
#ifdef CHY_HAS_STDBOOL_H
    const char *defines = "#include <stdbool.h>\n";
#else
    const char *defines =
        "#if (!defined(__cplusplus) && !defined(CFISH_HAS_STDBOOL))\n"
        "  typedef int bool;\n"
        "  #ifndef true\n"
        "    #define true 1\n"
        "  #endif\n"
        "  #ifndef false\n"
        "    #define false 0\n"
        "  #endif\n"
        "#endif\n";
#endif

    return CFCUtil_strdup(defines);
}

static char*
S_charmony_stdint_defines(void) {
#ifdef CHY_HAS_STDINT_H
    return CFCUtil_strdup("#include <stdint.h>\n");
#else
    const char *pattern =
        "#ifndef CFISH_HAS_STDINT\n"
        "  typedef %s int8_t;\n"
        "  typedef %s uint8_t;\n"
        "  typedef %s int16_t;\n"
        "  typedef %s uint16_t;\n"
        "  typedef %s int32_t;\n"
        "  typedef %s uint32_t;\n"
        "  typedef %s int64_t;\n"
        "  typedef %s uint64_t;\n"
        "#endif\n";
    return CFCUtil_sprintf(pattern,
                           XSTRING(CHY_INT8_T),  XSTRING(CHY_UINT8_T),
                           XSTRING(CHY_INT16_T), XSTRING(CHY_UINT16_T),
                           XSTRING(CHY_INT32_T), XSTRING(CHY_UINT32_T),
                           XSTRING(CHY_INT64_T), XSTRING(CHY_UINT64_T));
#endif
}

static char*
S_charmony_alloca_defines(void) {
    char *defines = CFCUtil_strdup("");

#if defined(CHY_HAS_ALLOCA_H)
    defines = CFCUtil_cat(defines, "#include <alloca.h>\n", NULL);
#elif defined(CHY_HAS_MALLOC_H)
    defines = CFCUtil_cat(defines, "#include <malloc.h>\n", NULL);
#elif defined(CHY_ALLOCA_IN_STDLIB_H)
    defines = CFCUtil_cat(defines, "#include <stdlib.h>\n", NULL);
#endif

    defines = CFCUtil_cat(defines, "#define cfish_alloca ",
                          XSTRING(chy_alloca), "\n", NULL);

    return defines;
}

void
CFCBindCore_copy_headers(CFCBindCore *self, const char *dest_dir) {
    char *default_dest = NULL;

    if (dest_dir == NULL || dest_dir[0] == '\0') {
        default_dest = CFCUtil_sprintf("%s" CHY_DIR_SEP "share" CHY_DIR_SEP
                                       "clownfish" CHY_DIR_SEP "include",
                                       CFCHierarchy_get_dest(self->hierarchy));
        dest_dir = default_dest;
    }

    /* Copy .cfp files. */
    CFCParcel **parcels = CFCParcel_all_parcels();
    for (size_t i = 0; parcels[i] != NULL; i++) {
        CFCParcel *parcel = parcels[i];
        if (CFCParcel_included(parcel) || !CFCParcel_is_installed(parcel)) {
            continue;
        }

        const char *source_path = CFCParcel_get_cfp_path(parcel);
        const char *parcel_name = CFCParcel_get_name(parcel);
        CFCVersion *version     = CFCParcel_get_version(parcel);
        const char *vstring     = CFCVersion_get_vstring(version);

        char *dest_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s" CHY_DIR_SEP
                                          "%s" CHY_DIR_SEP "parcel.json",
                                          dest_dir, parcel_name, vstring);

        size_t len = 0;
        char *content = CFCUtil_slurp_text(source_path, &len);
        CFCUtil_write_file(dest_path, content, len);

        FREEMEM(content);
        FREEMEM(dest_path);
    }

    /* Copy .cfh files. */
    CFCFile **files = CFCHierarchy_files(self->hierarchy);
    for (size_t i = 0; files[i] != NULL; i++) {
        CFCFile *file = files[i];
        if (CFCFile_included(file)) { continue; }
        CFCParcel *parcel = CFCFile_get_parcel(file);
        if (!CFCParcel_is_installed(parcel)) { continue; }

        const char *source_path = CFCFile_get_path(file);
        const char *parcel_name = CFCParcel_get_name(parcel);
        CFCVersion *version     = CFCParcel_get_version(parcel);
        const char *vstring     = CFCVersion_get_vstring(version);
        const char *path_part   = CFCFile_get_path_part(file);

        char *dest_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s" CHY_DIR_SEP
                                          "%s" CHY_DIR_SEP "%s.cfh", dest_dir,
                                          parcel_name, vstring, path_part);

        size_t len = 0;
        char *content = CFCUtil_slurp_text(source_path, &len);
        CFCUtil_write_file(dest_path, content, len);

        FREEMEM(content);
        FREEMEM(dest_path);
    }

    FREEMEM(default_dest);
}

void
CFCBindCore_write_host_data_json(CFCBindCore *self, const char *dest_dir,
                                 const char *host_lang) {
    CFCParcel **parcels = CFCParcel_all_parcels();

    for (size_t i = 0; parcels[i] != NULL; i++) {
        CFCParcel *parcel = parcels[i];
        if (!CFCParcel_included(parcel) && CFCParcel_is_installed(parcel)) {
            S_write_host_data_json(self, parcel, dest_dir, host_lang);
        }
    }
}

static void
S_write_host_data_json(CFCBindCore *self, CFCParcel *parcel,
                       const char *dest_dir, const char *host_lang) {
    const char *prefix      = CFCParcel_get_prefix(parcel);
    const char *parcel_name = CFCParcel_get_name(parcel);
    CFCVersion *version     = CFCParcel_get_version(parcel);
    const char *vstring     = CFCVersion_get_vstring(version);
    char       *json_pairs  = CFCUtil_strdup("");

    const char *host_module_name = CFCParcel_get_host_module_name(parcel);
    if (host_module_name != NULL) {
        const char *pattern = "    \"host_module\": \"%s\"";
        char *pair = CFCUtil_sprintf(pattern, host_module_name);
        json_pairs = CFCUtil_cat(json_pairs, pair, NULL);
        FREEMEM(pair);
    }

    char *classes_json = CFCUtil_strdup("");
    CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy);

    for (size_t i = 0; ordered[i] != NULL; i++) {
        CFCClass *klass = ordered[i];
        const char *class_prefix = CFCClass_get_prefix(klass);
        if (strcmp(class_prefix, prefix) != 0) { continue; }

        CFCBindClass *class_binding = CFCBindClass_new(klass);

        char *class_json = CFCBindClass_host_data_json(class_binding);
        if (class_json[0] != '\0') {
            const char *sep = classes_json[0] == '\0' ? "" : ",\n";
            classes_json = CFCUtil_cat(classes_json, sep, class_json, NULL);
        }

        FREEMEM(class_json);
        CFCBase_decref((CFCBase*)class_binding);
    }
    FREEMEM(ordered);

    if (classes_json[0] != '\0') {
        const char *pattern =
            "    \"classes\": {\n"
            "%s\n"
            "    }";
        char *pair = CFCUtil_sprintf(pattern, classes_json);
        const char *sep = json_pairs[0] == '\0' ? "" : ",\n";
        json_pairs = CFCUtil_cat(json_pairs, sep, pair, NULL);
        FREEMEM(pair);
    }

    char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s" CHY_DIR_SEP "%s"
                                     CHY_DIR_SEP "parcel_%s.json", dest_dir,
                                     parcel_name, vstring, host_lang);
    remove(filepath);

    if (json_pairs[0] != '\0') {
        const char *pattern =
            "{\n"
            "%s\n"
            "}\n";
        char *json = CFCUtil_sprintf(pattern, json_pairs);
        CFCUtil_write_file(filepath, json, strlen(json));
        FREEMEM(json);
    }

    FREEMEM(filepath);
    FREEMEM(classes_json);
    FREEMEM(json_pairs);
}


