#include <assert.h>
#include <stdlib.h>
#include "ramjet.h"
#include "eval.h"
#include "move.h"
#include "hash.h"

// release the memory taken by the entries
void hash_free(HASHTABLE* ht)
{
    if(ht == NULL) return;

    free(ht->entry);
    ht->entry = NULL;
    ht->n = 0;
}

// allocate the given number of entries
int hash_alloc_n(HASHTABLE* ht, int n)
{
    if(ht == NULL) return -1;

    hash_free(ht);

    ht->entry = (HASHENTRY*) malloc(n * sizeof(HASHENTRY));
    if(ht->entry == NULL) return -1;
    ht->n = n & ~1;

    return 0;
}

// allocate the given space in MB
int hash_alloc_mb(HASHTABLE* ht, int mb)
{
    return hash_alloc_n(ht, (mb * 1048576) / sizeof(HASHENTRY));
}

// initialise the hash table
void hash_init(HASHTABLE* ht)
{
    if(ht == NULL) return;

    ht->entry = NULL;
    hash_alloc_n(ht, 2);
}

// set all entries to invalid
void hash_clear(HASHTABLE* ht)
{
    assert(ht != NULL);

    int i;
    for(i = 0; i < ht->n; ++i) {
        ht->entry[i].mv = MV_NULL;
        ht->entry[i].scr = 0;
        ht->entry[i].depth = 0;
        ht->entry[i].flag = HF_NULL;
    }
}

// compute the entry replacement priority
static int hash_entry_value(HASHENTRY* entry, HASHKEY key)
{
    assert(entry != NULL);

    int val = 0xFF - entry->depth;
    if(entry->key == key) val += 0x200;
    if(entry->flag == HF_NULL) val += 0x100;
    return val;
}

// store a new entry
void hash_insert(HASHTABLE* ht, HASHKEY key, MOVE mv, EVAL scr, int ply, int depth, HASHFLAG flag)
{
    assert(ht != NULL);

    // two-slots bucket (depth-preferred always replace)
    int slot = (key % ht->n) & ~1;
    if(hash_entry_value(&ht->entry[slot + 1], key) > hash_entry_value(&ht->entry[slot], key))
        ++slot;

    // overwrite the entry
    ht->entry[slot].key = key;
    ht->entry[slot].mv = (HASHMOVE) (mv & 0xFFFF);
    ht->entry[slot].scr = (HASHEVAL) (eval_ply_2_abs(scr, ply) & 0xFFFF);
    ht->entry[slot].depth = (HASHDEPTH) (depth & 0xFF);
    ht->entry[slot].flag = flag;
}

// probe the hash table
HASHPROBE hash_probe(HASHTABLE* ht, HASHKEY key, MOVE* mv, EVAL* scr, int ply, int depth, EVAL alpha, EVAL beta)
{
    assert(ht != NULL);

    // search for a match (WARNING: IT ACCEPTS OLD ENTRIES)
    int slot = (key % ht->n) & ~1;
    if(ht->entry[slot + 1].key == key)
        ++slot;
    else if(ht->entry[slot].key != key)
        return HP_NONE;

    // return best move and score
    *mv = (MOVE) ht->entry[slot].mv;
    *scr = eval_abs_2_ply((EVAL) ht->entry[slot].scr, ply);

    // pruning decision
    if(ht->entry[slot].depth >= depth && (ht->entry[slot].flag == HF_EXACT ||
                                         (ht->entry[slot].flag == HF_ALPHA && *scr <= alpha) ||
                                         (ht->entry[slot].flag == HF_BETA && *scr >= beta)))
        return HP_PRUNE;

    return HP_MOVE;
}

// test the functions above
int hash_test()
{
    HASHTABLE ht; MOVE mv; EVAL scr;
    hash_alloc_n(&ht, 2);
    hash_clear(&ht);

    // test insertion (empty and matching slots) and probing
    hash_insert(&ht, 0, MV_NULL, 0, 0, 0, HF_EXACT);
    hash_insert(&ht, 1, MV_NULL, 0, 0, 3, HF_EXACT);
    hash_insert(&ht, 0, MV_NULL, 0, 0, 1, HF_EXACT);
    if(hash_probe(&ht, 0, &mv, &scr, 0, 1, 0, 0) != HP_PRUNE ||
       hash_probe(&ht, 1, &mv, &scr, 0, 4, 0, 0) != HP_MOVE)
        return 1;

    //test insertion (depth-preferred always replace)
    hash_insert(&ht, 2, MV_NULL, 0, 0, 4, HF_EXACT);
    hash_insert(&ht, 0, MV_NULL, 0, 0, 2, HF_EXACT);
    if(hash_probe(&ht, 1, &mv, &scr, 0, 1, 0, 0) != HP_NONE ||
       hash_probe(&ht, 0, &mv, &scr, 0, 3, 0, 0) != HP_MOVE ||
       hash_probe(&ht, 2, &mv, &scr, 0, 4, 0, 0) != HP_PRUNE)
        return 2;

    return 0;
}
