#include <assert.h>
#include "core/termpool.h"
#include "core/xmalloc.h"
#include "core/vector.h"
#include "core/stuff.h"

/* FIXME */
#define prism_quit(msg) quit("***  {PRISM FATAL ERROR: " msg "}\n")
NORET quit(const char *);

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

/* [04 Apr 2009, by yuizumi]
 * This value should be sufficiently large enough to have malloc(3)
 * return an address with its top bit set on 32-bit Linux systems.
 */
#define BLOCK_SIZE 1048576

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

/* [05 Apr 2009, by yuizumi]
 * The area referred by this variable is shared by prism_hash_value()
 * and term_pool_store(), under the assumption that BPLONG values and
 * BPLONG_PTR values (i.e. pointers) are aligned in the same way even
 * without cast operations.
 */
static BPLONG_PTR work;

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

struct term_pool {
    BPLONG_PTR head;
    BPLONG_PTR curr;
    BPLONG_PTR tail;
    struct hash_entry **bucks;
    size_t nbucks;
    size_t count;
};

struct hash_entry {
    TERM term;
    BPULONG hash;
    struct hash_entry *next;
};

/*--------------------------------------------------------------------*/
/*                      Functions from B-Prolog                       */

/* mic.c */
void    c_STATISTICS(void);

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

/* unify.c */
int     unifyNumberedTerms(TERM, TERM);

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

static ptrdiff_t trail_pos0 = 0;

static void number_vars(TERM term)
{
  CACHE_REGS
    assert(trail_pos0 == 0);

    trail_pos0 = trail_up_addr - trail_top;
    PRE_NUMBER_VAR(0);
    numberVarTermOpt(term);

    if (number_var_exception != 0) {
        prism_quit("suspension variables not supported in Prism");
    }
}

static void revert_vars(void)
{
  CACHE_REGS
    BPLONG_PTR trail_top0;

    assert(trail_pos0 != 0);

    trail_top0 = trail_up_addr - trail_pos0;
    UNDO_TRAILING;
    trail_pos0 = 0;
}

/* [29 Mar 2009, by yuizumi]
 * See Also: "Algorithms in C, Third Edition," by Robert Sedgewick,
 * Addison-Wesley, 1998.
 */
static BPULONG prism_hash_value(TERM term)
{
  CACHE_REGS
    TERM    t, *rest;
    BPLONG  i, n;
    SYM_REC_PTR sym;

    BPULONG a = 2130563839ul;
    BPULONG b = 1561772629ul;
    BPULONG h = 0;
    BPULONG u;

    rest = (TERM *)work;

    VECTOR_PUSH(rest, term);

    while (! VECTOR_EMPTY(rest)) {
        t = VECTOR_POP(rest);

nderef_loop:
        switch (XTAG(t)) {
        case REF0:
        case REF1:
            XNDEREF(t, nderef_loop);
            assert(false); /* numbered by number_vars() */

        case ATM:
        case INT:
        case NVAR:
	    u = (BPULONG)t;
            break;

        case LST:
            VECTOR_PUSH(rest, GET_CDR(t));
            VECTOR_PUSH(rest, GET_CAR(t));
            u = (BPULONG)LST;
            break;

        case STR:
            sym = GET_STR_SYM_REC(t);
            n = GET_ARITY_STR(sym);
            for (i = n; i >= 1; i--) {
                VECTOR_PUSH(rest, GET_ARG(t, i));
            }
            u = (BPULONG)ADDTAG(sym, STR);
            break;

        case SUSP:
            assert(false); /* rejected by number_vars() */

        default:
            assert(false);
        }
        h = (a * h) + (BPULONG)(u);
        a *= b;
    }

    work = (BPLONG *)rest;
    return h;
}

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

static BPLONG_PTR term_pool_allocate(TERM_POOL *this, size_t size)
{
    BPLONG_PTR p_tmp;

    assert(size <= MAX_ARITY + 1);

    if (this->head == NULL || this->curr + size > this->tail) {
	    BP_MALLOC(p_tmp, BLOCK_SIZE, "(prism part)");
        *p_tmp = (BPLONG)(this->head);
        this->head = p_tmp + 0;
        this->curr = p_tmp + 1;
        this->tail = p_tmp + BLOCK_SIZE;
    }

    p_tmp = this->curr;
    this->curr += size;
    return p_tmp;
}

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

static TERM term_pool_store(TERM_POOL *this, TERM term)
{
    TERM    *p, *q, **rest;
    BPLONG  i, n;

    SYM_REC_PTR sym;

    rest = (void *)(work);

    VECTOR_PUSH(rest, &term);

    while (! VECTOR_EMPTY(rest)) {
        p = VECTOR_POP(rest);

nderef_loop:
        switch (XTAG(*p)) {
        case REF0:
        case REF1:
            XNDEREF(*p, nderef_loop);
            assert(false); /* numbered by number_vars() */

        case ATM:
        case INT:
        case NVAR:
            break;

        case LST:
            q = term_pool_allocate(this, 2);
            *(q + 1) = GET_CDR(*p);
            VECTOR_PUSH(rest, q + 1);
            *(q + 0) = GET_CAR(*p);
            VECTOR_PUSH(rest, q + 0);
            *p = ADDTAG(q, LST);
            break;

        case STR:
            sym = GET_STR_SYM_REC(*p);
            n = GET_ARITY_STR(sym);
            q = term_pool_allocate(this, n + 1);
            *q = (TERM)(sym);
            for (i = n; i >= 1; i--) {
                *(q + i) = GET_ARG(*p, i);
                VECTOR_PUSH(rest, q + i);
            }
            *p = ADDTAG(q, STR);
            break;

        case SUSP:
            assert(false); /* rejected by number_vars() */

        default:
            assert(false);
        }
    }

    work = (void *)(rest);
    return term;
}

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

static void term_pool_rehash(TERM_POOL *this)
{
    struct hash_entry **bucks, *p, *q;
    size_t nbucks, i;

    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] = NULL;

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

        while (p != NULL) {
            q = p;
            p = p->next;
            q->next = bucks[q->hash % nbucks];
            bucks[q->hash % nbucks] = q;
        }
    }

	FREE(this->bucks);

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

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

static TERM term_pool_search(const TERM_POOL *this, TERM term, BPULONG hash)
{
    struct hash_entry *p;

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

    while (p != NULL) {
        if (hash == p->hash) {
            if (unifyNumberedTerms(term, p->term)) {
                return p->term;
            }
        }
        p = p->next;
    }

    return NULL_TERM;
}

static TERM term_pool_insert(TERM_POOL *this, TERM term, BPULONG hash)
{
    struct hash_entry *entry;

    if (++(this->count) >= this->nbucks)
        term_pool_rehash(this);

    entry = MALLOC(sizeof(struct hash_entry));
    entry->term = term_pool_store(this, term);
    entry->hash = hash;
    entry->next = this->bucks[hash % this->nbucks];
    this->bucks[hash % this->nbucks] = entry;

    return entry->term;
}

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

static TERM term_pool_intern(const TERM_POOL *this1, TERM_POOL *this2, TERM term)
{
    BPULONG hash;
    TERM    rval;

    assert(this2 == NULL || this2 == this1);

nderef_loop:
    switch (XTAG(term)) {
    case REF0:
    case REF1:
        XNDEREF(term, nderef_loop);
        return MAKE_NVAR(0);

    case ATM:
    case INT:
    case NVAR:
        return term;

    case LST:
    case STR:
        break;

    case SUSP:
        prism_quit("suspension variables not supported in Prism");

    default:
        assert(false);
    }

    number_vars(term);

    hash = prism_hash_value(term);
    rval = term_pool_search(this1, term, hash);

    if (rval == NULL_TERM && this2 != NULL) {
        rval = term_pool_insert(this2, term, hash);
    }

    revert_vars();

    return rval;
}

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

TERM_POOL * term_pool_create(void)
{
    TERM_POOL *this;
    int i;

    this = MALLOC(sizeof(struct term_pool));

    this->head   = NULL;
    this->curr   = NULL;
    this->tail   = NULL;
    this->nbucks = 17;
    this->count  = 0;
    this->bucks  = MALLOC(sizeof(struct hash_entry *) * this->nbucks);

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

    if (work == NULL) {
        VECTOR_INIT_CAPA(work, 4096);
    }

    return this;
}

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

void term_pool_delete(TERM_POOL *this)
{
    BPLONG_PTR p1, p2;
    struct hash_entry *q1, *q2;
    int i;

    p1 = this->head;

    while (p1 != NULL) {
        p2 = p1;
        p1 = (BPLONG_PTR)(*p1);
        FREE(p2);
    }

    for (i = 0; i < this->nbucks; i++) {
        q1 = this->bucks[i];
        while (q1 != NULL) {
            q2 = q1;
            q1 = q1->next;
            FREE(q2);
        }
    }

    FREE(this->bucks);
    FREE(this);
}

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

TERM term_pool_retrieve(const TERM_POOL *this, TERM term)
{
    return term_pool_intern(this, NULL, term);
}

TERM term_pool_register(TERM_POOL *this, TERM term)
{
    return term_pool_intern(this, this, term);
}

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