// This file is a part of Julia. License is MIT: http://julialang.org/license

#define hash_size(h) (jl_array_len(h)/2)

// compute empirical max-probe for a given size
#define max_probe(size) ((size)<=1024 ? 16 : (size)>>6)

#define keyhash(k)     jl_object_id(k)
#define h2index(hv,sz) (index_t)(((hv) & ((sz)-1))*2)

static void **jl_table_lookup_bp(jl_array_t **pa, void *key);

void jl_idtable_rehash(jl_array_t **pa, size_t newsz)
{
    // Assume *pa don't need a write barrier
    // pa doesn't have to be a GC slot but *pa needs to be rooted
    size_t sz = jl_array_len(*pa);
    size_t i;
    void **ol = (void**)(*pa)->data;
    jl_array_t *newa = jl_alloc_cell_1d(newsz);
    // keep the original array in the original slot since we need `ol`
    // to be valid in the loop below.
    JL_GC_PUSH1(&newa);
    for(i=0; i < sz; i+=2) {
        if (ol[i+1] != NULL) {
            (*jl_table_lookup_bp(&newa, ol[i])) = ol[i+1];
            jl_gc_wb(newa, ol[i+1]);
             // it is however necessary here because allocation
            // can (and will) occur in a recursive call inside table_lookup_bp
        }
    }
    *pa = newa;
    // we do not check the write barrier here
    // because pa always points to a C stack location
    // (see jl_eqtable_put and jl_finalize_deserializer)
    // it should be changed if this assumption no longer holds
    JL_GC_POP();
}

static void **jl_table_lookup_bp(jl_array_t **pa, void *key)
{
    // pa points to a **rooted** gc frame slot
    uint_t hv;
    jl_array_t *a = *pa;
    size_t orig, index, iter;
    size_t newsz, sz = hash_size(a);
    size_t maxprobe = max_probe(sz);
    void **tab = (void**)a->data;

    hv = keyhash((jl_value_t*)key);
 retry_bp:
    iter = 0;
    index = h2index(hv,sz);
    sz *= 2;
    orig = index;

    do {
        if (tab[index+1] == NULL) {
            tab[index] = key;
            jl_gc_wb(a, key);
            return &tab[index+1];
        }

        if (jl_egal((jl_value_t*)key, (jl_value_t*)tab[index]))
            return &tab[index+1];

        index = (index+2) & (sz-1);
        iter++;
        if (iter > maxprobe)
            break;
    } while (index != orig);

    /* table full */
    /* quadruple size, rehash, retry the insert */
    /* it's important to grow the table really fast; otherwise we waste */
    /* lots of time rehashing all the keys over and over. */
    sz = jl_array_len(a);
    if (sz >= (1<<19) || (sz <= (1<<8)))
        newsz = sz<<1;
    else if (sz <= HT_N_INLINE)
        newsz = HT_N_INLINE;
    else
        newsz = sz<<2;
    jl_idtable_rehash(pa, newsz);

    a = *pa;
    tab = (void**)a->data;
    sz = hash_size(a);
    maxprobe = max_probe(sz);

    goto retry_bp;

    return NULL;
}

/* returns bp if key is in hash, otherwise NULL */
/* if return is non-NULL and *bp == NULL then key was deleted */
static void **jl_table_peek_bp(jl_array_t *a, void *key)
{
    size_t sz = hash_size(a);
    size_t maxprobe = max_probe(sz);
    void **tab = (void**)a->data;
    uint_t hv = keyhash((jl_value_t*)key);
    size_t index = h2index(hv, sz);
    sz *= 2;
    size_t orig = index;
    size_t iter = 0;

    do {
        if (tab[index] == NULL)
            return NULL;
        if (jl_egal((jl_value_t*)key, (jl_value_t*)tab[index]))
            return &tab[index+1];

        index = (index+2) & (sz-1);
        iter++;
        if (iter > maxprobe)
            break;
    } while (index != orig);

    return NULL;
}

DLLEXPORT
jl_array_t *jl_eqtable_put(jl_array_t *h, void *key, void *val)
{
    JL_GC_PUSH1(&h);
    // &h may be assigned to in jl_idtable_rehash so it need to be rooted
    void **bp = jl_table_lookup_bp(&h, key);
    *bp = val;
    jl_gc_wb(h, val);
    JL_GC_POP();
    return h;
}

DLLEXPORT
jl_value_t *jl_eqtable_get(jl_array_t *h, void *key, jl_value_t *deflt)
{
    void **bp = jl_table_peek_bp(h, key);
    if (bp == NULL || *bp == NULL)
        return deflt;
    return (jl_value_t*)*bp;
}

DLLEXPORT
jl_value_t *jl_eqtable_pop(jl_array_t *h, void *key, jl_value_t *deflt)
{
    void **bp = jl_table_peek_bp(h, key);
    if (bp == NULL || *bp == NULL)
        return deflt;
    jl_value_t *val = (jl_value_t*)*bp;
    *(bp-1) = jl_nothing; // clear the key
    *bp = NULL;
    return val;
}

DLLEXPORT
size_t jl_eqtable_nextind(jl_array_t *t, size_t i)
{
    if (i&1) i++;
    size_t alen = jl_array_dim0(t);
    while (i < alen && ((void**)t->data)[i+1] == NULL)
        i+=2;
    if (i >= alen) return (size_t)-1;
    return i;
}

#undef hash_size
#undef max_probe
