#include "codegen_c.h"
#include "flatcc/flatcc_types.h"

/* -DFLATCC_PORTABLE may help if inttypes.h is missing. */
#ifndef PRId64
#include <inttypes.h>
#endif

static int gen_json_printer_pretext(fb_output_t *out)
{
    fprintf(out->fp,
        "#ifndef %s_JSON_PRINTER_H\n"
        "#define %s_JSON_PRINTER_H\n",
        out->S->basenameup, out->S->basenameup);

    fprintf(out->fp, "\n/* " FLATCC_GENERATED_BY " */\n\n");
    fprintf(out->fp, "#include \"flatcc/flatcc_json_printer.h\"\n");
    fb_gen_c_includes(out, "_json_printer.h", "_JSON_PRINTER_H");
    gen_prologue(out);
    fprintf(out->fp, "\n");
    return 0;
}

static int gen_json_printer_footer(fb_output_t *out)
{
    gen_epilogue(out);
    fprintf(out->fp,
        "#endif /* %s_JSON_PRINTER_H */\n",
        out->S->basenameup);
    return 0;
}

static int gen_json_printer_enum(fb_output_t *out, fb_compound_type_t *ct)
{
    fb_symbol_t *sym;
    fb_member_t *member;
    fb_scoped_name_t snt, snref;
    const char *tp, *tn, *ns;
    int bit_flags;
    uint64_t mask = 0;
    char *constwrap = "";
    char *ut = "";
    fb_scalar_type_t st = ct->type.st;

    fb_clear(snt);
    fb_clear(snref);
    fb_compound_name(ct, &snt);
    tp = scalar_type_prefix(st);
    tn = scalar_type_name(st);
    ns = scalar_type_ns(st, out->nsc);

    bit_flags = !!(ct->metadata_flags & fb_f_bit_flags);
    if (bit_flags) {
        switch (ct->size) {
        case 1:
            mask = UINT8_MAX, constwrap = "UINT8_C", ut = "uint8_t";
            break;
        case 2:
            mask = UINT16_MAX, constwrap = "UINT16_C", ut = "uint16_t";
            break;
        case 4:
            mask = UINT32_MAX, constwrap = "UINT32_C", ut = "uint32_t";
            break;
        default:
            mask = UINT64_MAX, constwrap = "UINT64_C", ut = "uint64_t";
            break;
        }
        for (sym = ct->members; sym; sym = sym->link) {
            member = (fb_member_t *)sym;
            switch (member->value.type) {
            case vt_uint:
                mask &= ~(uint64_t)member->value.u;
                break;
            case vt_int:
                mask &= ~(uint64_t)member->value.i;
                break;
            case vt_bool:
                mask &= ~(uint64_t)member->value.b;
                break;
            }
        }
    }

    fprintf(out->fp,
            "static void %s_print_json_enum(flatcc_json_printer_t *ctx, %s%s v)\n{\n",
            snt.text, ns, tn);
    if (bit_flags) {
        if (strcmp(ut, tn)) {
            fprintf(out->fp, "    %s x = (%s)v;\n", ut, ut);
        } else {
            fprintf(out->fp, "    %s x = v;\n", ut);
        }
        fprintf(out->fp,
                "    int multiple = 0 != (x & (x - 1));\n"
                "    int i = 0;\n");

        fprintf(out->fp, "\n");
        /*
         * If the value is not entirely within the known bit flags, print as
         * a number.
         */
        if (mask) {
            fprintf(out->fp,
                    "    if ((x & %s(0x%"PRIx64")) || x == 0) {\n"
                    "        flatcc_json_printer_%s(ctx, v);\n"
                    "        return;\n"
                    "    }\n",
                   constwrap, mask, tp);
        }
        /*
         * Test if multiple bits set. We may have a configuration option
         * that requires multiple flags to be quoted like `color: "Red Green"`
         * but unquoted if just a single value like `color: Green`.
         *
         * The index `i` is used to add space separators much like an
         * index is provided for struct members to handle comma.
         */
        fprintf(out->fp, "    flatcc_json_printer_delimit_enum_flags(ctx, multiple);\n");
        for (sym = ct->members; sym; sym = sym->link) {
            member = (fb_member_t *)sym;
            switch (member->value.type) {
            case vt_uint:
                fprintf(out->fp, "    if (x & %s(0x%"PRIx64")) flatcc_json_printer_enum_flag(ctx, i++, \"%.*s\", %ld);\n",
                        constwrap, member->value.u, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
            case vt_int:
                fprintf(out->fp, "    if (x & %s(0x%"PRIx64")) flatcc_json_printer_enum_flag(ctx, i++, \"%.*s\", %ld);\n",
                        constwrap, (uint64_t)member->value.i, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
            case vt_bool:
                fprintf(out->fp, "    if (x & %s(0x%"PRIx64")) flatcc_json_printer_enum_flag(ctx, i++, \"%.*s\", %ld);\n",
                        constwrap, (uint64_t)member->value.b, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
            default:
                gen_panic(out, "internal error: unexpected value type for enum json_print");
                break;
            }
        }
        fprintf(out->fp, "    flatcc_json_printer_delimit_enum_flags(ctx, multiple);\n");
    } else {
        fprintf(out->fp, "\n    switch (v) {\n");
        for (sym = ct->members; sym; sym = sym->link) {
            member = (fb_member_t *)sym;
            switch (member->value.type) {
            case vt_uint:
                fprintf(out->fp, "    case %s(%"PRIu64"): flatcc_json_printer_enum(ctx, \"%.*s\", %ld); break;\n",
                        constwrap, member->value.u, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
            case vt_int:
                fprintf(out->fp, "    case %s(%"PRId64"): flatcc_json_printer_enum(ctx, \"%.*s\", %ld); break;\n",
                        constwrap, member->value.i, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
            case vt_bool:
                fprintf(out->fp, "    case %s(%u): flatcc_json_printer_enum(ctx, \"%.*s\", %ld); break;\n",
                        constwrap, member->value.b, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
            default:
                gen_panic(out, "internal error: unexpected value type for enum json_print");
                break;
            }
        }
        fprintf(out->fp,
                "    default: flatcc_json_printer_%s(ctx, v); break;\n"
                "    }\n",
                tp);
    }
    fprintf(out->fp, "}\n\n");
    return 0;
}

static int gen_json_printer_union_type(fb_output_t *out, fb_compound_type_t *ct)
{
    fb_symbol_t *sym;
    fb_member_t *member;
    fb_scoped_name_t snt;

    fb_clear(snt);
    fb_compound_name(ct, &snt);

    fprintf(out->fp,
            "static void %s_print_json_union_type(flatcc_json_printer_t *ctx, flatbuffers_utype_t type)\n"
            "{\n    switch (type) {\n",
            snt.text);
    for (sym = ct->members; sym; sym = sym->link) {
        member = (fb_member_t *)sym;
        if (member->type.type == vt_missing) {
            continue;
        }
        fprintf(out->fp,
                "    case %u:\n"
                "        flatcc_json_printer_enum(ctx, \"%.*s\", %ld);\n"
                "        break;\n",
                (unsigned)member->value.u, (int)sym->ident->len, sym->ident->text, sym->ident->len);
    }
    fprintf(out->fp,
            "    default:\n"
            "        flatcc_json_printer_enum(ctx, \"NONE\", 4);\n"
            "        break;\n");
    fprintf(out->fp,
            "    }\n}\n\n");
    return 0;
}

static int gen_json_printer_union_member(fb_output_t *out, fb_compound_type_t *ct)
{
    fb_symbol_t *sym;
    fb_member_t *member;
    fb_scoped_name_t snt, snref;

    fb_clear(snt);
    fb_clear(snref);
    fb_compound_name(ct, &snt);

    fprintf(out->fp,
            "static void %s_print_json_union(flatcc_json_printer_t *ctx, flatcc_json_printer_union_descriptor_t *ud)\n"
            "{\n    switch (ud->type) {\n",
            snt.text);
    for (sym = ct->members; sym; sym = sym->link) {
        member = (fb_member_t *)sym;
        switch (member->type.type) {
        case vt_missing:
            continue;
        case vt_compound_type_ref:
            fb_compound_name(member->type.ct, &snref);
            switch (member->type.ct->symbol.kind) {
            case fb_is_table:
                fprintf(out->fp,
                        "    case %u:\n"
                        "        flatcc_json_printer_union_table(ctx, ud, %s_print_json_table);\n"
                        "        break;\n",
                        (unsigned)member->value.u, snref.text);
                continue;
            case fb_is_struct:
                fprintf(out->fp,
                        "    case %u:\n"
                        "        flatcc_json_printer_union_struct(ctx, ud, %s_print_json_struct);\n"
                        "        break;\n",
                        (unsigned)member->value.u, snref.text);
                continue;
            default:
                gen_panic(out, "internal error: unexpected union type\n");
                return -1;
            }
        case vt_string_type:
            fprintf(out->fp,
                    "    case %u:\n"
                    "        flatcc_json_printer_union_string(ctx, ud);\n"
                    "        break;\n",
                        (unsigned)member->value.u);
            continue;
        default:
            gen_panic(out, "internal error: unexpected union type\n");
            return -1;
        }
    }
    fprintf(out->fp,
                "    default:\n"
                "        break;\n");
    fprintf(out->fp,
            "    }\n}\n\n");
    return 0;
}

static int gen_json_printer_union(fb_output_t *out, fb_compound_type_t *ct)
{
    gen_json_printer_union_type(out, ct);
    gen_json_printer_union_member(out, ct);
    return 0;
}

static int gen_json_printer_struct(fb_output_t *out, fb_compound_type_t *ct)
{
    fb_symbol_t *sym;
    fb_member_t *member;
    fb_scoped_name_t snt, snref;
    int index = 0;
    const char *tp;

    fb_clear(snt);
    fb_clear(snref);
    fb_compound_name(ct, &snt);

    fprintf(out->fp,
            "static void %s_print_json_struct(flatcc_json_printer_t *ctx, const void *p)\n"
            "{\n",
            snt.text);
    for (sym = ct->members; sym; ++index, sym = sym->link) {
        member = (fb_member_t *)sym;
        if (member->metadata_flags & fb_f_deprecated) {
            continue;
        }
        switch (member->type.type) {
        case vt_scalar_type:
            tp = scalar_type_prefix(member->type.st);
            fprintf(
                    out->fp,
                    "    flatcc_json_printer_%s_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld);\n",
                    tp, index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len);
            break;
        case vt_fixed_array_type:
            tp = scalar_type_prefix(member->type.st);
            fprintf(
                    out->fp,
                    "    flatcc_json_printer_%s_array_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld, %d);\n",
                    tp, index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len, member->type.len);
            break;
        case vt_fixed_array_compound_type_ref:
            fb_compound_name(member->type.ct, &snref);
            switch (member->type.ct->symbol.kind) {
            case fb_is_enum:
#if FLATCC_JSON_PRINT_MAP_ENUMS
                tp = scalar_type_prefix(member->type.ct->type.st);
                fprintf(out->fp,
                        "    flatcc_json_printer_%s_enum_array_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld, %d, %s_print_json_enum);\n",
                        tp, index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len, member->type.len, snref.text);
                break;
#else
                tp = scalar_type_prefix(member->type.ct->type.st);
                fprintf(
                        out->fp,
                        "    flatcc_json_printer_%s_array_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld, %d);\n",
                        tp, index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len, member->type.len);
                break;
#endif
            case fb_is_struct:
                fprintf(out->fp,
                        "    flatcc_json_printer_embedded_struct_array_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld, %"PRIu64", %"PRIu64", %s_print_json_struct);\n",
                        index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len,
                        (uint64_t)member->type.ct->size, (uint64_t)member->type.len, snref.text);
            }
            break;
        case vt_compound_type_ref:
            fb_compound_name(member->type.ct, &snref);
            switch (member->type.ct->symbol.kind) {
            case fb_is_enum:
#if FLATCC_JSON_PRINT_MAP_ENUMS
                tp = scalar_type_prefix(member->type.ct->type.st);
                fprintf(out->fp,
                        "    flatcc_json_printer_%s_enum_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld, %s_print_json_enum);\n",
                        tp, index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                break;
#else
                tp = scalar_type_prefix(member->type.ct->type.st);
                fprintf(
                        out->fp,
                        "    flatcc_json_printer_%s_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld);\n",
                        tp, index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
#endif
            case fb_is_struct:
                fprintf(out->fp,
                        "    flatcc_json_printer_embedded_struct_field(ctx, %d, p, %"PRIu64", \"%.*s\", %ld, %s_print_json_struct);\n",
                        index, (uint64_t)member->offset, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                break;
            }
            break;
        }
    }
    fprintf(out->fp, "}\n\n");
    fprintf(out->fp,
            "static inline int %s_print_json_as_root(flatcc_json_printer_t *ctx, const void *buf, size_t bufsiz, const char *fid)\n"
            "{\n    return flatcc_json_printer_struct_as_root(ctx, buf, bufsiz, fid, %s_print_json_struct);\n}\n\n",
            snt.text, snt.text);
    return 0;
}

static int gen_json_printer_table(fb_output_t *out, fb_compound_type_t *ct)
{
    fb_symbol_t *sym;
    fb_member_t *member;
    fb_scoped_name_t snt, snref;
    const char *tp;
    int is_optional;
    int ret = 0;

    fb_clear(snt);
    fb_clear(snref);
    fb_compound_name(ct, &snt);

    /* Fields are printed in field id order for consistency across schema version. */
    fprintf(out->fp,
            "static void %s_print_json_table(flatcc_json_printer_t *ctx, flatcc_json_printer_table_descriptor_t *td)\n"
            "{",
            snt.text);

    for (sym = ct->members; sym; sym = sym->link) {
        member = (fb_member_t *)sym;
        sym = &member->symbol;
        if (member->metadata_flags & fb_f_deprecated) {
            continue;
        }
        is_optional = !!(member->flags & fb_fm_optional);
        fprintf(out->fp, "\n    ");
        switch (member->type.type) {
        case vt_scalar_type:
            tp = scalar_type_prefix(member->type.st);
            if (is_optional) {
                fprintf( out->fp,
                    "flatcc_json_printer_%s_optional_field(ctx, td, %"PRIu64", \"%.*s\", %ld);",
                    tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len);
            } else {
                fb_literal_t literal;
                if (!print_literal(member->type.st, &member->value, literal)) return -1;
                fprintf( out->fp,
                    "flatcc_json_printer_%s_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s);",
                    tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, literal);
            }
            break;
        case vt_vector_type:
            if (member->metadata_flags & (fb_f_base64 | fb_f_base64url)) {
                fprintf(out->fp,
                        "flatcc_json_printer_uint8_vector_base64_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %u);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len,
                        !(member->metadata_flags & fb_f_base64));
            } else if (member->nest) {
                fb_compound_name((fb_compound_type_t *)&member->nest->symbol, &snref);
                if (member->nest->symbol.kind == fb_is_table) {
                    /*
                     * Always set fid to 0 since it is difficult to know what is right.
                     * We do know the type from the field attribute.
                     */
                    fprintf(out->fp,
                        "flatcc_json_printer_table_as_nested_root(ctx, td, %"PRIu64", \"%.*s\", %ld, 0, %s_print_json_table);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                } else {
                    /*
                     * Always set fid to 0 since it is difficult to know what is right.
                     * We do know the type from the field attribute.
                     */
                    fprintf(out->fp,
                        "flatcc_json_printer_struct_as_nested_root(ctx, td, %"PRIu64", \"%.*s\", %ld, 0, %s_print_json_struct);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                }
            } else {
                tp = scalar_type_prefix(member->type.st);
                fprintf(out->fp,
                        "flatcc_json_printer_%s_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len);
            }
            break;
        case vt_string_type:
            fprintf(out->fp,
                    "flatcc_json_printer_string_field(ctx, td, %"PRIu64", \"%.*s\", %ld);",
                    member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len);
            break;
        case vt_vector_string_type:
            fprintf(out->fp,
                    "flatcc_json_printer_string_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld);",
                    member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len);
            break;
        case vt_compound_type_ref:
            fb_compound_name(member->type.ct, &snref);
            switch (member->type.ct->symbol.kind) {
            case fb_is_enum:
                tp = scalar_type_prefix(member->type.ct->type.st);
#if FLATCC_JSON_PRINT_MAP_ENUMS
                if (is_optional) {
                    fprintf(out->fp,
                        "flatcc_json_printer_%s_enum_optional_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s_print_json_enum);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                } else {
                    fb_literal_t literal;
                    if (!print_literal(member->type.ct->type.st, &member->value, literal)) return -1;
                    fprintf(out->fp,
                        "flatcc_json_printer_%s_enum_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s, %s_print_json_enum);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, literal, snref.text);
                }
#else
                if (is_optional) {
                    fprintf( out->fp,
                        "flatcc_json_printer_%s_optional_field(ctx, td, %"PRIu64", \"%.*s\", %ld);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                } else {
                    fb_literal_t literal;
                    if (!print_literal(member->type.ct->type.st, &member->value, literal)) return -1;
                    fprintf( out->fp,
                        "flatcc_json_printer_%s_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, literal);
                }
#endif
                break;
            case fb_is_struct:
                fprintf(out->fp,
                        "flatcc_json_printer_struct_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s_print_json_struct);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                break;
            case fb_is_table:
                fprintf(out->fp,
                        "flatcc_json_printer_table_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s_print_json_table);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                break;
            case fb_is_union:
                fprintf(out->fp,
                        "flatcc_json_printer_union_field(ctx, td, %"PRIu64", \"%.*s\", %ld, "
                        "%s_print_json_union_type, %s_print_json_union);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text, snref.text);
                break;
            default:
                gen_panic(out, "internal error: unexpected compound type for table json_print");
                goto fail;
            }
            break;
        case vt_vector_compound_type_ref:
            fb_compound_name(member->type.ct, &snref);
            switch (member->type.ct->symbol.kind) {
            case fb_is_table:
                fprintf(out->fp,
                        "flatcc_json_printer_table_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s_print_json_table);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                break;
            case fb_is_enum:
                tp = scalar_type_prefix(member->type.ct->type.st);
#if FLATCC_JSON_PRINT_MAP_ENUMS
                fprintf(out->fp,
                        "flatcc_json_printer_%s_enum_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %s_print_json_enum);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text);
                break;
#else
                fprintf(out->fp,
                        "flatcc_json_printer_%s_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld);",
                        tp, member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len);
                break;
#endif
            case fb_is_struct:
                fprintf(out->fp,
                        "flatcc_json_printer_struct_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld, %"PRIu64", %s_print_json_struct);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, (uint64_t)member->size, snref.text);
                break;
            case fb_is_union:
                fprintf(out->fp,
                        "flatcc_json_printer_union_vector_field(ctx, td, %"PRIu64", \"%.*s\", %ld, "
                        "%s_print_json_union_type, %s_print_json_union);",
                        member->id, (int)sym->ident->len, sym->ident->text, sym->ident->len, snref.text, snref.text);
                break;

            default:
                gen_panic(out, "internal error: unexpected vector compound type for table json_print");
                goto fail;
            }
            break;
        }
    }
    fprintf(out->fp, "\n}\n\n");
    fprintf(out->fp,
            "static inline int %s_print_json_as_root(flatcc_json_printer_t *ctx, const void *buf, size_t bufsiz, const char *fid)\n"
            "{\n    return flatcc_json_printer_table_as_root(ctx, buf, bufsiz, fid, %s_print_json_table);\n}\n\n",
            snt.text, snt.text);
done:
    return ret;
fail:
    ret = -1;
    goto done;
}

/*
 * Only tables are mutually recursive. Structs are sorted and unions are
 * defined earlier, depending on the table prototypes.
 */
static int gen_json_printer_prototypes(fb_output_t *out)
{
    fb_symbol_t *sym;
    fb_scoped_name_t snt;
    fb_symbol_t *root_type = out->S->root_type.type;

    fb_clear(snt);

    if (root_type)
    switch (root_type->kind) {
    case fb_is_table:
    case fb_is_struct:
        fprintf(out->fp,
                "/*\n"
                " * Prints the default root table or struct from a buffer which must have\n"
                " * the schema declared file identifier, if any. It is also possible to\n"
                " * call the type specific `print_json_as_root` function wich accepts an\n"
                " * optional identifier (or 0) as argument. The printer `ctx` object must\n"
                " * be initialized with the appropriate output type, or it can be 0 which\n"
                " * defaults to stdout. NOTE: `ctx` is not generally allowed to be null, only\n"
                " * here for a simplified interface.\n"
                " */\n");
    fprintf(out->fp,
            "static int %s_print_json(flatcc_json_printer_t *ctx, const char *buf, size_t bufsiz);\n\n",
            out->S->basename);
        break;
    default:
        break;
    }

    for (sym = out->S->symbols; sym; sym = sym->link) {
        switch (sym->kind) {
        case fb_is_union:
            fb_compound_name((fb_compound_type_t *)sym, &snt);
            fprintf(out->fp,
                    "static void %s_print_json_union_type(flatcc_json_printer_t *ctx, flatbuffers_utype_t type);\n"
                    "static void %s_print_json_union(flatcc_json_printer_t *ctx, flatcc_json_printer_union_descriptor_t *ud);\n",
                    snt.text, snt.text);
            break;
        case fb_is_table:
            fb_compound_name((fb_compound_type_t *)sym, &snt);
            fprintf(out->fp,
                    "static void %s_print_json_table(flatcc_json_printer_t *ctx, flatcc_json_printer_table_descriptor_t *td);\n",
                    snt.text);
            break;
        case fb_is_struct:
            fb_compound_name((fb_compound_type_t *)sym, &snt);
            fprintf(out->fp,
                    "static void %s_print_json_struct(flatcc_json_printer_t *ctx, const void *p);\n",
                    snt.text);
            break;
        }
    }
    fprintf(out->fp, "\n");
    return 0;
}

static int gen_json_printer_enums(fb_output_t *out)
{
    fb_symbol_t *sym;

    for (sym = out->S->symbols; sym; sym = sym->link) {
        switch (sym->kind) {
        case fb_is_enum:
            gen_json_printer_enum(out, (fb_compound_type_t *)sym);
        }
    }
    return 0;
}

static int gen_json_printer_unions(fb_output_t *out)
{
    fb_symbol_t *sym;

    for (sym = out->S->symbols; sym; sym = sym->link) {
        switch (sym->kind) {
        case fb_is_union:
            gen_json_printer_union(out, (fb_compound_type_t *)sym);
        }
    }
    return 0;
}

static int gen_json_printer_structs(fb_output_t *out)
{
    fb_symbol_t *sym;

    for (sym = out->S->symbols; sym; sym = sym->link) {
        switch (sym->kind) {
        case fb_is_struct:
            gen_json_printer_struct(out, (fb_compound_type_t *)sym);
        }
    }
    return 0;
}

static int gen_json_printer_tables(fb_output_t *out)
{
    fb_symbol_t *sym;

    for (sym = out->S->symbols; sym; sym = sym->link) {
        switch (sym->kind) {
        case fb_is_table:
            gen_json_printer_table(out, (fb_compound_type_t *)sym);
        }
    }
    return 0;
}

/* Same for structs and tables. */
static int gen_root_type_printer(fb_output_t *out, fb_compound_type_t *ct)
{
    fb_scoped_name_t snt;

    fb_clear(snt);
    fb_compound_name(ct, &snt);

    fprintf(out->fp,
            "static int %s_print_json(flatcc_json_printer_t *ctx, const char *buf, size_t bufsiz)\n",
            out->S->basename);
    fprintf(out->fp,
            "{\n"
            "    flatcc_json_printer_t printer;\n"
            "\n"
            "    if (ctx == 0) {\n"
            "        ctx = &printer;\n"
            "        flatcc_json_printer_init(ctx, 0);\n"
            "    }\n"
            "    return %s_print_json_as_root(ctx, buf, bufsiz, ",
            snt.text);
    if (out->S->file_identifier.type == vt_string) {
        fprintf(out->fp,
                "\"%.*s\");\n",
                out->S->file_identifier.s.len, out->S->file_identifier.s.s);
    } else {
        fprintf(out->fp,
                "0);");
    }
    fprintf(out->fp,
        "}\n\n");
    return 0;
}

static int gen_json_root_printer(fb_output_t *out)
{
    fb_symbol_t *root_type = out->S->root_type.type;

    if (!root_type) {
        return 0;
    }
    if (root_type) {
        switch (root_type->kind) {
        case fb_is_table:
        case fb_is_struct:
            return gen_root_type_printer(out, (fb_compound_type_t *)root_type);
        default:
            break;
        }
    }
    return 0;
}

int fb_gen_c_json_printer(fb_output_t *out)
{
    gen_json_printer_pretext(out);
    gen_json_printer_prototypes(out);
    gen_json_printer_enums(out);
    gen_json_printer_unions(out);
    gen_json_printer_structs(out);
    gen_json_printer_tables(out);
    gen_json_root_printer(out);
    gen_json_printer_footer(out);
    return 0;
}
