#include "core/xmalloc.h"
#include "core/vector.h"
#include "core/termpool.h"
#include "core/idtable.h"
#include "core/stuff.h"

/*--------------------------------------------------------------------*/

/* table.c */
TERM    unnumberVarTerm(TERM, BPLONG_PTR, BPLONG_PTR);

/*--------------------------------------------------------------------*/

struct id_table {
    TERM_POOL *store;
    struct id_table_entry *elems;
    IDNUM *bucks;
    IDNUM nbucks;
};

struct id_table_entry {
    TERM  term;
    IDNUM next;
};

/*--------------------------------------------------------------------*/

static void id_table_rehash(ID_TABLE *this)
{
    IDNUM *bucks, nbucks, i, j;

    nbucks = 2 * this->nbucks + 1;

    /* find the next prime number */
    for (i = 3; i * i <= nbucks; ) {
        if (nbucks % i == 0) {
            nbucks += 2;
            i = 3;
        }
        else {
            i += 2;
        }
    }

    bucks = MALLOC(sizeof(struct hash_entry *) * nbucks);

    for (i = 0; i < nbucks; i++)
        bucks[i] = ID_NONE;

    for (i = 0; i < VECTOR_SIZE(this->elems); i++) {
        j = (IDNUM)((BPULONG)(this->elems[i].term) % nbucks);
        this->elems[i].next = bucks[j];
        bucks[j] = i;
    }

	FREE(this->bucks);

    this->nbucks = nbucks;
    this->bucks = bucks;
}

static IDNUM id_table_search(const ID_TABLE *this, TERM term)
{
    BPULONG hash;
    IDNUM i;

    hash = (BPULONG)(term);

    i = this->bucks[hash % this->nbucks];

    while (i != ID_NONE) {
        if (term == this->elems[i].term) {
            return i;
        }
        i = this->elems[i].next;
    }

    return ID_NONE;
}

static IDNUM id_table_insert(ID_TABLE *this, TERM term)
{
    BPULONG hash;
    IDNUM n;
    const char *bpx_term_2_string(TERM);

    hash = (BPULONG)(term);

    n = (IDNUM)(VECTOR_SIZE(this->elems));

    if (n >= this->nbucks) {
        id_table_rehash(this);
    }

    VECTOR_PUSH_NONE(this->elems);
    this->elems[n].term = term;
    this->elems[n].next = this->bucks[hash % this->nbucks];
    this->bucks[hash % this->nbucks] = n;

    /* fprintf(curr_out,">> TERM: %s = %d\n",bpx_term_2_string(term),n); */

    return n;
}

/*--------------------------------------------------------------------*/

ID_TABLE * id_table_create(void)
{
    ID_TABLE *this;
    IDNUM i;

    this = MALLOC(sizeof(struct id_table));
	
    this->elems  = NULL;
    this->nbucks = 17;  /* prime number */
    this->bucks  = MALLOC(sizeof(IDNUM) * this->nbucks);
    this->store  = term_pool_create();

    for (i = 0; i < this->nbucks; i++)
        this->bucks[i] = ID_NONE;

    VECTOR_INIT(this->elems);
    return this;
}

void id_table_delete(ID_TABLE *this)
{
    VECTOR_FREE(this->elems);
    FREE(this->bucks);
    term_pool_delete(this->store);

    FREE(this);
}

/*--------------------------------------------------------------------*/

TERM id_table_id2term(const ID_TABLE *this, IDNUM i)
{
    return this->elems[i].term; /* numbered */
}

IDNUM id_table_retrieve(const ID_TABLE *this, TERM term)
{
    term = term_pool_retrieve(this->store, term);

    return id_table_search(this, term);
}

IDNUM id_table_register(ID_TABLE *this, TERM term)
{
    BPULONG hash;
    IDNUM i;

    term = term_pool_register(this->store, term);
    hash = (BPULONG)(term);

    i = id_table_search(this, term);
    if (i == ID_NONE) {
        i = id_table_insert(this, term);
    }
    return i;
}

int id_table_count(const ID_TABLE *this)
{
    return (int)VECTOR_SIZE(this->elems);
}

/*--------------------------------------------------------------------*/

TERM unnumber_var_term(TERM term)
{
  CACHE_REGS
    BPLONG mvn = -1;
    return unnumberVarTerm(term, local_top, &mvn);
}
