#include "pxtbase.h"
#include <limits.h>
#include <stdlib.h>

using namespace std;

#define p10(v) __builtin_powi(10, v)

namespace pxt {

static HandlerBinding *handlerBindings;

HandlerBinding *findBinding(int source, int value) {
    for (auto p = handlerBindings; p; p = p->next) {
        if (p->source == source && p->value == value) {
            return p;
        }
    }
    return 0;
}

void setBinding(int source, int value, Action act) {
    auto curr = findBinding(source, value);
    incr(act);
    if (curr) {
        decr(curr->action);
        curr->action = act;
        return;
    }
    curr = new HandlerBinding();
    curr->next = handlerBindings;
    curr->source = source;
    curr->value = value;
    curr->action = act;
    handlerBindings = curr;
}

static const uint16_t emptyString[]
    __attribute__((aligned(4))) = {0xffff, PXT_REF_TAG_STRING, 0, 0};

static const uint16_t emptyBuffer[]
    __attribute__((aligned(4))) = {0xffff, PXT_REF_TAG_BUFFER, 0, 0};

String mkString(const char *data, int len) {
    if (len < 0)
        len = strlen(data);
    if (len == 0)
        return (String)emptyString;
    String r = new (::operator new(sizeof(BoxedString) + len + 1)) BoxedString();
    r->length = len;
    if (data)
        memcpy(r->data, data, len);
    r->data[len] = 0;
    MEMDBG("mkString: len=%d => %p", len, r);
    return r;
}

Buffer mkBuffer(const uint8_t *data, int len) {
    if (len <= 0)
        return (Buffer)emptyBuffer;
    Buffer r = new (::operator new(sizeof(BoxedBuffer) + len)) BoxedBuffer();
    r->length = len;
    if (data)
        memcpy(r->data, data, len);
    else
        memset(r->data, 0, len);
    MEMDBG("mkBuffer: len=%d => %p", len, r);
    return r;
}

#ifndef X86_64
TNumber mkNaN() {
    // TODO optimize
    return fromDouble(NAN);
}
#endif

static unsigned random_value = 0xC0DA1;

void seedRandom(unsigned seed) {
    random_value = seed;
}

unsigned getRandom(unsigned max) {
    unsigned m, result;

    do {
        m = (unsigned)max;
        result = 0;

        do {
            // Cycle the LFSR (Linear Feedback Shift Register).
            // We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneier here
            // (a true legend in the field!),
            // For those interested, it's documented in his paper:
            // "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent
            // generator for 32-bit Microprocessors"
            // https://www.schneier.com/paper-pseudorandom-sequence.html
            unsigned r = random_value;

            r = ((((r >> 31) ^ (r >> 6) ^ (r >> 4) ^ (r >> 2) ^ (r >> 1) ^ r) & 1) << 31) |
                (r >> 1);

            random_value = r;

            result = ((result << 1) | (r & 0x00000001));
        } while (m >>= 1);
    } while (result > (unsigned)max);

    return result;
}

PXT_DEF_STRING(sTrue, "\x04\x00true")
PXT_DEF_STRING(sFalse, "\x05\x00false")
PXT_DEF_STRING(sUndefined, "\x09\x00undefined")
PXT_DEF_STRING(sNull, "\x04\x00null")
PXT_DEF_STRING(sObject, "\x08\x00[Object]")
PXT_DEF_STRING(sFunction, "\x0A\x00[Function]")
PXT_DEF_STRING(sNaN, "\x03\x00NaN")
PXT_DEF_STRING(sInf, "\x08\x00Infinity")
PXT_DEF_STRING(sMInf, "\x09\x00-Infinity")
} // namespace pxt

#ifndef X86_64

namespace String_ {

//%
String mkEmpty() {
    return mkString("", 0);
}

//%
String fromCharCode(int code) {
    char buf[] = {(char)code, 0};
    return mkString(buf, 1);
}

//%
String charAt(String s, int pos) {
    if (s && 0 <= pos && pos < s->length) {
        return fromCharCode(s->data[pos]);
    } else {
        return mkEmpty();
    }
}

//%
TNumber charCodeAt(String s, int pos) {
    if (s && 0 <= pos && pos < s->length) {
        return fromInt(s->data[pos]);
    } else {
        return mkNaN();
    }
}

//%
String concat(String s, String other) {
    if (!s)
        s = (String)sNull;
    if (!other)
        other = (String)sNull;
    if (s->length == 0)
        return (String)incrRC(other);
    if (other->length == 0)
        return (String)incrRC(s);
    String r = mkString(NULL, s->length + other->length);
    memcpy(r->data, s->data, s->length);
    memcpy(r->data + s->length, other->data, other->length);
    return r;
}

//%
int compare(String s, String that) {
    if (s == that)
        return 0;
    // TODO this isn't quite right, in JS both `null < "foo"` and `null > "foo"` are false
    if (!s)
        return -1;
    if (!that)
        return 1;
    int compareResult = strcmp(s->data, that->data);
    if (compareResult < 0)
        return -1;
    else if (compareResult > 0)
        return 1;
    return 0;
}

//%
int length(String s) {
    return s->length;
}

#define isspace(c) ((c) == ' ')

double mystrtod(const char *p, char **endp) {
    while (isspace(*p))
        p++;
    double m = 1;
    double v = 0;
    int dot = 0;
    if (*p == '+')
        p++;
    if (*p == '-') {
        m = -1;
        p++;
    }
    if (*p == '0' && (p[1] | 0x20) == 'x') {
        return m * strtol(p, endp, 16);
    }
    while (*p) {
        int c = *p - '0';
        if (0 <= c && c <= 9) {
            v *= 10;
            v += c;
            if (dot)
                m /= 10;
        } else if (!dot && *p == '.') {
            dot = 1;
        } else if (*p == 'e' || *p == 'E') {
            break;
        } else {
            while (isspace(*p))
                p++;
            if (*p)
                return NAN;
            break;
        }
        p++;
    }

    v *= m;

    if (*p) {
        p++;
        int pw = strtol(p, endp, 10);
        v *= p10(pw);
    }
    else {
        *endp = (char *) p;
    }

    return v;
}

//%
TNumber toNumber(String s) {
    // JSCHECK
    char *endptr;
    double v = mystrtod(s->data, &endptr);
    if (endptr != s->data + s->length)
        v = NAN;
    else if (v == 0.0 || v == -0.0)
        v = v;
    else if (!isnormal(v))
        v = NAN;
    return fromDouble(v);
}

//%
String substr(String s, int start, int length) {
    if (length <= 0)
        return mkEmpty();
    if (start < 0)
        start = max(s->length + start, 0);
    length = min(length, s->length - start);
    return mkString(s->data + start, length);
}
} // namespace String_

namespace Boolean_ {
//%
bool bang(int v) {
    return v == 0;
}
} // namespace Boolean_

namespace pxt {

// ES5 9.5, 9.6
unsigned toUInt(TNumber v) {
    if (isNumber(v))
        return numValue(v);
    if (isSpecial(v)) {
        if ((intptr_t)v >> 6)
            return 1;
        else
            return 0;
    }
    if (!v)
        return 0;

    double num = toDouble(v);
    if (!isnormal(num))
        return 0;
    double rem = fmod(trunc(num), 4294967296.0);
    if (rem < 0.0)
        rem += 4294967296.0;
    return (unsigned)rem;
}
int toInt(TNumber v) {
    return (int)toUInt(v);
}

// only support double in tagged mode
double toDouble(TNumber v) {
    if (isTagged(v))
        return toInt(v);

    // JSCHECK
    ValType t = valType(v);
    if (t == ValType::Number) {
        BoxedNumber *p = (BoxedNumber *)v;
        return p->num;
    } else if (t == ValType::String) {
        return toDouble(String_::toNumber((String)v));
    } else {
        return NAN;
    }
}

float toFloat(TNumber v) {
    // TODO optimize?
    return (float)toDouble(v);
}

TNumber fromDouble(double r) {
#ifndef PXT_BOX_DEBUG
    int ri = ((int)r) << 1;
    if ((ri >> 1) == r)
        return (TNumber)(ri | 1);
#endif
    BoxedNumber *p = new BoxedNumber();
    p->num = r;
    MEMDBG("mkNum: %p", p);
    return (TNumber)p;
}

TNumber fromFloat(float r) {
    // TODO optimize
    return fromDouble(r);
}

TNumber fromInt(int v) {
    if (canBeTagged(v))
        return TAG_NUMBER(v);
    return fromDouble(v);
}

TNumber fromUInt(unsigned v) {
#ifndef PXT_BOX_DEBUG
    if (v <= 0x3fffffff)
        return TAG_NUMBER(v);
#endif
    return fromDouble(v);
}

TValue fromBool(bool v) {
    if (v)
        return TAG_TRUE;
    else
        return TAG_FALSE;
}

TNumber eqFixup(TNumber v) {
    if (v == TAG_NULL)
        return TAG_UNDEFINED;
    if (v == TAG_TRUE)
        return TAG_NUMBER(1);
    if (v == TAG_FALSE)
        return TAG_NUMBER(0);
    return v;
}

bool eqq_bool(TValue a, TValue b) {
    // TODO improve this

    if (a == b)
        return true;

    ValType ta = valType(a);
    ValType tb = valType(b);

    if (ta != tb)
        return false;

    if (ta == ValType::String)
        return String_::compare((String)a, (String)b) == 0;

    int aa = (int)a;
    int bb = (int)b;

    // if at least one of the values is tagged, they are not equal
    if ((aa | bb) & 3)
        return false;

    if (ta == ValType::Number)
        return toDouble(a) == toDouble(b);
    else
        return a == b;
}

bool eq_bool(TValue a, TValue b) {
    return eqq_bool(eqFixup(a), eqFixup(b));
}

//%
bool switch_eq(TValue a, TValue b) {
    if (eqq_bool(eqFixup(a), eqFixup(b))) {
        decr(b);
        return true;
    }
    return false;
}

} // namespace pxt

namespace langsupp {
//%
TValue ptreq(TValue a, TValue b) {
    return eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

//%
TValue ptreqq(TValue a, TValue b) {
    return eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

//%
TValue ptrneq(TValue a, TValue b) {
    return !eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

//%
TValue ptrneqq(TValue a, TValue b) {
    return !eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}
} // namespace langsupp

#define NUMOP(op) return fromDouble(toDouble(a) op toDouble(b));
#define BITOP(op) return fromInt(toInt(a) op toInt(b));
namespace numops {

//%
int toBool(TValue v) {
    if (isTagged(v)) {
        if (v == TAG_UNDEFINED || v == TAG_NULL || v == TAG_FALSE || v == TAG_NUMBER(0))
            return 0;
        else
            return 1;
    }

    ValType t = valType(v);
    if (t == ValType::String) {
        String s = (String)v;
        if (s->length == 0)
            return 0;
    } else if (t == ValType::Number) {
        double x = toDouble(v);
        if (isnan(x) || x == 0.0 || x == -0.0)
            return 0;
        else
            return 1;
    }

    return 1;
}

//%
int toBoolDecr(TValue v) {
    if (v == TAG_TRUE)
        return 1;
    if (v == TAG_FALSE)
        return 0;
    int r = toBool(v);
    decr(v);
    return r;
}

// TODO
// The integer, non-overflow case for add/sub/bit opts is handled in assembly

//%
TNumber adds(TNumber a, TNumber b){NUMOP(+)}

//%
TNumber subs(TNumber a, TNumber b){NUMOP(-)}

//%
TNumber muls(TNumber a, TNumber b) {
    if (bothNumbers(a, b)) {
        int aa = (int)a;
        int bb = (int)b;
        // if both operands fit 15 bits, the result will not overflow int
        if ((aa >> 15 == 0 || aa >> 15 == -1) && (bb >> 15 == 0 || bb >> 15 == -1)) {
            // it may overflow 31 bit int though - use fromInt to convert properly
            return fromInt((aa >> 1) * (bb >> 1));
        }
    }
    NUMOP(*)
}

//%
TNumber div(TNumber a, TNumber b){NUMOP(/)}

//%
TNumber mod(TNumber a, TNumber b) {
    if (isNumber(a) && isNumber(b) && numValue(b))
        BITOP(%)
    return fromDouble(fmod(toDouble(a), toDouble(b)));
}

//%
TNumber lsls(TNumber a, TNumber b){BITOP(<<)}

//%
TNumber lsrs(TNumber a, TNumber b) {
    return fromUInt(toUInt(a) >> toUInt(b));
}

//%
TNumber asrs(TNumber a, TNumber b){BITOP(>>)}

//%
TNumber eors(TNumber a, TNumber b){BITOP (^)}

//%
TNumber orrs(TNumber a, TNumber b){BITOP(|)}

//%
TNumber bnot(TNumber a) {
    return fromInt(~toInt(a));
}

//%
TNumber ands(TNumber a, TNumber b) {
    BITOP(&)
}

#define CMPOP_RAW(op)                                                                              \
    if (bothNumbers(a, b))                                                                         \
        return (int)a op((int)b);                                                                  \
    return toDouble(a) op toDouble(b);

#define CMPOP(op)                                                                                  \
    if (bothNumbers(a, b))                                                                         \
        return ((int)a op((int)b)) ? TAG_TRUE : TAG_FALSE;                                         \
    return toDouble(a) op toDouble(b) ? TAG_TRUE : TAG_FALSE;

//%
bool lt_bool(TNumber a, TNumber b){CMPOP_RAW(<)}

//%
TNumber le(TNumber a, TNumber b){CMPOP(<=)}

//%
TNumber lt(TNumber a, TNumber b){CMPOP(<)}

//%
TNumber ge(TNumber a, TNumber b){CMPOP(>=)}

//%
TNumber gt(TNumber a, TNumber b){CMPOP(>)}

//%
TNumber eq(TNumber a, TNumber b) {
    return pxt::eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

//%
TNumber neq(TNumber a, TNumber b) {
    return !pxt::eq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

//%
TNumber eqq(TNumber a, TNumber b) {
    return pxt::eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

//%
TNumber neqq(TNumber a, TNumber b) {
    return !pxt::eqq_bool(a, b) ? TAG_TRUE : TAG_FALSE;
}

void mycvt(double d, char *buf) {
    if (d < 0) {
        *buf++ = '-';
        d = -d;
    }

    if (!d) {
        *buf++ = '0';
        *buf++ = 0;
        return;
    }

    int pw = (int)log10(d);
    int e = 1;
    int beforeDot = 1;

    if (0.000001 <= d && d < 1e21) {
        if (pw > 0) {
            d /= p10(pw);
            beforeDot = 1 + pw;
        }
    } else {
        d /= p10(pw);
        e = pw;
    }

    int sig = 0;
    while (sig < 17 || beforeDot > 0) {
        // printf("%f sig=%d bd=%d\n", d, sig, beforeDot);
        int c = (int)d;
        *buf++ = '0' + c;
        d = (d - c) * 10;
        if (--beforeDot == 0)
            *buf++ = '.';
        if (sig || c)
            sig++;
    }

    buf--;
    while (*buf == '0')
        buf--;
    if (*buf == '.')
        buf--;
    buf++;

    if (e != 1) {
        *buf++ = 'e';
        itoa(e, buf);
    } else {
        *buf = 0;
    }
}

//%
String toString(TValue v) {

    if (v == TAG_UNDEFINED)
        return (String)(void *)sUndefined;
    else if (v == TAG_FALSE)
        return (String)(void *)sFalse;
    else if (v == TAG_TRUE)
        return (String)(void *)sTrue;
    else if (v == TAG_NULL)
        return (String)(void *)sNull;
    ValType t = valType(v);

    if (t == ValType::String) {
        return (String)(void *)incr(v);
    } else if (t == ValType::Number) {
        char buf[64];

        if (isNumber(v)) {
            itoa(numValue(v), buf);
            return mkString(buf);
        }

        double x = toDouble(v);

        if (isnan(x))
            return (String)(void *)sNaN;
        if (isinf(x)) {
            if (x < 0)
                return (String)(void *)sMInf;
            else
                return (String)(void *)sInf;
        }
        mycvt(x, buf);

        return mkString(buf);
    } else if (t == ValType::Function) {
        return (String)(void *)sFunction;
    } else {
        return (String)(void *)sObject;
    }
}
} // namespace numops

namespace Math_ {
//%
TNumber pow(TNumber x, TNumber y) {
    // regular pow() from math.h is 4k of code
    return fromDouble(__builtin_powi(toDouble(x), toInt(y)));
}

//%
TNumber atan2(TNumber y, TNumber x) {
    return fromDouble(::atan2(toDouble(y), toDouble(x)));
}

double randomDouble() {
    return getRandom(UINT_MAX) / ((double)UINT_MAX + 1) +
           getRandom(0xffffff) / ((double)UINT_MAX * 0xffffff);
}

//%
TNumber random() {
    return fromDouble(randomDouble());
}

//%
TNumber randomRange(TNumber min, TNumber max) {
    if (isNumber(min) && isNumber(max)) {
        int mini = numValue(min);
        int maxi = numValue(max);
        if (mini > maxi) {
            int temp = mini;
            mini = maxi;
            maxi = temp;
        }
        if (maxi == mini)
            return fromInt(mini);
        else
            return fromInt(mini + getRandom(maxi - mini));
    } else {
        double mind = toDouble(min);
        double maxd = toDouble(max);
        if (mind > maxd) {
            double temp = mind;
            mind = maxd;
            maxd = temp;
        }
        if (maxd == mind)
            return fromDouble(mind);
        else {
            return fromDouble(mind + randomDouble() * (maxd - mind));
        }
    }
}

#define SINGLE(op) return fromDouble(::op(toDouble(x)));

//%
TNumber log(TNumber x){SINGLE(log)}

//%
TNumber log10(TNumber x){SINGLE(log10)}

//%
TNumber tan(TNumber x){SINGLE(tan)}

//%
TNumber sin(TNumber x){SINGLE(sin)}

//%
TNumber cos(TNumber x){SINGLE(cos)}

//%
TNumber atan(TNumber x){SINGLE(atan)}

//%
TNumber asin(TNumber x){SINGLE(asin)}

//%
TNumber acos(TNumber x){SINGLE(acos)}

//%
TNumber sqrt(TNumber x){SINGLE(sqrt)}

//%
TNumber floor(TNumber x){SINGLE(floor)}

//%
TNumber ceil(TNumber x){SINGLE(ceil)}

//%
TNumber trunc(TNumber x){SINGLE(trunc)}

//%
TNumber round(TNumber x) {
    SINGLE(round)
}

//%
int imul(int x, int y) {
    return x * y;
}

//%
int idiv(int x, int y) {
    return x / y;
}
} // namespace Math_

namespace Array_ {
//%
RefCollection *mk(unsigned flags) {
    auto r = new RefCollection();
    MEMDBG("mkColl: %p", r);
    return r;
}
//%
int length(RefCollection *c) {
    return c->length();
}
//%
void setLength(RefCollection *c, int newLength) {
    c->setLength(newLength);
}
//%
void push(RefCollection *c, TValue x) {
    c->push(x);
}
//%
TValue pop(RefCollection *c) {
    return c->pop();
}
//%
TValue getAt(RefCollection *c, int x) {
    return c->getAt(x);
}
//%
void setAt(RefCollection *c, int x, TValue y) {
    c->setAt(x, y);
}
//%
TValue removeAt(RefCollection *c, int x) {
    return c->removeAt(x);
}
//%
void insertAt(RefCollection *c, int x, TValue value) {
    c->insertAt(x, value);
}
//%
int indexOf(RefCollection *c, TValue x, int start) {
    return c->indexOf(x, start);
}
//%
bool removeElement(RefCollection *c, TValue x) {
    return c->removeElement(x);
}
} // namespace Array_

namespace pxt {
//%
void *ptrOfLiteral(int offset);

//%
unsigned programSize() {
    return bytecode[17] * 2;
}

//%
int getConfig(int key, int defl) {
    int *cfgData = *(int **)&bytecode[18];
    for (int i = 0;; i += 2) {
        if (cfgData[i] == key)
            return cfgData[i + 1];
        if (cfgData[i] == 0)
            return defl;
    }
}

} // namespace pxt

namespace pxtrt {
//%
TValue ldloc(RefLocal *r) {
    return r->v;
}

//%
TValue ldlocRef(RefRefLocal *r) {
    TValue tmp = r->v;
    incr(tmp);
    return tmp;
}

//%
void stloc(RefLocal *r, TValue v) {
    r->v = v;
}

//%
void stlocRef(RefRefLocal *r, TValue v) {
    decr(r->v);
    r->v = v;
}

//%
RefLocal *mkloc() {
    auto r = new RefLocal();
    MEMDBG("mkloc: %p", r);
    return r;
}

//%
RefRefLocal *mklocRef() {
    auto r = new RefRefLocal();
    MEMDBG("mklocRef: %p", r);
    return r;
}

// All of the functions below unref() self. This is for performance reasons -
// the code emitter will not emit the unrefs for them.

//%
TValue ldfld(RefRecord *r, int idx) {
    TValue tmp = r->ld(idx);
    r->unref();
    return tmp;
}

//%
TValue ldfldRef(RefRecord *r, int idx) {
    TValue tmp = r->ldref(idx);
    r->unref();
    return tmp;
}

//%
void stfld(RefRecord *r, int idx, TValue val) {
    r->st(idx, val);
    r->unref();
}

//%
void stfldRef(RefRecord *r, int idx, TValue val) {
    r->stref(idx, val);
    r->unref();
}

// Store a captured local in a closure. It returns the action, so it can be chained.
//%
RefAction *stclo(RefAction *a, int idx, TValue v) {
    // DBG("STCLO "); a->print(); DBG("@%d = %p\n", idx, (void*)v);
    a->stCore(idx, v);
    return a;
}

//%
void panic(int code) {
    target_panic(code);
}

//%
String emptyToNull(String s) {
    if (!s || s->length == 0)
        return NULL;
    return s;
}

//%
int ptrToBool(TValue p) {
    if (p) {
        decr(p);
        return 1;
    } else {
        return 0;
    }
}

//%
RefMap *mkMap() {
    auto r = new RefMap();
    MEMDBG("mkMap: %p", r);
    return r;
}

//%
TValue mapGet(RefMap *map, unsigned key) {
    int i = map->findIdx(key);
    if (i < 0) {
        map->unref();
        return 0;
    }
    TValue r = incr(map->values.get(i));
    map->unref();
    return r;
}

//%
TValue mapGetRef(RefMap *map, unsigned key) {
    return mapGet(map, key);
}

//%
void mapSet(RefMap *map, unsigned key, TValue val) {
    int i = map->findIdx(key);
    if (i < 0) {
        map->keys.push((TValue)key);
        map->values.push(val);
    } else {
        map->values.setRef(i, val);
    }
    map->unref();
}

//%
void mapSetRef(RefMap *map, unsigned key, TValue val) {
    mapSet(map, key, val);
}

//
// Debugger
//

// This is only to be called once at the beginning of lambda function
//%
void *getGlobalsPtr() {
#ifdef DEVICE_GROUP_ID_USER
    fiber_set_group(DEVICE_GROUP_ID_USER);
#endif

    return globals;
}

//%
void runtimeWarning(String s) {
    // noop for now
}
} // namespace pxtrt
#endif

namespace pxt {

//%
ValType valType(TValue v) {
    if (isTagged(v)) {
        if (!v)
            return ValType::Undefined;

        if (isNumber(v))
            return ValType::Number;
        if (v == TAG_TRUE || v == TAG_FALSE)
            return ValType::Boolean;
        else if (v == TAG_NULL)
            return ValType::Object;
        else {
            oops();
            return ValType::Object;
        }
    } else {
        int tag = ((RefObject *)v)->vtable;

        if (tag == PXT_REF_TAG_STRING)
            return ValType::String;
        else if (tag == PXT_REF_TAG_NUMBER)
            return ValType::Number;
        else if (tag == PXT_REF_TAG_ACTION || getVTable((RefObject *)v) == &RefAction_vtable)
            return ValType::Function;

        return ValType::Object;
    }
}

PXT_DEF_STRING(sObjectTp, "\x06\x00object")
PXT_DEF_STRING(sBooleanTp, "\x07\x00boolean")
PXT_DEF_STRING(sStringTp, "\x06\x00string")
PXT_DEF_STRING(sNumberTp, "\x06\x00number")
PXT_DEF_STRING(sFunctionTp, "\x08\x00function")
PXT_DEF_STRING(sUndefinedTp, "\x09\x00undefined")

//%
String typeOf(TValue v) {
    switch (valType(v)) {
    case ValType::Undefined:
        return (String)sUndefinedTp;
    case ValType::Boolean:
        return (String)sBooleanTp;
    case ValType::Number:
        return (String)sNumberTp;
    case ValType::String:
        return (String)sStringTp;
    case ValType::Object:
        return (String)sObjectTp;
    case ValType::Function:
        return (String)sFunctionTp;
    default:
        oops();
        return 0;
    }
}

// Maybe in future we will want separate print methods; for now ignore
void anyPrint(TValue v) {
    if (valType(v) == ValType::Object) {
        if (isRefCounted(v)) {
            auto o = (RefObject *)v;
            auto meth = ((RefObjectMethod)getVTable(o)->methods[1]);
            if ((void *)meth == (void *)&anyPrint)
                DMESG("[RefObject refs=%d vt=%p]", o->refcnt, o->vtable);
            else
                meth(o);
        } else {
            DMESG("[Native %p]", v);
        }
    } else {
#ifndef X86_64
        String s = numops::toString(v);
        DMESG("[%s %p = %s]", pxt::typeOf(v)->data, v, s->data);
        decr((TValue)s);
#endif
    }
}

void dtorDoNothing() {}

#define PRIM_VTABLE(name, sz)                                                                      \
    const VTable name = {sz,                                                                       \
                         0,                                                                        \
                         0,                                                                        \
                         {                                                                         \
                             (void *)&dtorDoNothing,                                               \
                             (void *)&anyPrint,                                                    \
                         }};
PRIM_VTABLE(string_vt, 0)
PRIM_VTABLE(image_vt, 0)
PRIM_VTABLE(buffer_vt, 0)
PRIM_VTABLE(number_vt, 12)
PRIM_VTABLE(action_vt, 0)

static const VTable *primVtables[] = {0,          // 0
                                      &string_vt, // 1
                                      &buffer_vt, // 2
                                      &image_vt,  // 3
                                      // filler:
                                      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                      0, 0, 0, 0, 0, 0, 0,
                                      &number_vt, // 32
                                      &action_vt, // 33
                                      0};

VTable *getVTable(RefObject *r) {
    if (r->vtable >= 34)
        return (VTable *)((uintptr_t)r->vtable << vtableShift);
    if (r->vtable == 0)
        target_panic(100);
    return (VTable *)primVtables[r->vtable];
}
} // namespace pxt