/*--------------------------------------------------------------------------
    Pawny 0.3, chess engine (source code).
    Copyright (C) 2009 - 2011 by Mincho Georgiev.
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    contact: pawnychess@gmail.com 
    web: http://www.pawny.netii.net/
----------------------------------------------------------------------------*/

#include "data.h"
#include <malloc.h>

#define BUCKET_SIZE 4
#define MAX_AGE 15
#define MAX_DRAFT 256
#define ALIGNMENT (sizeof(tt_t) * BUCKET_SIZE)

static int age;

void tt_free()  {aligned_free((void *)tt); tt = 0;}
void tt_clear() {aligned_wipe_out((void *)tt,(size_t)(opt->tt_size),(size_t)ALIGNMENT);}

int tt_init(int MB)
{///min size - 8MB, max - 1024:
	tt_free();
	if(MB < 8) MB = 8; if(MB > 1024) MB = 1024;
	opt->tt_size = MB*1024*1024;
	tt = (tt_t *)aligned_malloc((size_t)opt->tt_size, ALIGNMENT);
	if(!tt) return false;
	opt->tt_numbuckets = (opt->tt_size / ALIGNMENT) - 1;
	opt->tt_numentries = opt->tt_numbuckets * BUCKET_SIZE;
	tt_clear();
	age = 1;
	return true;
}

uint8 tt_probe(int depth, int *value, int beta, uint32 *h_move, uint8 *null_ok)
{
	tt_t *h_entry; int i;
	
	h_entry = tt + (board->hash & opt->tt_numentries);
	
	for(i = 0; i < BUCKET_SIZE; i++)
	{	if((h_entry+i)->key == board->hash)
			break;
	}
	if(i < BUCKET_SIZE)
	{	h_entry += i;
		//'is_null_move_relevant' test:
		if((h_entry->flag & 0x0F) == TT_ALPHA 
		&&  h_entry->value < beta
		&& ((depth - 3 - 1) <= h_entry->depth))
			*null_ok = false;
		
		*h_move = h_entry->move;
		if(h_entry->depth >= depth)
		{	*value = h_entry->value;
			return (h_entry->flag & 0x0F);
		}
	}
	return TT_UNKNOWN;
}

void tt_save(int depth, int value, uint8 flag, uint32 h_move)
{
	int i,draft_replace;
	tt_t *h_entry, *h_replace = 0;
	
	h_entry = tt + (board->hash & opt->tt_numentries);
	
	draft_replace = MAX_DRAFT; //'firstentry' hack
	
	for(i = 0; i < BUCKET_SIZE; i++)
	{	if((h_entry+i)->key == board->hash)
		{//direct hit -> unconditional replacement:
			h_entry += i;
			si->num_hash_saves++;
			h_entry->value = (sint16)value;
			h_entry->depth = (uint8)depth;
			h_entry->flag  = (uint8)(age << 4) | flag;
			if(h_move) h_entry->move = h_move;
			return;
		}

		//looking for 'another search' entry
		//that holds the lowest draft so far.
		if(((h_entry+i)->flag>>4) != age
		&& (h_entry+i)->depth < draft_replace)
		{	h_replace = h_entry + i;
			draft_replace = (h_entry + i)->depth;
		}
	}
	
	//still no entry to replace, so looking
	//for the entry with the lowest draft, regardless
	//of the hash generation:
	if(!h_replace)
	{	for(i = 0; i < BUCKET_SIZE; i++)
		{	if((h_entry+i)->depth < draft_replace)
			{	h_replace = h_entry + i;
				draft_replace = (h_entry + i)->depth;
			}
		}
	}
			
	//replacing the 'draft|age prefered' entry:
	si->num_hash_saves++;
	h_replace->key   = board->hash;
	h_replace->value = (sint16)value;
	h_replace->depth = (uint8)depth;
	h_replace->flag  = (uint8)(age << 4) | flag;
	if(h_move) h_replace->move = h_move;
}

void tt_check()
{
	if(age > MAX_AGE) age = 1;
	else age++;
}	

uint32 tt_get_hashmove()
{///getting the hash move for the current pos.

	int i;
	
	tt_t *h_entry = tt + (board->hash & opt->tt_numentries);
	
	for(i = 0; i < BUCKET_SIZE; i++)
	{	if((h_entry+i)->key == board->hash)
			return (h_entry+i)->move;
	}
	return 0;
}

int tt_retrieve_ponder(move_t *m)
{///retrieving the next move from tt
	move_make(si->rootmove);
	m->p = tt_get_hashmove();
	if(!m->p)
	{	move_undo();
		return false;
	}
	move_undo();
	return true;
}

