/********************************************************************
** 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_map.h"
#include "be_string.h"
#include "be_vector.h"
#include "be_class.h"
#include "be_mem.h"
#include "be_gc.h"
#include "be_vm.h"
#include "be_exec.h"
#include <string.h>

#define key(node)           (&(node)->key)
#define value(node)         (&(node)->value)
#define isnil(node)         var_isnil(key(node))
#define setnil(node)        var_setnil(key(node))
#define hash2slot(m, h)     ((m)->slots + (h) % (m)->size)
#define hashcode(_v)        _hashcode(vm, (_v)->type, (_v)->v)
#define keytype(key)        ((signed char)(key)->type)

#define next(node)          ((node)->key.next)
#define pos2slot(map, n)    ((n) != LASTNODE ? ((map)->slots + (n)) : NULL)
#define pos(map, node)      ((int)((node) - (map)->slots))
#define setkey(node, _v)    { (node)->key.type = (bbyte)(_v)->type; \
                              (node)->key.v = (_v)->v; }

#define datasize(size)      ((size) * sizeof(bmapnode))

#define LASTNODE            ((1 << 24) - 1)

static int map_nextsize(int size)
{
    be_assert(size < LASTNODE);
    if (size < LASTNODE) {
        return be_nextsize(size);
    }
    return LASTNODE + 1;
}

static uint32_t hashptr(void *p)
{
    uintptr_t i = (uintptr_t)p;
    return (uint32_t)((i ^ (i >> 16)) & 0xFFFFFFFF);
}

#if BE_USE_SINGLE_FLOAT
#define hashreal(v)     ((uint32_t)((v).i))
#else
static uint32_t hashreal(union bvaldata v)
{
    union { breal r; uint32_t i[2]; } u;
    u.r = v.r;
    return u.i[0] ^ u.i[1];
}
#endif

#if BE_USE_OVERLOAD_HASH
static uint32_t hashins(bvm *vm, binstance *obj)
{
    int type = be_instance_member(vm, obj, str_literal(vm, "hash"), vm->top);
    if (basetype(type) == BE_FUNCTION) {
        bvalue *top = vm->top;
        var_setinstance(top + 1, obj);
        vm->top += 2;
        be_dofunc(vm, top, 1); /* call method 'item' */
        vm->top -= 2;
        if (!var_isint(vm->top)) { /* check the return value */
            const char *name = str(be_instance_name(obj));
            be_raise(vm, "runtime_error", be_pushfstring(vm,
                "the value of `%s::hash()` is not a 'int'",
                strlen(name) ? name : "<anonymous>"));
        }
        return (uint32_t)var_toint(vm->top);
    }
    return hashptr(obj);
}
#endif

static uint32_t _hashcode(bvm *vm, int type, union bvaldata v)
{
    (void)vm;
    switch (type) {
    case BE_NIL: return 0;
    case BE_BOOL: return (uint32_t)v.b;
    case BE_INT: return (uint32_t)v.i;
    case BE_REAL: return hashreal(v);
    case BE_STRING: return be_strhash(v.s);
#if BE_USE_OVERLOAD_HASH
    case BE_INSTANCE: return hashins(vm, v.p);
#endif
    default: return hashptr(v.p);
    }
}

static int eqnode(bvm *vm, bmapnode *node, bvalue *key, uint32_t hash)
{
    (void)vm;
    if (!var_isnil(key)) {
        bmapkey *k = key(node);
#if BE_USE_OVERLOAD_HASH
        if (var_isinstance(key)) {
            bvalue kv;
            kv.type = k->type;
            kv.v = k->v;
            return be_vm_iseq(vm, key, &kv);
        }
#endif
        if(keytype(k) == key->type && hashcode(k) == hash) {
            switch (key->type) {
            case BE_BOOL: return var_tobool(key) == var_tobool(k);
            case BE_INT: return var_toint(key) == var_toint(k);
            case BE_REAL: return var_toreal(key) == var_toreal(k);
            case BE_STRING: return be_eqstr(var_tostr(key), var_tostr(k));
            default: return var_toobj(key) == var_toobj(k);
            }
        }
    }
    return 0;
}

static bmapnode* findprev(bmap *map, bmapnode *list, bmapnode *slot)
{
    int n, pos = pos(map, slot);
    bmapnode *prev = list;
    for (;;) {
        n = next(prev);
        if (n == pos || n == LASTNODE) {
            break;
        }
        prev = map->slots + n;
    }
    return n == pos ? prev : NULL;
}

static bmapnode* nextfree(bmap *map)
{
    bmapnode *base = map->slots;
    while (map->lastfree >= base) {
        if (isnil(map->lastfree)) {
            return map->lastfree;
        }
        --map->lastfree;
    }
    return NULL;
}

static bmapnode* insert(bvm *vm, bmap *map, bvalue *key, uint32_t hash)
{
    bmapnode *slot = hash2slot(map, hash);
    if (isnil(slot)) { /* empty slot */
        setkey(slot, key);
        next(slot) = LASTNODE;
    } else {
        uint32_t h = hashcode(key(slot)); /* get the hashcode of the exist node */
        bmapnode *mainslot = hash2slot(map, h); /* get the main-slot */
        bmapnode *new = nextfree(map); /* get a free slot */
        if (mainslot == slot) { /* old is main slot */
            /* insert in first */
            setkey(new, key);
            next(new) = next(slot);
            next(slot) = pos(map, new);
            slot = new;
        } else {
            bmapnode *prev = findprev(map, mainslot, slot);
            next(prev) = pos(map, new); /* link the previous node */
            *new = *slot; /* copy to new slot */
            setkey(slot, key);
            next(slot) = LASTNODE;
        }
    }
    return slot;
}

static bmapnode* find(bvm *vm, bmap *map, bvalue *key, uint32_t hash)
{
    bmapnode *slot = hash2slot(map, hash);
    if (isnil(slot)) {
        return NULL;
    }
    while (!eqnode(vm, slot, key, hash)) {
        int n = next(slot);
        if (n == LASTNODE) {
            return NULL;
        }
        slot = map->slots + n;
    }
    return slot;
}

static void resize(bvm *vm, bmap *map, int size)
{
    int i, oldsize;
    bmapnode *slots, *oldslots;
    if (size < map->count) {
        return;
    }
    oldsize = map->size;
    oldslots = map->slots;
    slots = be_malloc(vm, datasize(size));
    for (i = 0; i < size; ++i) {
        setnil(slots + i);
        next(slots + i) = LASTNODE;
    }
    map->size = size;
    map->slots = slots;
    map->lastfree = slots + size - 1;
    /* rehash */
    for (i = 0; i < oldsize; ++i) {
        bmapnode *node = oldslots + i;
        if (!isnil(node)) {
            bvalue v;
            bmapnode *newslot;
            v.type = node->key.type;
            v.v = node->key.v;
            newslot = insert(vm, map, &v, hashcode(&v));
            newslot->value = node->value;
        }
    }
    be_free(vm, oldslots, datasize(oldsize));
}

bmap* be_map_new(bvm *vm)
{
    bgcobject *gco = be_gcnew(vm, BE_MAP, bmap);
    bmap *map = cast_map(gco);
    if (map) {
        map->size = 0;
        map->count = 0;
        map->slots = NULL;
        var_setmap(vm->top, map);
        be_incrtop(vm);
        resize(vm, map, 2);
        be_stackpop(vm, 1);
    }
    return map;
}

void be_map_delete(bvm *vm, bmap *map)
{
    be_free(vm, map->slots, datasize(map->size));
    be_free(vm, map, sizeof(bmap));
}

bvalue* be_map_find(bvm *vm, bmap *map, bvalue *key)
{
    bmapnode *entry = find(vm, map, key, hashcode(key));
    return entry ? value(entry) : NULL;
}

bvalue* be_map_insert(bvm *vm, bmap *map, bvalue *key, bvalue *value)
{
    uint32_t hash = hashcode(key);
    bmapnode *entry = find(vm, map, key, hash);
    if (!entry) { /* new entry */
        if (map->count >= map->size) {
            resize(vm, map, map_nextsize(map->size));
        }
        entry = insert(vm, map, key, hash);
        ++map->count;
    }
    if (value) {
        entry->value = *value;
    }
    return value(entry);
}

int be_map_remove(bvm *vm, bmap *map, bvalue *key)
{
    uint32_t hash = hashcode(key);
    bmapnode *slot = hash2slot(map, hash); /* main slot */

    if (eqnode(vm, slot, key, hash)) { /* first node */
        bmapnode *next = pos2slot(map, next(slot));
        if (next) { /* has next */
            *slot = *next; /* first: copy the second node to the slot */
            slot = next; /* second: set the second node to nil (empty) */
        }
    } else { /* the node will be remove is not first-node */
        bmapnode *prev = slot;
        for (;;) { /* find the previous node */
            int n = next(prev);
            slot = pos2slot(map, n);
            if (slot == NULL) { /* node not found */
                return bfalse;
            }
            if (eqnode(vm, slot, key, hash)) {
                break;
            }
            prev = slot;
        }
        /* link the list */
        next(prev) = next(slot);
    }
    /* set to nil */
    setnil(slot);
    /* add to lastfree */
    if (map->lastfree < slot) {
        map->lastfree = slot;
    }
    --map->count;
    return btrue;
}

bvalue* be_map_findstr(bvm *vm, bmap *map, bstring *key)
{
    bvalue v;
    var_setstr(&v, key);
    return be_map_find(vm, map, &v);
}

bvalue* be_map_insertstr(bvm *vm, bmap *map, bstring *key, bvalue *value)
{
    bvalue v;
    var_setstr(&v, key);
    set_fixed(key);
    bvalue * r = be_map_insert(vm, map, &v, value);
    restore_fixed(key);
    return r;
}

void be_map_removestr(bvm *vm, bmap *map, bstring *key)
{
    bvalue v;
    var_setstr(&v, key);
    be_map_remove(vm, map, &v);
}

bmapnode* be_map_next(bmap *map, bmapiter *iter)
{
    bmapnode *end = map->slots + map->size;
    *iter = *iter ? *iter + 1 : map->slots;
    while (*iter < end && isnil(*iter)) {
        ++(*iter);
    }
    return *iter < end ? *iter : NULL;
}

bmapnode* be_map_val2node(bvalue *value)
{
    return (bmapnode *)((size_t)value - sizeof(bmapkey));
}

void be_map_compact(bvm *vm, bmap *map)
{
    (void)vm;
    if (!gc_isconst(map)) {
        resize(vm, map, map->count ? map->count : 1);
    }
}
