/********************************************************************
** Copyright (c) 2018-2020 Guan Wenliang
** This file is part of the Berry default interpreter.
** skiars@qq.com, https://github.com/Skiars/berry
** See Copyright Notice in the LICENSE file or at
** https://github.com/Skiars/berry/blob/master/LICENSE
********************************************************************/
#include "be_object.h"
#include "be_module.h"
#include "be_string.h"
#include "be_vector.h"
#include "be_class.h"
#include "be_list.h"
#include "be_debug.h"
#include "be_map.h"
#include "be_vm.h"
#include "be_decoder.h"
#include "be_sys.h"
#include "be_mem.h"
#include "be_byteslib.h"
#include "be_gc.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <inttypes.h>

extern const bclass be_class_list;
extern const bclass be_class_map;
extern const bclass be_class_bytes;

#if BE_USE_SOLIDIFY_MODULE
#include <inttypes.h>

#define be_builtin_count(vm) \
    be_vector_count(&(vm)->gbldesc.builtin.vlist)

#ifndef INST_BUF_SIZE
#define INST_BUF_SIZE   768
#endif

#define logfmt(...)                                     \
    do {                                                \
        char __lbuf[INST_BUF_SIZE];                     \
        snprintf(__lbuf, sizeof(__lbuf), __VA_ARGS__);  \
        if (fout) {                                     \
            be_fwrite(fout, __lbuf, strlen(__lbuf));    \
        } else {                                        \
            be_writestring(__lbuf);                     \
        }                                               \
    } while (0)

/* minimal version without formatting and without size limit */
#define lognofmt(__s)                                   \
    do {                                                \
        if (fout) {                                     \
            be_fwrite(fout, __s, strlen(__s));          \
        } else {                                        \
            be_writestring(__s);                        \
        }                                               \
    } while (0)

/********************************************************************\
 * Encode string to identifiers
 * 
 * `_X` is used as an escape marker
\********************************************************************/
static unsigned toidentifier_length(const char *s)
{
    unsigned len = 1;
    const char * p = s;
    while (*p) {
        if (p[0] == '_' && p[1] != '\0' && p[1] == 'X') {
            len += 3;
            p += 2;
        } else if (isalnum(p[0]) || p[0] == '_') {
            p++;
            len++;
        } else {        // escape
            p++;
            len += 4;
        }
    }
    return len;
}

inline static char hexdigit(int v)
{
    v = v & 0xF;
    if (v >= 10)    return v - 10 + 'A';
    return v + '0';
}

static void toidentifier(char *to, const char *p)
{
    while (*p) {
        if (p[0] == '_' && p[1] == 'X') {
            to[0] = '_';
            to[1] = 'X';
            to[2] = '_';
            p += 2;
            to += 3;
        } else if (isalnum(p[0]) || p[0] == '_') {
            *to = *p;
            to++;
            p++;
        } else {        // escape
            to[0] = '_';
            to[1] = 'X';
            to[2] = hexdigit((*p & 0xF0) >> 4);
            to[3] = hexdigit(*p & 0x0F);
            p++;
            to += 4;
        }
    }
    *to = 0;      // final NULL
}

static void m_solidify_bvalue(bvm *vm, bbool str_literal, const bvalue * value, const char *prefix_name, const char *key, void* fout);

#if BE_USE_COMPACT_MAP
static void m_solidify_map_value(bvm *vm, bbool str_literal, const bvalue * value, const char *prefix_name, const char *key, void* fout);
static void m_solidify_kval(bvm *vm, bbool str_literal, const bvalue * value, const char *prefix_name, const char *key, void* fout);
#endif

static void m_solidify_map(bvm *vm, bbool str_literal, bmap * map, const char *prefix_name, void* fout)
{
    // compact first
    be_map_compact(vm, map);
    
    logfmt("    be_nested_map(%i,\n", map->count);

#if BE_USE_COMPACT_MAP
    logfmt("    ( (struct bmapnode*) &(const bmapnodec[]) {\n");
    for (int i = 0; i < map->size; i++) {
        bmapnode * node = &map->slots[i];
        if (node->key.type == BE_NIL) {
            continue;   /* key not used */
        }
        int key_next = node->key.next;
        if (0xFFFFFF == key_next) {
            key_next = -1;      /* more readable */
        }
        if (node->key.type == BE_STRING) {
            /* convert the string literal to identifier */
            const char * key = str(node->key.v.s);
            size_t id_len = toidentifier_length(key);
            char id_buf[id_len];
            toidentifier(id_buf, key);
            if (!str_literal) {
                logfmt("        { be_ckey(%s, %i), ", id_buf, key_next);
            } else {
                logfmt("        { be_ckey_weak(%s, %i), ", id_buf, key_next);
            }
            m_solidify_map_value(vm, str_literal, &node->value, prefix_name, str(node->key.v.s), fout);
        } else if (node->key.type == BE_INT) {
#if BE_INTGER_TYPE == 2
            logfmt("        { be_ckey_int(%lli, %i), ", node->key.v.i, key_next);
#else
            logfmt("        { be_ckey_int(%i, %i), ", node->key.v.i, key_next);
#endif
            m_solidify_map_value(vm, str_literal, &node->value, prefix_name, NULL, fout);
        } else {
            char error[64];
            snprintf(error, sizeof(error), "Unsupported type in key: %i", node->key.type);
            be_raise(vm, "internal_error", error);
        }

        logfmt(" },\n");
    }
    logfmt("    }))");
#else
    logfmt("    ( (struct bmapnode*) &(const bmapnode[]) {\n");
    for (int i = 0; i < map->size; i++) {
        bmapnode * node = &map->slots[i];
        if (node->key.type == BE_NIL) {
            continue;   /* key not used */
        }
        int key_next = node->key.next;
        if (0xFFFFFF == key_next) {
            key_next = -1;      /* more readable */
        }
        if (node->key.type == BE_STRING) {
            /* convert the string literal to identifier */
            const char * key = str(node->key.v.s);
            size_t id_len = toidentifier_length(key);
            char id_buf[id_len];
            toidentifier(id_buf, key);
            if (!str_literal) {
                logfmt("        { be_const_key(%s, %i), ", id_buf, key_next);
            } else {
                logfmt("        { be_const_key_weak(%s, %i), ", id_buf, key_next);
            }
            m_solidify_bvalue(vm, str_literal, &node->value, prefix_name, str(node->key.v.s), fout);
        } else if (node->key.type == BE_INT) {
#if BE_INTGER_TYPE == 2
            logfmt("        { be_const_key_int(%lli, %i), ", node->key.v.i, key_next);
#else
            logfmt("        { be_const_key_int(%i, %i), ", node->key.v.i, key_next);
#endif
            m_solidify_bvalue(vm, str_literal, &node->value, prefix_name, NULL, fout);
        } else {
            char error[64];
            snprintf(error, sizeof(error), "Unsupported type in key: %i", node->key.type);
            be_raise(vm, "internal_error", error);
        }

        logfmt(" },\n");
    }
    logfmt("    }))");        // TODO need terminal comma?
#endif

}

static void m_solidify_list(bvm *vm, bbool str_literal, const blist * list, const char *prefix_name, void* fout)
{
    logfmt("    be_nested_list(%i,\n", list->count);

    logfmt("    ( (struct bvalue*) &(const bvalue[]) {\n");
    for (int i = 0; i < list->count; i++) {
        logfmt("        ");
        m_solidify_bvalue(vm, str_literal, &list->data[i], prefix_name, "", fout);
        logfmt(",\n");
    }
    logfmt("    }))");        // TODO need terminal comma?
}

#if BE_USE_COMPACT_MAP
/* Emit one compact map node's value: the value type byte, then the value
 * payload (a `union bvaldata` initializer). Mirrors the legacy m_solidify_bvalue
 * but splits type from payload to match the bmapnodec layout. */
static void m_solidify_map_value(bvm *vm, bbool str_literal, const bvalue * value, const char *prefix_name, const char *key, void* fout)
{
    int type = var_primetype(value);
    /* --- value type byte --- */
    const char *tname;
    switch (type) {
    case BE_NIL:      tname = "BE_NIL";      break;
    case BE_BOOL:     tname = "BE_BOOL";     break;
    case BE_INT:      tname = "BE_INT";      break;
    case BE_INDEX:    tname = "BE_INDEX";    break;
    case BE_REAL:     tname = "BE_REAL";     break;
    case BE_STRING:   tname = "BE_STRING";   break;
    case BE_CLOSURE:  tname = "BE_CLOSURE";  break;
    case BE_CLASS:    tname = "BE_CLASS";    break;
    case BE_COMPTR:   tname = "BE_COMPTR";   break;
    case BE_NTVFUNC:  tname = "BE_NTVFUNC";  break;
    case BE_INSTANCE: tname = "BE_INSTANCE"; break;
    case BE_MAP:      tname = "BE_MAP";      break;
    case BE_LIST:     tname = "BE_LIST";     break;
    default:
        {
            char error[80];
            snprintf(error, sizeof(error), "Unsupported type in compact map value: %i", type);
            be_raise(vm, "internal_error", error);
        }
        return;
    }
    if (var_isstatic(value)) {
        logfmt("%s | BE_STATIC, ", tname);
    } else {
        logfmt("%s, ", tname);
    }
    /* --- value payload --- */
    switch (type) {
    case BE_INSTANCE:
    {
        binstance * ins = (binstance *) var_toobj(value);
        bclass * cl = ins->_class;
        if (cl == &be_class_bytes) {
            const void * bufptr = var_toobj(&ins->members[0]);
            int32_t len = var_toint(&ins->members[1]);
            size_t hex_len = len * 2 + 1;
            char * hex_out = be_pushbuffer(vm, hex_len);
            be_bytes_tohex(hex_out, hex_len, bufptr, len);
            lognofmt("be_kv_bytes_instance(");
            lognofmt(hex_out);
            lognofmt(")");
            be_pop(vm, 1);
        } else if (ins->super || ins->sub) {
            be_raise(vm, "internal_error", "instance must not have a super/sub class");
        } else {
            const char * cl_ptr = "";
            if (cl == &be_class_map) { cl_ptr = "map"; }
            else if (cl == &be_class_list) { cl_ptr = "list"; }
            else { be_raise(vm, "internal_error", "unsupported class"); }
            /* payload is a pointer to a nested simple instance */
            logfmt("be_kv_ptr(be_nested_simple_instance(&be_class_%s, {\n", cl_ptr);
            if (cl == &be_class_map) {
                logfmt("        be_const_map( * ");
            } else {
                logfmt("        be_const_list( * ");
            }
            m_solidify_bvalue(vm, str_literal, &ins->members[0], prefix_name, key, fout);
            logfmt("    ) } ))");
        }
    }
        break;
    case BE_MAP:
        logfmt("be_kv_ptr(");
        m_solidify_map(vm, str_literal, (bmap *) var_toobj(value), prefix_name, fout);
        logfmt(")");
        break;
    case BE_LIST:
        logfmt("be_kv_ptr(");
        m_solidify_list(vm, str_literal, (blist *) var_toobj(value), prefix_name, fout);
        logfmt(")");
        break;
    default:
        /* scalars, strings, closures, classes, comptr, ntvfunc */
        m_solidify_kval(vm, str_literal, value, prefix_name, key, fout);
        break;
    }
}
#endif /* BE_USE_COMPACT_MAP */

// pass key name in case of class, or NULL if none
static void m_solidify_bvalue(bvm *vm, bbool str_literal, const bvalue * value, const char *prefix_name, const char *key, void* fout)
{
    int type = var_primetype(value);
    switch (type) {
    case BE_NIL:
        logfmt("be_const_nil()");
        break;
    case BE_BOOL:
        logfmt("be_const_bool(%i)", var_tobool(value));
        break;
    case BE_INT:
#if BE_INTGER_TYPE == 2
        logfmt("be_const_int(%lli)", var_toint(value));
#else
        logfmt("be_const_int(%i)", var_toint(value));
#endif
        break;
    case BE_INDEX:
#if BE_INTGER_TYPE == 2
        logfmt("be_const_var(%lli)", var_toint(value));
#else
        logfmt("be_const_var(%i)", var_toint(value));
#endif
        break;
    case BE_REAL:
#if BE_USE_SINGLE_FLOAT
        logfmt("be_const_real_hex(0x%08" PRIX32 ")", (uint32_t)(uintptr_t)var_toobj(value));
#else
        logfmt("be_const_real_hex(0x%016" PRIx64 ")", (uint64_t)var_toobj(value));
#endif
        break;
    case BE_STRING:
        {
            const char * str = str(var_tostr(value));
            size_t len = strlen(str);
            size_t id_len = toidentifier_length(str);
            char id_buf_stack[64];
            char *id_buf = id_buf_stack;
            if (id_len >= 64) {
                id_buf = be_os_malloc(id_len);
                if (!id_buf) {
                    be_raise(vm, "memory_error", "could not allocated buffer");
                }
            }
            toidentifier(id_buf, str);
            if (len >= 255) {
                /* decompose to avoid any size limit */
                lognofmt("be_nested_str_long(");
                lognofmt(id_buf);
                lognofmt(")");
            } else if (!str_literal) {
                logfmt("be_nested_str(%s)", id_buf);
            } else {
                logfmt("be_nested_str_weak(%s)", id_buf);
            }
            if (id_buf != id_buf_stack) {
                be_os_free(id_buf);
            }
        }
        break;
    case BE_CLOSURE:
        {
            bclosure *clo = (bclosure*) var_toobj(value);
            const char * func_name = str(clo->proto->name);
            size_t id_len = toidentifier_length(func_name);
            char func_name_id[id_len];
            toidentifier(func_name_id, func_name);
            logfmt("be_const_%sclosure(%s%s%s_closure)",
                var_isstatic(value) ? "static_" : "",
                prefix_name ? prefix_name : "", prefix_name ? "_" : "",
                func_name_id);
        }
        break;
    case BE_CLASS:
        logfmt("be_const_class(be_class_%s)", str(((bclass*) var_toobj(value))->name));
        break;
    case BE_COMPTR:
        logfmt("be_const_comptr(&be_ntv_%s_%s)", prefix_name ? prefix_name : "unknown", key ? key : "unknown");
        break;
    case BE_NTVFUNC:
        logfmt("be_const_%sfunc(be_ntv_%s_%s)",
            var_isstatic(value) ? "static_" : "",
            prefix_name ? prefix_name : "unknown", key ? key : "unknown");
        break;
    case BE_INSTANCE:
    {
        binstance * ins = (binstance *) var_toobj(value);
        bclass * cl = ins->_class;

        if (cl ==  &be_class_bytes) {
            const void * bufptr = var_toobj(&ins->members[0]);
            int32_t len = var_toint(&ins->members[1]);
            size_t hex_len = len * 2 + 1;

            char * hex_out = be_pushbuffer(vm, hex_len);
            be_bytes_tohex(hex_out, hex_len, bufptr, len);
            lognofmt("be_const_bytes_instance(");
            lognofmt(hex_out);
            lognofmt(")");
            be_pop(vm, 1);
        } else if (ins->super || ins->sub) {
            be_raise(vm, "internal_error", "instance must not have a super/sub class");
        } else {
            const char * cl_ptr = "";
            if (cl == &be_class_map) { cl_ptr = "map"; }
            else if (cl == &be_class_list) { cl_ptr = "list"; }
            else { be_raise(vm, "internal_error", "unsupported class"); }

            logfmt("be_const_simple_instance(be_nested_simple_instance(&be_class_%s, {\n", cl_ptr);
            if (cl == &be_class_map) {
                logfmt("        be_const_map( * ");
            } else {
                logfmt("        be_const_list( * ");
            }
            m_solidify_bvalue(vm, str_literal, &ins->members[0], prefix_name, key, fout);
            logfmt("    ) } ))");
        }
    }
        break;
    case BE_MAP:
        m_solidify_map(vm, str_literal, (bmap *) var_toobj(value), prefix_name, fout);
        break;
    case BE_LIST:
        m_solidify_list(vm, str_literal, (blist *) var_toobj(value), prefix_name, fout);
        break;
    default:
        {
            char error[64];
            snprintf(error, sizeof(error), "Unsupported type in function constants: %i", type);
            be_raise(vm, "internal_error", error);
        }
    }
}

static void m_solidify_subclass(bvm *vm, bbool str_literal, const bclass *cl, void* fout);

/* read constant `idx` of proto `pr` into a bvalue, regardless of layout */
static bvalue proto_kvalue(const bproto *pr, int idx)
{
    bvalue v;
    proto_const_get(pr, idx, v);
    return v;
}

#if BE_USE_COMPACT_KTAB || BE_USE_COMPACT_MAP
/* emit the `union bvaldata` payload-word initializer for one constant */
static void m_solidify_kval(bvm *vm, bbool str_literal, const bvalue * value, const char *prefix_name, const char *key, void* fout)
{
    int type = var_primetype(value);
    switch (type) {
    case BE_NIL:
        logfmt("be_kv_nil()");
        break;
    case BE_BOOL:
        logfmt("be_kv_bool(%i)", var_tobool(value));
        break;
    case BE_INT:
    case BE_INDEX:
#if BE_INTGER_TYPE == 2
        logfmt("be_kv_int(%lli)", var_toint(value));
#else
        logfmt("be_kv_int(%i)", (int)var_toint(value));
#endif
        break;
    case BE_REAL:
#if BE_USE_SINGLE_FLOAT
        logfmt("be_kv_real(0x%08" PRIX32 ")", (uint32_t)(uintptr_t)var_toobj(value));
#else
        logfmt("be_kv_real(0x%016" PRIx64 ")", (uint64_t)var_toobj(value));
#endif
        break;
    case BE_STRING:
        {
            const char * cstr = str(var_tostr(value));
            size_t len = strlen(cstr);
            size_t id_len = toidentifier_length(cstr);
            char id_buf_stack[64];
            char *id_buf = id_buf_stack;
            if (id_len >= 64) {
                id_buf = be_os_malloc(id_len);
                if (!id_buf) {
                    be_raise(vm, "memory_error", "could not allocated buffer");
                }
            }
            toidentifier(id_buf, cstr);
            /* be_kv_str / be_kv_str_weak / be_kv_str_long carry the &be_const_str_
             * detail inside the macro and are recognized by coc (strong / weak /
             * long-bclstring) so the referenced string is registered */
            if (len >= 255) {
                logfmt("be_kv_str_long(%s)", id_buf);
            } else if (!str_literal) {
                logfmt("be_kv_str(%s)", id_buf);
            } else {
                logfmt("be_kv_str_weak(%s)", id_buf);
            }
            if (id_buf != id_buf_stack) {
                be_os_free(id_buf);
            }
        }
        break;
    case BE_CLOSURE:
        {
            bclosure *clo = (bclosure*) var_toobj(value);
            const char * func_name = str(clo->proto->name);
            size_t id_len = toidentifier_length(func_name);
            char func_name_id[id_len];
            toidentifier(func_name_id, func_name);
            logfmt("be_kv_closure(%s%s%s_closure)",
                prefix_name ? prefix_name : "", prefix_name ? "_" : "",
                func_name_id);
        }
        break;
    case BE_CLASS:
        logfmt("be_kv_class(be_class_%s)", str(((bclass*) var_toobj(value))->name));
        break;
    case BE_COMPTR:
        logfmt("be_kv_comptr(&be_ntv_%s_%s)", prefix_name ? prefix_name : "unknown", key ? key : "unknown");
        break;
    case BE_NTVFUNC:
        logfmt("be_kv_func(be_ntv_%s_%s)", prefix_name ? prefix_name : "unknown", key ? key : "unknown");
        break;
    default:
        {
            char error[80];
            snprintf(error, sizeof(error), "Unsupported type in compact ktab constants: %i", type);
            be_raise(vm, "internal_error", error);
        }
    }
}

/* emit the type-byte symbolic name for one constant (with BE_STATIC if set) */
static void m_solidify_ktype(bvm *vm, const bvalue * value, void* fout)
{
    int type = var_primetype(value);
    const char *name;
    switch (type) {
    case BE_NIL:     name = "BE_NIL";     break;
    case BE_BOOL:    name = "BE_BOOL";    break;
    case BE_INT:     name = "BE_INT";     break;
    case BE_INDEX:   name = "BE_INDEX";   break;
    case BE_REAL:    name = "BE_REAL";    break;
    case BE_STRING:  name = "BE_STRING";  break;
    case BE_CLOSURE: name = "BE_CLOSURE"; break;
    case BE_CLASS:   name = "BE_CLASS";   break;
    case BE_COMPTR:  name = "BE_COMPTR";  break;
    case BE_NTVFUNC: name = "BE_NTVFUNC"; break;
    default:
        {
            char error[80];
            snprintf(error, sizeof(error), "Unsupported type in compact ktab constants: %i", type);
            be_raise(vm, "internal_error", error);
        }
        return;
    }
    if (var_isstatic(value)) {
        logfmt("%s | BE_STATIC", name);
    } else {
        logfmt("%s", name);
    }
}
#endif /* BE_USE_COMPACT_KTAB || BE_USE_COMPACT_MAP */

/* emit a proto's constant table as two inline arrays (payload words + types) */
#if BE_USE_COMPACT_KTAB
static void m_solidify_proto_ktab_compact(bvm *vm, bbool str_literal, const bproto *pr, int indent, void* fout)
{
    logfmt("%*s( &(const union bvaldata[%2d]) {     /* constants */\n", indent, "", pr->nconst);
    for (int k = 0; k < pr->nconst; k++) {
        bvalue kv = proto_kvalue(pr, k);
        logfmt("%*s/* K%-3d */  ", indent, "", k);
        m_solidify_kval(vm, str_literal, &kv, NULL, NULL, fout);
        logfmt(",\n");
    }
    logfmt("%*s}),\n", indent, "");
    logfmt("%*s( &(const bbyte[%2d]) {     /* constant types */\n", indent, "", pr->nconst);
    for (int k = 0; k < pr->nconst; k++) {
        bvalue kv = proto_kvalue(pr, k);
        logfmt("%*s/* K%-3d */  ", indent, "", k);
        m_solidify_ktype(vm, &kv, fout);
        logfmt(",\n");
    }
    logfmt("%*s}),\n", indent, "");
}
#endif /* BE_USE_COMPACT_KTAB */


/* solidify any inner class */
static void m_solidify_closure_inner_class(bvm *vm, bbool str_literal, const bclosure *clo, void* fout)
{
    // parse any class in constants to output it first
    bproto *pr = clo->proto;
    if ((!gc_isconst(clo)) && (pr->nconst > 0) && (!(pr->varg & BE_VA_SHARED_KTAB)) && (!(pr->varg & BE_VA_NOCOMPACT))) {        /* if shared ktab or nocompact, skip */
        for (int k = 0; k < pr->nconst; k++) {
            if (proto_const_type(pr, k) == BE_CLASS) {
                if ((k == 0) && (pr->varg & BE_VA_STATICMETHOD)) {
                    // it is the implicit '_class' variable from a static method, don't dump the class
                } else {
                    // output the class
                    bvalue kv = proto_kvalue(pr, k);
                    m_solidify_subclass(vm, str_literal, (bclass*) var_toobj(&kv), fout);
                }
            }
        }
    }
}

static void m_solidify_proto(bvm *vm, bbool str_literal, const bproto *pr, const char * func_name, int indent, const char * prefix_name, void* fout)
{
    logfmt("%*sbe_nested_proto(\n", indent, "");
    indent += 2;

    logfmt("%*s%d,                          /* nstack */\n", indent, "", pr->nstack);
    logfmt("%*s%d,                          /* argc */\n", indent, "", pr->argc);
    logfmt("%*s%d,                          /* varg */\n", indent, "", pr->varg);
    logfmt("%*s%d,                          /* has upvals */\n", indent, "", (pr->nupvals > 0) ? 1 : 0);

    if (pr->nupvals > 0) {
        logfmt("%*s( &(const bupvaldesc[%2d]) {  /* upvals */\n", indent, "", pr->nupvals);
        for (int32_t i = 0; i < pr->nupvals; i++) {
            logfmt("%*s  be_local_const_upval(%i, %i),\n", indent, "", pr->upvals[i].instack, pr->upvals[i].idx);
        }
        logfmt("%*s}),\n", indent, "");
    } else {
        logfmt("%*sNULL,                       /* no upvals */\n", indent, "");
    }

    logfmt("%*s%d,                          /* has sup protos */\n", indent, "", (pr->nproto > 0) ? 1 : 0);
    if (pr->nproto > 0) {
        logfmt("%*s( &(const struct bproto*[%2d]) {\n", indent, "", pr->nproto);
        for (int32_t i = 0; i < pr->nproto; i++) {
            size_t sub_len = strlen(func_name) + 10;
            char sub_name[sub_len];
            snprintf(sub_name, sizeof(sub_name), "%s_%"PRId32, func_name, i);
            m_solidify_proto(vm, str_literal, pr->ptab[i], sub_name, indent+2, prefix_name, fout);
            logfmt(",\n");
        }
        logfmt("%*s}),\n", indent, "");
    } else {
        logfmt("%*sNULL,                       /* no sub protos */\n", indent, "");
    }

    logfmt("%*s%d,                          /* has constants */\n", indent, "", (pr->nconst > 0) ? 1 : 0);
    if (pr->nconst > 0) {
        // we output the full table unless it's a shared ktab
#if BE_USE_COMPACT_KTAB
        if (pr->varg & BE_VA_SHARED_KTAB) {
            logfmt("%*s&be_kval_%s, &be_ktype_%s,     /* shared constants */\n", indent, "", prefix_name, prefix_name);
        } else {
            m_solidify_proto_ktab_compact(vm, str_literal, pr, indent, fout);
        }
#else
        if (pr->varg & BE_VA_SHARED_KTAB) {
            logfmt("%*s&be_ktab_%s,     /* shared constants */\n", indent, "", prefix_name);
        } else {
            logfmt("%*s( &(const bvalue[%2d]) {     /* constants */\n", indent, "", pr->nconst);
            for (int k = 0; k < pr->nconst; k++) {
                bvalue kv = proto_kvalue(pr, k);
                logfmt("%*s/* K%-3d */  ", indent, "", k);
                m_solidify_bvalue(vm, str_literal, &kv, NULL, NULL, fout);
                logfmt(",\n");
            }
            logfmt("%*s}),\n", indent, "");
        }
#endif
    } else {
#if BE_USE_COMPACT_KTAB
        logfmt("%*sNULL, NULL,                 /* no const */\n", indent, "");
#else
        logfmt("%*sNULL,                       /* no const */\n", indent, "");
#endif
    }

    /* convert the string literal to identifier */
    const char * key = str(pr->name);
    size_t id_len = toidentifier_length(key);
    char id_buf[id_len];
    toidentifier(id_buf, key);
    if (!str_literal) {
        logfmt("%*s&be_const_str_%s,\n", indent, "", id_buf);
    } else {
        logfmt("%*sbe_str_weak(%s),\n", indent, "", id_buf);
    }
    // hard-code source as "solidified" for solidified
    logfmt("%*s&be_const_str_solidified,\n", indent, "");

    logfmt("%*s( &(const binstruction[%2d]) {  /* code */\n", indent, "", pr->codesize);
    for (int pc = 0; pc < pr->codesize; pc++) {
        uint32_t ins = pr->code[pc];
        logfmt("%*s  0x%08"PRIX32",  //", indent, "", ins);
        be_print_inst(ins, pc, fout);
        bopcode op = IGET_OP(ins);
        if (op == OP_GETGBL || op == OP_SETGBL) {
            // check if the global is in built-ins
            int glb = IGET_Bx(ins);
            if (glb > be_builtin_count(vm)) {
                // not supported
                logfmt("\n===== unsupported global G%d\n", glb);
                be_raise(vm, "internal_error", "Unsupported access to non-builtin global");
            }
        }
    }
    logfmt("%*s})\n", indent, "");
    indent -= 2;
    logfmt("%*s)", indent, "");

}

static void m_solidify_closure(bvm *vm, bbool str_literal, const bclosure *clo, const char * prefix_name, void* fout)
{   
    bproto *pr = clo->proto;
    const char * func_name = str(pr->name);

    if (clo->nupvals > 0) {
        const char *name = str(clo->proto->name);
        logfmt("--> Unsupported upvals in closure in '%s' <---", name ? name : "<unkown>");
        // be_raise(vm, "internal_error", "Unsupported upvals in closure");
    }

    int indent = 2;

    m_solidify_closure_inner_class(vm, str_literal, clo, fout);

    logfmt("\n");
    logfmt("/********************************************************************\n");
    logfmt("** Solidified function: %s\n", func_name);
    logfmt("********************************************************************/\n");

    {
        size_t id_len = toidentifier_length(func_name);
        char func_name_id[id_len];
        toidentifier(func_name_id, func_name);
        logfmt("be_local_closure(%s%s%s,   /* name */\n",
            prefix_name ? prefix_name : "", prefix_name ? "_" : "",
            func_name_id);
    }

    m_solidify_proto(vm, str_literal, pr, func_name, indent, prefix_name, fout);
    logfmt("\n");

    // closure
    logfmt(");\n");
    logfmt("/*******************************************************************/\n\n");
}

static void m_compact_class(bvm *vm, bbool str_literal, const bclass *cla, void* fout);

static void m_solidify_subclass(bvm *vm, bbool str_literal, const bclass *cla, void* fout)
{
    const char * classname = str(cla->name);

    /* TODO try compacting for now */
    m_compact_class(vm, str_literal, cla, fout);

    char prefix_name[strlen(classname) + 10];
    snprintf(prefix_name, sizeof(prefix_name), "class_%s", classname);
    /* pre-declare class to support '_class' implicit variable */
    logfmt("\nextern const bclass be_class_%s;\n", classname);

    /* iterate on members to dump closures */
    if (cla->members) {
        bmapnode *node;
        bmapiter iter = be_map_iter();
        while ((node = be_map_next(cla->members, &iter)) != NULL) {
            if (var_isstr(&node->key) && var_isclosure(&node->value)) {
                bclosure *f = var_toobj(&node->value);
                m_solidify_closure(vm, str_literal, f, prefix_name, fout);
            }
        }
    }


    logfmt("\n");
    logfmt("/********************************************************************\n");
    logfmt("** Solidified class: %s\n", classname);
    logfmt("********************************************************************/\n");

    if (cla->super) {
        logfmt("extern const bclass be_class_%s;\n", str(cla->super->name));
    }

    logfmt("be_local_class(%s,\n", classname);
    logfmt("    %i,\n", cla->nvar);
    if (cla->super) {
        logfmt("    &be_class_%s,\n", str(cla->super->name));
    } else {
        logfmt("    NULL,\n");
    }

    if (cla->members) {
        m_solidify_map(vm, str_literal, cla->members, prefix_name, fout);
        logfmt(",\n");
    } else {
        logfmt("    NULL,\n");
    }

    size_t id_len = toidentifier_length(classname);
    char id_buf[id_len];
    toidentifier(id_buf, classname);
    if (!str_literal) {
        logfmt("    (bstring*) &be_const_str_%s\n", id_buf);
    } else {
        logfmt("    be_str_weak(%s)\n", id_buf);
    }
    logfmt(");\n");
}

static void m_solidify_class(bvm *vm, bbool str_literal, bclass *cl, void* fout)
{
    m_solidify_subclass(vm, str_literal, cl, fout);
}

static void m_solidify_module(bvm *vm, bbool str_literal, bmodule *ml, void* fout)
{
    const char * modulename = be_module_name(ml);
    if (!modulename) { modulename = ""; }
    // char prefix_name[strlen(modulename) + 10];
    // snprintf(prefix_name, sizeof(prefix_name), "module_%s", modulename);

    /* iterate on members to dump closures and classes */
    if (ml->table) {
        bmapnode *node;
        bmapiter iter = be_map_iter();
        while ((node = be_map_next(ml->table, &iter)) != NULL) {
            if (var_isstr(&node->key) && var_isclosure(&node->value)) {
                bclosure *f = var_toobj(&node->value);
                m_solidify_closure(vm, str_literal, f, NULL, fout);
            }
            if (var_isstr(&node->key) && var_isclass(&node->value)) {
                bclass *cl = var_toobj(&node->value);
                m_solidify_subclass(vm, str_literal, cl, fout);
            }
        }
    }


    logfmt("\n");
    logfmt("/********************************************************************\n");
    logfmt("** Solidified module: %s\n", modulename);
    logfmt("********************************************************************/\n");

    logfmt("be_local_module(%s,\n", modulename);
    logfmt("    \"%s\",\n", modulename);

    if (ml->table) {
        m_solidify_map(vm, str_literal, ml->table, NULL, fout);
        logfmt("\n");
    } else {
        logfmt("    NULL,\n");
    }
    logfmt(");\n");
    logfmt("BE_EXPORT_VARIABLE be_define_const_native_module(%s);\n", modulename);
    logfmt("/********************************************************************/\n");

}

static int m_dump(bvm *vm)
{
    int top = be_top(vm);
    if (top >= 1) {
        bvalue *v = be_indexof(vm, 1);
        bbool str_literal = bfalse;
        if (top >= 2) {
            str_literal = be_tobool(vm, 2);
        }
        void* fout = NULL;      /* output file */
        if (top >= 3 && be_isinstance(vm, 3)) {
            be_getmember(vm, 3, ".p");
            if (be_iscomptr(vm, -1)) {
                fout = be_tocomptr(vm, -1);
            }
            be_pop(vm, 1);
        }
        const char *prefix_name = NULL;  /* allow to specify an explicit prefix */
        if (top >= 4 && be_isstring(vm, 4)) {
            prefix_name = be_tostring(vm, 4);
        }
        if (var_isclosure(v)) {
            m_solidify_closure(vm, str_literal, var_toobj(v), prefix_name, fout);
        } else if (var_isclass(v)) {
            m_solidify_class(vm, str_literal, var_toobj(v), fout);
        } else if (var_ismodule(v)) {
            m_solidify_module(vm, str_literal, var_toobj(v), fout);
        } else {
            be_raise(vm, "value_error", "unsupported type");
        }
    }
    be_return_nil(vm);
}

static void m_compact_class(bvm *vm, bbool str_literal, const bclass *cla, void* fout)
{
    const char * classname = str(cla->name);

    /* reserve an array big enough for max size ktab (256) */
    const int MAX_KTAB_SIZE = 256;
    bvalue ktab[MAX_KTAB_SIZE];       /* size is 2048 byte for 32 bits, so fitting in ESP32, may need to be changed on smaller architectures */
    int ktab_size = 0;

    /* for statistics, keep the aggregate number of bvalues */
    int ktab_total = 0;

    /* iterate on members to dump closures */
    if (cla->members) {
        bmapnode *node;
        bmapiter iter = be_map_iter();

        /* first iteration to build the global ktab */
        while ((node = be_map_next(cla->members, &iter)) != NULL) {
            if (var_isstr(&node->key) && var_isclosure(&node->value)) {
                bclosure *cl = var_toobj(&node->value);
                bproto *pr = cl->proto;

                if ((gc_isconst(cl)) || (pr->varg & BE_VA_SHARED_KTAB) || (pr->varg & BE_VA_NOCOMPACT)) { continue; }

                // iterate on each bvalue in ktab
                for (int i = 0; i < pr->nconst; i++) {
                    bvalue ki = proto_kvalue(pr, i);
                    // look if the bvalue pair is already in ktab
                    int found = 0;
                    for (int j = 0; j < ktab_size; j++) {
                        // to avoid any size issue, we compare all bytes
                        if ((ki.type == ktab[j].type) && (ki.v.i == ktab[j].v.i) && (ki.v.c == ktab[j].v.c)) {
                            found = 1;
                            break;
                        }
                    }
                    // if not already there, add it
                    if (!found) {
                        ktab[ktab_size++] = ki;
                    }
                    if (ktab_size >= MAX_KTAB_SIZE) {
                        logfmt("// ktab too big for class '%s' - skipping\n", classname);
                        return;
                    }
                }
                ktab_total += pr->nconst;
            }
        }

        if (ktab_size == ktab_total) {
            return;         /* nothing to optimize, can happen for classes with zero or 1 method */
        }
        /* allocate a proper ktab */
        bvalue *new_ktab = be_malloc(vm, sizeof(bvalue) * ktab_size);
        memmove(new_ktab, ktab, sizeof(bvalue) * ktab_size);
#if BE_USE_COMPACT_KTAB
        /* build a single shared compact ktab block (kval words + type bytes)
         * that every method of this class will point to at runtime */
        char *shared_blk = be_malloc(vm, (size_t)ktab_size * (sizeof(union bvaldata) + sizeof(bbyte)));
        union bvaldata *shared_kval = (union bvaldata*)shared_blk;
        bbyte *shared_ktype = (bbyte*)(shared_blk + (size_t)ktab_size * sizeof(union bvaldata));
        for (int i = 0; i < ktab_size; i++) {
            shared_kval[i] = new_ktab[i].v;
            shared_ktype[i] = (bbyte)new_ktab[i].type;
        }
        be_free(vm, new_ktab, sizeof(bvalue) * ktab_size);  /* intermediate, no longer needed */
#endif

        /* second iteration to replace ktab and patch code */
        iter = be_map_iter();
        while ((node = be_map_next(cla->members, &iter)) != NULL) {
            if (var_isstr(&node->key) && var_isclosure(&node->value)) {
                bclosure *cl = var_toobj(&node->value);
                bproto *pr = cl->proto;

                if ((gc_isconst(cl)) || (pr->varg & BE_VA_SHARED_KTAB) || (pr->varg & BE_VA_NOCOMPACT)) { continue; }

                uint8_t mapping_array[MAX_KTAB_SIZE];
                // iterate in proto ktab to get the index in the global ktab
                for (int i = 0; i < pr->nconst; i++) {
                    bvalue ki = proto_kvalue(pr, i);
                    for (int j = 0; j < ktab_size; j++) {
                        // compare all bytes
                        if ((ki.type == ktab[j].type) && (ki.v.i == ktab[j].v.i) && (ki.v.c == ktab[j].v.c)) {
                            mapping_array[i] = j;
                            break;
                        }
                    }
                }

                // replace ktab
#if BE_USE_COMPACT_KTAB
                pr->kval = shared_kval;
                pr->ktype = shared_ktype;
#else
                pr->ktab = new_ktab;
#endif
                pr->nconst = ktab_size;
                // flag as shared ktab
                pr->varg |= BE_VA_SHARED_KTAB;
                // parse code to replace any K<x> reference
                for (int pc = 0; pc < pr->codesize; pc++) {
                    uint32_t ins = pr->code[pc];
                    bopcode op = IGET_OP(ins);

                    /* handle all impacted opcodes */
                    /* Possibilities: */
                    /* "B" | "B and C" | "Bx" contain a constant code */
                    /* special case for OP_RET where "B" may not contain anything */
                    switch (op) {
                    case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV:
                    case OP_MOD: case OP_LT: case OP_LE: case OP_EQ:
                    case OP_NE:  case OP_GT:  case OP_GE: case OP_CONNECT:
                    case OP_GETMBR: case OP_SETMBR:  case OP_GETMET:
                    case OP_GETIDX: case OP_SETIDX: case OP_AND:
                    case OP_OR: case OP_XOR: case OP_SHL: case OP_SHR:
                    case OP_RAISE:
                        // B and C might contain 'K' constant
                        if (isKB(ins)) {
                            int kidx = IGET_RKB(ins) & KR_MASK;
                            if (kidx >= ktab_size) {
                                be_raise(vm, "value_error", "invalid ktab index");
                            }
                            ins = (ins & ~IRKB_MASK) | ISET_RKB(setK(mapping_array[kidx]));
                        }
                        if (isKC(ins)) {
                            int kidx = IGET_RKC(ins) & KR_MASK;
                            if (kidx >= ktab_size) {
                                be_raise(vm, "value_error", "invalid ktab index");
                            }
                            ins = (ins & ~IRKC_MASK) | ISET_RKC(setK(mapping_array[kidx]));
                        }
                        pr->code[pc] = ins;
                        break;
                    case OP_MOVE: case OP_SETSUPER: case OP_NEG: case OP_FLIP: case OP_IMPORT:
                    case OP_GETNGBL: case OP_SETNGBL:
                        // Only B might contain 'K' constant
                        if (isKB(ins)) {
                            int kidx = IGET_RKB(ins) & KR_MASK;
                            if (kidx >= ktab_size) {
                                be_raise(vm, "value_error", "invalid ktab index");
                            }
                            ins = (ins & ~IRKB_MASK) | ISET_RKB(setK(mapping_array[kidx]));
                        }
                        pr->code[pc] = ins;
                        break;
                    case OP_CLASS:
                    case OP_LDCONST:
                        // Bx contains the K
                        {
                            int kidx = IGET_Bx(ins);
                            if (kidx >= ktab_size) {
                                be_raise(vm, "value_error", "invalid ktab index");
                            }
                            ins = (ins & ~IBx_MASK) | ISET_Bx(mapping_array[kidx]);
                            pr->code[pc] = ins;
                        }
                        break;
                    case OP_RET:
                        if (IGET_RA(ins)) {
                            // Only B might contain 'K' constant
                            if (isKB(ins)) {
                                int kidx = IGET_RKB(ins) & KR_MASK;
                                if (kidx >= ktab_size) {
                                    be_raise(vm, "value_error", "invalid ktab index");
                                }
                                ins = (ins & ~IRKB_MASK) | ISET_RKB(setK(mapping_array[kidx]));
                            }
                        }
                        pr->code[pc] = ins;
                        break;
                    /* The following opcodes are not impacted by shared constant table
                    case OP_GETUPV: case OP_SETUPV:
                    case OP_LDCONST:
                    case OP_CALL:
                    case OP_CLOSURE:
                    case OP_CLOSE: case OP_LDNIL:
                    case OP_EXBLK:
                    case OP_CATCH:
                    case OP_GETGBL: case OP_SETGBL:
                    case OP_JMP:
                    case OP_JMPT: case OP_JMPF:
                    case OP_LDINT:
                    case OP_LDBOOL:     */
                    default:
                        break;
                    }

                }
            }
        }
    }
    // logfmt("extern const bclass be_class_%s;\n", classname);
    // scan classes and generate extern statements for classes
    for (int k = 0; k < ktab_size; k++) {
        // if it's a class, print an extern statement
        if (var_isclass(&ktab[k])) {
            bclass *cl = var_toobj(&ktab[k]);
            logfmt("extern const bclass be_class_%s;\n", str(cl->name));
        }
    }

    // scan again to export all sub-classes
    for (int k = 0; k < ktab_size; k++) {
        // if it's a class, print an extern statement
        if (var_isclass(&ktab[k])) {
            bclass *cl = var_toobj(&ktab[k]);
            if (cl != cla) {
                m_solidify_subclass(vm, str_literal, cl, fout);
            }
        }
    }

    // output shared ktab
    int indent = 0;
#if BE_USE_COMPACT_KTAB
    logfmt("// compact class '%s' ktab size: %d, total: %d (saved %i bvalues)\n", classname, ktab_size, ktab_total, (ktab_total - ktab_size));
    logfmt("static const union bvaldata be_kval_class_%s[%i] = {\n", classname, ktab_size);
    for (int k = 0; k < ktab_size; k++) {
        logfmt("%*s/* K%-3d */  ", indent + 2, "", k);
        m_solidify_kval(vm, str_literal, &ktab[k], NULL, NULL, fout);
        logfmt(",\n");
    }
    logfmt("%*s};\n", indent, "");
    logfmt("static const bbyte be_ktype_class_%s[%i] = {\n", classname, ktab_size);
    for (int k = 0; k < ktab_size; k++) {
        logfmt("%*s/* K%-3d */  ", indent + 2, "", k);
        m_solidify_ktype(vm, &ktab[k], fout);
        logfmt(",\n");
    }
    logfmt("%*s};\n", indent, "");
    logfmt("\n");
#else
    logfmt("// compact class '%s' ktab size: %d, total: %d (saved %i bytes)\n", classname, ktab_size, ktab_total, (ktab_total - ktab_size) * 8);
    logfmt("static const bvalue be_ktab_class_%s[%i] = {\n", classname, ktab_size);
    for (int k = 0; k < ktab_size; k++) {
        logfmt("%*s/* K%-3d */  ", indent + 2, "", k);
        m_solidify_bvalue(vm, str_literal, &ktab[k], NULL, NULL, fout);
        logfmt(",\n");
    }
    logfmt("%*s};\n", indent, "");
    logfmt("\n");
#endif
}

// takes a class or a module
// scans all first level bproto
// build a consolidated 'ktab' array
// check that the array is not bigger than 256 (which is the max acceptable constants)
// (for now) print the potential saving
static int m_compact(bvm *vm)
{
    int top = be_top(vm);
    if (top >= 1) {
        bvalue *v = be_indexof(vm, 1);
        bbool str_literal = bfalse;
        if (top >= 2) {
            str_literal = be_tobool(vm, 2);
        }
        void* fout = NULL;      /* output file */
        if (top >= 3 && be_isinstance(vm, 3)) {
            be_getmember(vm, 3, ".p");
            if (be_iscomptr(vm, -1)) {
                fout = be_tocomptr(vm, -1);
            }
            be_pop(vm, 1);
        }
        // const char *prefix_name = NULL;  /* allow to specify an explicit prefix */
        // if (top >= 4 && be_isstring(vm, 4)) {
        //     prefix_name = be_tostring(vm, 4);
        // }
        if (var_isclass(v)) {
            m_compact_class(vm, str_literal, var_toobj(v), fout);
        } else if (var_ismodule(v)) {
            // TODO
        } else {
            be_raise(vm, "value_error", "unsupported type");
        }
    }
    be_return_nil(vm);
}

static int m_nocompact(bvm *vm)
{
    int top = be_top(vm);
    if (top >= 1) {
        bvalue *v = be_indexof(vm, 1);
        if (var_isclosure(v)) {
            bclosure *cl = var_toobj(v);
            bproto *pr = cl->proto;
            pr->varg |= BE_VA_NOCOMPACT;
        } else {
            be_raise(vm, "value_error", "unsupported type");
        }
    }
    be_return_nil(vm);
}

#if !BE_USE_PRECOMPILED_OBJECT
be_native_module_attr_table(solidify) {
    be_native_module_function("dump", m_dump),
    be_native_module_function("compact", m_compact),
};

be_define_native_module(solidify, NULL);
#else
/* @const_object_info_begin
module solidify (scope: global, depend: BE_USE_SOLIDIFY_MODULE) {
    dump, func(m_dump)
    compact, func(m_compact)
    nocompact, func(m_nocompact)
}
@const_object_info_end */
#include "../generate/be_fixed_solidify.h"
#endif

#endif /* BE_USE_SOLIFIDY_MODULE */
