#include <stdio.h>
#include "book.h"
#include "polyglot.h"
#include "piece.h"
#include "square.h"
#include "move.h"

static pgentry_t entry_none={0,0,0,0};


static int int_from_file(FILE *f, int l, uint64_t *r){
    int i,c;
    for(i=0;i<l;i++){
        c=fgetc(f);
        if(c==EOF){
            return false;
        }
        (*r)=((*r)<<8)+c;
    }
    return true;
}

static int entry_from_file(FILE *f, pgentry_t *entry){
    int ret;
    uint64_t r;
    ret=int_from_file(f,8,&r);
    if(!ret) return false;
    entry->key=r;
    ret=int_from_file(f,2,&r);
    if(!ret) return false;
    entry->move=r;
    ret=int_from_file(f,2,&r);
    if(!ret) return false;
    entry->score=r;
    ret=int_from_file(f,4,&r);
    if(!ret) return false;
    entry->learn=r;
    return true;
}

static int find_key(FILE *f, uint64_t key, pgentry_t *entry){
    int first, last, middle;
    pgentry_t first_entry=entry_none, last_entry,middle_entry;
    first=-1;
    if(fseek(f,-16,SEEK_END)){
        *entry=entry_none;
        entry->key=key+1; //hack
        return -1;
    }
    last=ftell(f)/16;
    entry_from_file(f,&last_entry);
    while(1){
        if(last-first==1){
            *entry=last_entry;
            return last;
        }
        middle=(first+last)/2;
        fseek(f,16*middle,SEEK_SET);
        entry_from_file(f,&middle_entry);
        if(key<=middle_entry.key){
            last=middle;
            last_entry=middle_entry;
        }else{
            first=middle;
            first_entry=middle_entry;
        }
    }
}
static uint8_t pgtognuprom[5]={0, /*no promotion */
			       2, /*knight */
			       3, /*bishop*/
			       4, /*rook*/
			       5};/*queen*/


// TODO: Check legality of move so that a defective polyglot book does not
// crash gnuchess!
int gnuchess_move(board_t *board,int move){
    int move_part,prom_part,t,f;
    
    t=TOSQ(move);
    f=FROMSQ(move);
    if(board->cboard[f]==king){
	if(false){
	}else if(move==MOVE(E1,H1) || move==MOVE(E1,G1)){
	    return MOVE(E1,G1)|CASTLING;
	}else if(move==MOVE(E1,A1) || move==MOVE(E1,C1)){
	    return MOVE(E1,C1)|CASTLING;
	}else if(move==MOVE(E8,H8) || move==MOVE(E8,G8)){
	    return MOVE(E8,G8)|CASTLING;
	}else if(move==MOVE(E8,A8) || move==MOVE(E8,C8)){
	    return MOVE(E8,C8)|CASTLING;
	}
    }
    if(board->cboard[f]==pawn && FILE_(f)!=FILE_(t) && board->cboard[t]==empty){
	return move_part | ENPASSANT;
    }

    move_part=move & 0xFFF;
    prom_part=(move & PROMOTION)>>12;
    if(prom_part!=0){
	if(prom_part>=5){
	    Log("Illegal promotion move %x\n");
	    return NOMOVE;
	}
    }
    return (pgtognuprom[prom_part]<<12)+move_part;
}


/*
 * We check that the first 10 keys are in ascending order. This is
 * a reasonable test to check the validity of a polyglot book.
 *
 */
int PolyglotBookValidate(book_t *book){
    pgentry_t entry[1];
    HashType key=ULL(0);
    int i;
    if(!book->pg_file && !(book->pg_file=fopen(book->filename,"r+b"))){
        OutputConsole("Can't open %s\n",book->filename);
        return false;
    }
    for(i=0;i<10;i++){
	if(!entry_from_file(book->pg_file, entry)){
	    break;
	}
	if(entry->key<key){
	    return false;
	}
	key=entry->key;
    }

    return true;
}

/*
 * When we get here we assume that we have a valid polyglot book
 *
 */

void PolyglotBookQuery(book_t *book, board_t *board, movelist_t *movelist){
    pgentry_t entry;
    int count,offset;

    if(!book->pg_file && !(book->pg_file=fopen(book->filename,"r+b"))){
        OutputConsole("Can't open %s\n",book->filename);
	book->type=BOOK_INVALID;
        return;
    }
    
    offset=find_key(book->pg_file,board->HashKey,&entry);

    if(entry.key!=board->HashKey){
	movelist->gencnt=0;
	return;
    }
    movelist->moves[0].move=gnuchess_move(board,entry.move);
    movelist->moves[0].score=entry.score;
    count=1;
    fseek(book->pg_file,16*(offset+1),SEEK_SET);
    while(true){
        if(!entry_from_file(book->pg_file,&entry)){
            break;
        }
        if(entry.key!=board->HashKey){
            break;
        }
        if(count==MAXMOVES){
	    break;
        }
	movelist->moves[count].move=gnuchess_move(board,entry.move);
	movelist->moves[count].score=entry.score;
	count++;
    }
    movelist->gencnt=count;
    return;
}
