/* Pepito v1.59  -- file: pgn.c --
   Copyright (c) 2000-01 Carlos del Cacho

   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 2, 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; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Contact the author: guggenjaimito@yahoo.com
*/

#ifndef UNIQUE_SOURCE_FILE
#  include "datos.h"
#endif

#include <time.h>
#include "command.h"
#include "xboard.h"
#include "btree.h"
#include "book.h"
#include "stack.h"

extern int      MAX_BOOK_DEPTH;

#define IsDigit(a)   ((a) >= '0' && (a) <= '9')

#define NORMAL_MOVE     0
#define GOOD_MOVE       1
#define BAD_MOVE        2
#define NAG             3
#define DONT_UPDATE     4
#define LEARNT_AS_BAD   8
#define ABSOLUTE_SCORE 16

#define DRAW_SCORE    0
#define WHITE_WIN     1
#define BLACK_WIN     2
#define UNKNOWN       3

typedef struct {

    unsigned short white_wins;
    unsigned short black_wins;
    unsigned short draws;
    signed   short ce;
    unsigned char  bm;
    unsigned char  depth;
    unsigned char  special;

} book_entry;

db_handle *dbh;
int wpct;

#define BK_CMND_CNT 14

static const command_t bk_table[] = {

        { "build",    BK_Build    },
        { "cap",      BK_Cap      },
        { "clear",    BK_Clear    },
        { "del",      BK_Del      },
        { "edit",     BK_Edit     },
        { "help",     BK_Help     },
        { "load",     BK_Load     },
        { "import",   BK_Import   },
        { "probe",    BK_Probe    },
        { "quit",     BK_Quit     },
        { "setboard", WB_SetBoard },
        { "stats",    BK_Stats    },
        { "tab",      Def_Tab     },
        { "undo",     WB_Undo     }
};


command_group_t bk_commands = {

    BK_CMND_CNT,
    bk_table

};

static char *nag_string[] = { "NORMAL MOVE", "GOOD MOVE", "AVOID MOVE", "AMBIGUOUS" };

 
//--------------------------------------------------------------------------------
char *ExtraeCadena(char **pcadena, char delim_ini, char delim_fin, char *arg)
{
    char *pstr = *pcadena, *parg = arg;

    while (*pstr && delim_ini && delim_ini != *pstr)
        pstr++;

    if (*pstr == '\0')
    {
        *arg = 0;
        return arg;
    }

    if (delim_ini)
      pstr++;

    while (*pstr && delim_fin != *pstr)
       *parg++ = *pstr++;

    if (*pstr)
      pstr++;

    *parg = '\0';
    *pcadena = pstr;

    return arg;
}
//--------------------------------------------------------------------------------
static unsigned char piece_bitmap[] = {
    0x0, 0xe, 0xc, 0xd, 0xa, 0x9, 0x8, 
    0x6, 0x4, 0x5, 0x2, 0x1, 0x0
};
//--------------------------------------------------------------------------------
void FillCompressedEPDInfo(epd_t *record, int ce, int acd)
{
    int i, j, piece;
    entero64 mask;

    record->bitmap = mask = Blancas | Negras;

    for(j=0; mask; j++) {

        i = Lsb(mask);
        mask &= mask - 1;

        piece = piece_bitmap[Tablero[i]];

        if (i == A1)
        {
            if (Enroque & E_LARGO_B)
                piece |= 0x1;
        }
        else if (i == H1)
        {
            if (Enroque & E_CORTO_B)
                piece |= 0x1;
        }
        else if (i == A8)
        {
            if (Enroque & E_LARGO_N)
                piece |= 0x1;
        }
        else if (i == H8)
        {
            if (Enroque & E_CORTO_N)
                piece |= 0x1;
        }
        else if (AlPaso)
        {
            if (AlPaso > 32)
            {
                if  (i == AlPaso-8)
                   piece = 0x7;
            }
            else if  (i == AlPaso+8)
                piece = 0x7;
        }

        if (!(j&1))
           record->chessmen[j>>1] = piece;
        else
           record->chessmen[j>>1] |= piece << 4;
    }

    record->ce = ce;
    record->acd = acd;
}

//--------------------------------------------------------------------------------
int            ParsePGNGame(FILE ** ppfich, int (*pos_callback)(epd_t *pos, int result, int mtype))
{
    char            line[4096], *pline;
    char            arg[4096];
    int             game_over = 0;
    int             move_type;
    int             open = 0;
    entero32        jug;
    int             result = UNKNOWN;
    int             ret_code = 0;

    pgn_game_tag_t  tags;
    player_t        whitep, blackp;
    epd_t           position;

    PosicionInicial();

    InitPos(0);

    do {

        if (!LeeLinea(ppfich, line, 4096))
            return 0;

        if (*line == '[') {

            pline = line+1;
            ExtraeArg(&pline, arg);

            if (!strcmp("Event",arg))
                strcpy(tags.event, ExtraeCadena(&pline,'\"','\"',arg));
            else if (!strcmp("Site",arg))
                strcpy(tags.site, ExtraeCadena(&pline,'\"','\"',arg));
            else if (!strcmp("Round",arg))
                tags.round = atoi(ExtraeCadena(&pline,'\"','\"',arg));
            else if (!strcmp("White",arg))
                strcpy(whitep.name, ExtraeCadena(&pline,'\"','\"',arg));
            else if (!strcmp("Black",arg))
                strcpy(blackp.name, ExtraeCadena(&pline,'\"','\"',arg));
            else if (!strcmp("FEN",arg))
                LeeEPD(ExtraeCadena(&pline,'\"','\"',arg), NULL);
            else if (!strcmp("Result",arg))
            {
                strcpy(tags.result, ExtraeCadena(&pline,'\"','\"',arg));
                if (*arg == '1')
                {
                    if (arg[1] == '-')
                        result = WHITE_WIN;
                    else
                        result = DRAW_SCORE;
                }
                else if (*arg == '0')
                    result = BLACK_WIN;
            }

        }
    } while(!strcmp(line,"-") || *line == '\0' || *line == ';' || *line == '[');

    /* Ahora toca leer jugadas hasta que se acabe la partida... */

    game_over = 0;

    do {

        pline = line;

        while (ExtraeArg(&pline, arg)) {

            if (!strcmp(arg, "1/2-1/2") || !strcmp(arg, "1-0") || !strcmp(arg, "0-1") ||
                !strcmp(arg, "*"))
                game_over = 1;
            else if (open || *arg == '(' || *arg == '{') {
                char           *aux = arg;
                while (*aux != '\0') {
                    if (*aux == '(' || *aux == '{')
                        open++;
                    else if (*aux == ')' || *aux == '}')
                        open--;
                    aux++;
                }
            }
            else {
                if (IsDigit(*arg)) {
                    char           *pp;
                    int             ii;

                    for (pp = arg; *pp != '.' && *pp; pp++);
                    if (*pp) {
                        for (ii = 0, pp++; *pp; ii++, pp++)
                            arg[ii] = *pp;
                        arg[ii] = 0;
                    }
                }
                if (*arg && *arg != '$') {
                    switch (arg[strlen(arg) - 1]) {
                    case '?':
                        move_type = BAD_MOVE;
                        arg[strlen(arg) - 1] = 0;
                        break;
                    case '!':
                        move_type = GOOD_MOVE;
                        arg[strlen(arg) - 1] = 0;
                        break;
                    default:
                        move_type = NORMAL_MOVE;
                    }
                    if (LeeJugada(arg, &jug)) {

                        Mueve(jug);
                        CambiaTurno();

                        if ((NumJug < MAX_BOOK_DEPTH))
                        {
                            FillCompressedEPDInfo(&position, 0, 0);
                            pos_callback(&position, result, move_type);
                        }
                    } else {

                        // panic mode: report only the first error in
                        // each game

                        if (!ret_code)
                        {
                           Log("Error parsing move: '%s' at line %s\n", arg, line);
                           ret_code = 1;
                        }
                    }
                }
            }
        }
    } while (!game_over && LeeLinea(ppfich, line, 4096) && *line != '\0' && strcmp(line,"-"));

    return ret_code;
}
//--------------------------------------------------
void OverwriteCallback(void *data, int data_size, void *data_read, int datar_size)
{
    memcpy(data_read, data, data_size);
}
//--------------------------------------------------
void UpdateCallback(void *data, int data_size, void *data_read, int datar_size)
{
   ((book_entry *)data_read)->white_wins += ((book_entry *)data)->white_wins;
   ((book_entry *)data_read)->black_wins += ((book_entry *)data)->black_wins;
   ((book_entry *)data_read)->draws += ((book_entry *)data)->draws;
   ((book_entry *)data_read)->special |= ((book_entry *)data)->special;

   if (!(((book_entry *)data_read)->special & DONT_UPDATE) &&
      ((book_entry *)data_read)->depth <= ((book_entry *)data)->depth)
   {
      ((book_entry *)data_read)->depth = ((book_entry *)data)->depth;
      ((book_entry *)data_read)->ce = ((book_entry *)data)->ce;
      ((book_entry *)data_read)->bm = ((book_entry *)data)->bm;
   }
}
//--------------------------------------------------
int ConfirmCallback(void *data, int data_size)
{
   if (((book_entry *)data)->special & 0x80) // white to move
   {
       return  (((book_entry *)data)->black_wins * 100 / wpct) >=
           ((book_entry *)data)->white_wins ? 1 : 0;
   }

   // black to move...

   return  (((book_entry *)data)->white_wins * 100 / wpct) >=
           ((book_entry *)data)->black_wins ? 1 : 0;
}
//--------------------------------------------------
void BookUpdateMove()
{
    char str[256], key[10];
    book_entry data;

    memset(&data, 0, sizeof(book_entry));
    memcpy(key,&Historial[NumJug-1].hash,2);
    memcpy(key+2,&HashGlob, 8);
    SeekEntry(dbh, key, &data);

    Print("white wins (%d) ? ", data.white_wins);
    ReadString(str);
    if (*str)
      data.white_wins = atoi(str);

    Print("black wins (%d) ? ", data.black_wins);
    ReadString(str);
    if (*str)
      data.black_wins = atoi(str);

    Print("draws (%d) ? ", data.draws);
    ReadString(str);
    if (*str)
      data.draws = atoi(str);

    Print("ce (%d) ? ", data.ce);
    ReadString(str);
    if (*str)
      data.ce = atoi(str);

    Print("acd (%d) ? ", data.depth);
    ReadString(str);
    if (*str)
      data.depth = atoi(str);

    Print("nag (%s) ? ", nag_string[data.special & NAG]);
    ReadString(str);

    switch(*str)
    {
       case '?': data.special = BAD_MOVE;  break;
       case '!': data.special = GOOD_MOVE; break;
       case '\0': break;
       default: data.special = NORMAL_MOVE;
       
    }

    data.special |= (unsigned char)(Turno << 7);
    InsertEntry(dbh, key, &data, sizeof(book_entry), OverwriteCallback);
    DataBufferFlush(dbh);
}
//--------------------------------------------------
int BookDirectInsert(epd_t *pos, int result, int mtype)
{
    book_entry data;
    char key[10];

    memcpy(key,&Historial[NumJug-1].hash,2);
    memcpy(key+2,&HashGlob, 8);

    data.white_wins = 0;
    data.black_wins = 0;
    data.draws = 0;
    data.ce = 0;
    data.bm = 0;
    data.depth = 0;
    data.special = (unsigned char)((Turno << 7) | mtype);

    switch(result)
    {
       case WHITE_WIN:  data.white_wins++; break;
       case BLACK_WIN:  data.black_wins++; break;
       case DRAW_SCORE: data.draws++; break;
    }

    return InsertEntry(dbh, key, &data, sizeof(book_entry), UpdateCallback);
}
//--------------------------------------------------------
int LearnFromLoss()
{
    book_entry data;
    char key[10];

    while (NumJug > 0 && !(Historial[NumJug-1].status & SEVERAL_BOOK_MOVES))
    {
        CambiaTurno();
        DesMueve();  
    }

    data.white_wins = 0;
    data.black_wins = 0;
    data.draws = 0;
    data.ce = 0;
    data.bm = 0;
    data.depth = 0;

    if (NumJug)
    {
       memcpy(key,&Historial[NumJug-1].hash,2);
       memcpy(key+2,&HashGlob, 8);

       data.special = (unsigned char)((Turno << 7) | LEARNT_AS_BAD);

       InsertEntry(dbh, key, &data, sizeof(book_entry), UpdateCallback);
    }

    if (xb_info.my_color)
        data.black_wins++;
    else
        data.white_wins++;

    while (NumJug <= tab.opp_bk_limit)
    {
        Mueve(Historial[NumJug].jug);
        CambiaTurno();

        if (Turno == xb_info.my_color)
        {
            data.special = (unsigned char)(Turno << 7);
            memcpy(key,&Historial[NumJug-1].hash,2);
            memcpy(key+2,&HashGlob, 8);
            InsertEntry(dbh, key, &data, sizeof(book_entry), UpdateCallback);
        }

    }

    return 0;
}
//--------------------------------------------------
char* DateString()
{
    static char str[256];
    time_t secs;
    struct tm *ltime;
    
    time(&secs);
    ltime = localtime(&secs);
    strftime(str, 256, "%Y.%m.%d", ltime);
    return str;
}
//--------------------------------------------------
char* TimeString()
{
    static char str[256];
    time_t secs;
    struct tm *ltime;
    
    time(&secs);
    ltime = localtime(&secs);
    strftime(str, 256, "%H:%M:%S", ltime);
    return str;
}
//--------------------------------------------------
char *TimeControlString()
{
    static char str[256];
    if (xb_info.mv_ctrl)
        sprintf(str,"%d/%d", xb_info.mv_ctrl, xb_info.base/100);
    else
    {
        sprintf(str,"%d", xb_info.base/100);
        if (xb_info.inc)
          sprintf(str+strlen(str),"+%d", xb_info.inc/100);
    }
    return str;
}
//--------------------------------------------------
#define MAX_LINE 80

void EscribePGN(char *result)
{
    char jug_str[256], score[256];
    char *wp = xb_info.my_color ? my_name  : opp_name;
    char *bp = xb_info.my_color ? opp_name : my_name;
    char *piece_bkp = piece_rep;
    int wlbm =  xb_info.my_color ? tab.my_bk_limit : tab.opp_bk_limit;
    int blbm = !xb_info.my_color ? tab.my_bk_limit : tab.opp_bk_limit;
    int linepos = 0;

    piece_rep = lang_piece_str[3].pieces;

    GameLog("[Event \"?\"]\n"
            "[Site \"%s\"]\n"
            "[Date \"%s\"]\n"
            "[Round \"?\"]\n"
            "[White \"%s\"]\n"
            "[Black \"%s\"]\n"
            "[Result \"%s\"]\n"
            "[PlyCount \"%d\"]\n"
            "[TimeControl \"%s\"]\n"
            "[WhiteLastBkMove \"%d\"]\n"
            "[BlackLastBkMove \"%d\"]\n"
            "[Time \"%s\"]\n"
            "\n",
            site_name, DateString(), wp, bp, result, tab.jug_limit,
            TimeControlString(), 1+wlbm/2, 1+blbm/2, TimeString());

    while(NumJug > 0)
    {
       CambiaTurno();
       DesMueve();
    }

    while(NumJug < tab.jug_limit)
    {
        ImprimeJugada(Historial[NumJug].jug, jug_str);
        if (!(NumJug & 1))
            GameLog("%d. ", (NumJug/2)+1);


        Mueve(Historial[NumJug].jug);
        CambiaTurno();
        if (Jaque(Turno))
        {
            entero32 bkp = Historial[NumJug].jug;
            GeneraValidas(0);
            GameLog("%s%c", jug_str, tab.max_stack[0] == JugStack ? '#' : '+');
            Historial[NumJug].jug = bkp;
        }
        else
          GameLog("%s", jug_str);

        if (Turno != xb_info.my_color && Historial[NumJug-1].depth)
        {
            time_t secs = Historial[NumJug-1].time;

            if (Historial[NumJug-1].score > JAQUE_MATE-1000)
                sprintf(score,"+M%d",(JAQUE_MATE-Historial[NumJug-1].score+1)/2);
            else if (Historial[NumJug-1].score < -JAQUE_MATE+1000)
                sprintf(score, "-M%d",(JAQUE_MATE+Historial[NumJug-1].score)/2);
            else
                sprintf(score, "%s%.2f", Historial[NumJug-1].score > 0 ? "+" : "",                   
                        (float)Historial[NumJug-1].score/100);

            sprintf(jug_str,"%d:%s%d", secs/60, (secs%60) > 9 ? "" : "0",
                    secs%60);

            GameLog(" {%d %s %s}", Historial[NumJug-1].depth, score, jug_str);
        }

        GameLog(" ");
    }

    GameLog("%s\n\n", result);
    piece_rep = piece_bkp;
}

//--------------------------------------------------
int BookBulkLoadInsert(epd_t *pos, int result, int mtype)
{
    book_entry data;
    char key[10];

    memcpy(key,&Historial[NumJug-1].hash,2);
    memcpy(key+2,&HashGlob, 8);

    data.white_wins = 0;
    data.black_wins = 0;
    data.draws = 0;
    data.ce = 0;
    data.bm = 0;
    data.depth = 0;
    data.special = (unsigned char)((Turno << 7) | mtype);

    switch(result)
    {
       case WHITE_WIN:  data.white_wins++; break;
       case BLACK_WIN:  data.black_wins++; break;
       case DRAW_SCORE: data.draws++; break;
    }

    return GenerateRun(&dbh->bload, key, &data, sizeof(book_entry), UpdateCallback);
}
//--------------------------------------------------------
int BookProbe(int echo)
{
    book_entry data;
    char key[10];
    int score = 0;

    memcpy(key,&Historial[NumJug-1].hash,2);
    memcpy(key+2,&HashGlob, 8);

    if (SeekEntry(dbh, key, &data))
    {
        uint32 total;

        total = data.black_wins + data.draws + data.white_wins;

        if (echo)
        {
           Print("\n\tSearch data\t\tStatistics\n");
           Print("\t-----------\t\t----------\n");
           Print("\n\tce:    %5d\t\twhite wins: %5d (%.2f%%)\n",
                 (int)data.ce, data.white_wins,
                 data.white_wins*100.0/total);

           Print("\tbm:    %5d\t\tblack wins: %5d (%.2f%%)\n",
                  (int)data.bm, data.black_wins,
                  data.black_wins*100.0/total);

           Print("\tdepth: %5d\t\tdraws:      %5d (%.2f%%)\n",
                 (int)data.depth, data.draws,
                  data.draws*100.0/total);

           Print("\t           \t\ttotal:      %5d\n",total);
           Print("\n\tNag status\t\tLearn info\n");
           Print("\t----------\t\t-----------\n");
           Print("\n\t%s\t\t%s\n\n", nag_string[data.special & NAG],
                  data.special & LEARNT_AS_BAD ? "AVOID MOVE": "NONE");
        }

        if (!(data.special & BAD_MOVE) && !(data.special & LEARNT_AS_BAD))
        {
           if (Turno)
               score = data.black_wins + ((data.special & ABSOLUTE_SCORE) ? 0 :
                        (int)((data.black_wins + data.draws)*20.0/total));
           else
               score = data.white_wins + ((data.special & ABSOLUTE_SCORE) ? 0 :
                        (int)((data.white_wins + data.draws)*20.0/total));

           if (data.special & GOOD_MOVE)
               score += 5000000;

           score += data.depth * 500;

           if (score <= 0 && !(data.special & ABSOLUTE_SCORE))
             score = 1;  // minimal chance if not AVOID MOVE and score = zero
        }
    }
    else if (echo)
        Print("Position not in book.\n");

    return score;
}
//--------------------------------------------------------
void BuildBook(char *PGNFile, int maxply)
{
    FILE           *PGNfich;
    int             num_gam = 0;
    int             errors = 0;

    if (!(PGNfich = fopen(PGNFile, "rt"))) {
        Print("Error opening file\n");
        return;
    }

    MAX_BOOK_DEPTH = maxply;
    InitBulkLoad(&dbh->bload, 10);
    dbh->bload.data_size = sizeof(book_entry);


    while (!feof(PGNfich)) {
        num_gam++;
        errors += ParsePGNGame(&PGNfich, BookBulkLoadInsert);
        if (!(num_gam % 100))
            Print("\rFinished parsing GAME %d", num_gam);
    }

    Print("\rFinished parsing GAME %d\n", num_gam);

    MergeTempFiles(&dbh->bload, UpdateCallback, ConfirmCallback);
    BulkLoadTree(dbh, dbh->bload.current_run->fname);
    FreeRun(dbh->bload.current_run);
    fclose(PGNfich);
    Print("Book creation finished.\n");
    if (errors)
        Print("WARNING! PGN File contained %d error(s). See pepito.log for details\n", errors);
}
//-----------------------------------------------------------
void UnNagBook()
{
    offset_t    num;
    book_entry  entry;

    DataBufferFlush(dbh);

    fseek(dbh->fdata, 4, SEEK_SET);

    for (num = 4; num < dbh->data_off; num += sizeof(book_entry))
    {
        fread(&entry, sizeof(book_entry), 1, dbh->fdata);
        if (entry.special & NAG)
        {
            entry.special &= ~NAG;
            fseek(dbh->fdata, num, SEEK_SET);
            fwrite(&entry, sizeof(book_entry), 1, dbh->fdata);
            fflush(dbh->fdata);
        }
    }
}
//-----------------------------------------------------------
void UnLearnBook()
{
    offset_t    num;
    book_entry  entry;

    DataBufferFlush(dbh);

    fseek(dbh->fdata, 4, SEEK_SET);

    for (num = 4; num < dbh->data_off; num += sizeof(book_entry))
    {
        fread(&entry, sizeof(book_entry), 1, dbh->fdata);
        if (entry.special & LEARNT_AS_BAD)
        {
            entry.special &= ~LEARNT_AS_BAD;
            fseek(dbh->fdata, num, SEEK_SET);
            fwrite(&entry, sizeof(book_entry), 1, dbh->fdata);
            fflush(dbh->fdata);
        }
    }
}
//-----------------------------------------------------------
void UnCAPBook()
{
    offset_t    num;
    book_entry  entry;

    DataBufferFlush(dbh);

    fseek(dbh->fdata, 4, SEEK_SET);

    for (num = 4; num < dbh->data_off; num += sizeof(book_entry))
    {
        fread(&entry, sizeof(book_entry), 1, dbh->fdata);
        if (entry.depth)
        {
            entry.depth = 0;
            entry.ce = 0;
            fseek(dbh->fdata, num, SEEK_SET);
            fwrite(&entry, sizeof(book_entry), 1, dbh->fdata);
            fflush(dbh->fdata);
        }
    }
}
//-----------------------------------------------------------
void ImportEPDToBook(char *EPDFile)
{
    book_entry      data;
    FILE           *EPDfich;
    int             num_gam = 0;
    epd_info        pos_info;
    char            line[4096];
    uint8           key[10];
    int             cnt = 0;

    if (!(EPDfich = fopen(EPDFile, "rt"))) {
        Print("Error opening file\n");
        return;
    }

    while (LeeLinea(&EPDfich, line, 4096)) {

        if (LeeEPD(line, &pos_info))
        {
           if (pos_info.pv)
           {
               Mueve(pos_info.pv->jug);
               CambiaTurno();

               memcpy(key,&Historial[NumJug-1].hash,2);
               memcpy(key+2,&HashGlob, 8);

               data.white_wins = 0;
               data.black_wins = 0;
               data.draws = 0;

               data.ce = pos_info.ce;
               data.bm = 0;

               data.depth = pos_info.acd;
               data.special = (unsigned char)(Turno << 7);

               InsertEntry(dbh, key, &data, sizeof(book_entry),
                           UpdateCallback);

               CambiaTurno();
               DesMueve();
           }

           FreeMoveList(pos_info.pv);
           FreeMoveList(pos_info.bm);

           cnt++;

           if (!(cnt % 100))
              Print("\rFinished parsing EPD record %d", cnt);
        }
    }

    Print("\rFinished parsing EPD record %d", cnt);
    fclose(EPDfich);
    DataBufferFlush(dbh);
    Print("\nDone.\n");
}
//-----------------------------------------------------------
void ImportBeoFormatToBook(char *filen)
{
    book_entry      data;
    FILE           *bkfich;
    int             num_gam = 0;
    char            line[4096], *pline, arg[256];
    uint8           key[10];
    int             cnt = 0;

    if (!(bkfich = fopen(filen, "rt"))) {
        Print("Error opening file '%s'\n",filen);
        return;
    }

    while (LeeLinea(&bkfich, line, 4096)) {

        if (LeeEPD(line, NULL) &&
            LeeLinea(&bkfich, line, 4096))
        {
            pline = line;

            while(ExtraeArg(&pline, arg))
            {
                char mv[20], score[20];
                entero32 jug;
                char *parg = arg;

                ExtraeCadena(&parg, 0, '{', mv);
                ExtraeCadena(&parg, 0, '}', score);

                if (LeeJugada(mv, &jug))
                {
                    Mueve(jug);
                    CambiaTurno();

                    memcpy(key,&Historial[NumJug-1].hash,2);
                    memcpy(key+2,&HashGlob, 8);

                    data.draws = 0;
                    data.white_wins = 0;
                    data.black_wins = 0;

                    if (Turno)
                      data.black_wins = atoi(score);
                    else
                      data.white_wins = atoi(score);

                    data.ce = 0;
                    data.bm = 0;

                    data.depth = 0;
                    data.special = ((unsigned char)(Turno << 7)) | ABSOLUTE_SCORE;

                    InsertEntry(dbh, key, &data, sizeof(book_entry),
                                OverwriteCallback);

                    CambiaTurno();
                    DesMueve();
                }
            }

            cnt++;
  
            if (!(cnt % 100))
               Print("\rFinished parsing EPD record %d", cnt);
        }
    }


    Print("\rFinished parsing EPD record %d", cnt);
    fclose(bkfich);
    DataBufferFlush(dbh);
    Print("\nDone.\n");
}
//-----------------------------------------------------------
#define FBK_OPEN_LINE       0x40
#define FBK_CLOSE_LINE      0x80

void ImportFbkFormatToBook(char *filen) {

    int longitud;
    FILE *fp;
    unsigned char movedata[2];
    char mvstr[5];
    entero32 jug;
    book_entry      data;
    uint8           key[10];
    stack_t         stack;
    unsigned char   nag_conversion[4] = { NORMAL_MOVE, GOOD_MOVE, BAD_MOVE, BAD_MOVE };

    fp = fopen(filen,"rb");

    if (!fp)
    {
      Print("Non existent .fbk file\n");
      return;
    }

    PosicionInicial();
    InitPos(0);
    InitStack(&stack,sizeof(int));

    while (fread((char *)movedata, 1, 2, fp) == 2) {

       sprintf(mvstr, "%c%c%c%c",
               (movedata[0] & 7) + 'a',
               ((movedata[0] >> 3) & 7) + '1',
               (movedata[1] & 7) + 'a',
               ((movedata[1] >> 3) & 7) + '1'
              );

       if (!(movedata[0] & FBK_OPEN_LINE))
       {
          longitud = 1;
          InsertStack(&stack,&longitud);
       }
       else {
          int *pi = (int*) PeekStack(&stack,0);

          if (pi)
            *pi += 1;
       }

       if (LeeJugada(mvstr, &jug))
       {
           Mueve(jug);
           CambiaTurno();

           memcpy(key,&Historial[NumJug-1].hash,2);
           memcpy(key+2,&HashGlob, 8);
           data.draws = 0;
           data.white_wins = 0;
           data.black_wins = 0;
           data.ce = 0;
           data.bm = 0;
           data.depth = 0;
           data.special = ((unsigned char)(Turno << 7)) |
                          nag_conversion[(movedata[1] >> 6)&3];

           InsertEntry(dbh, key, &data, sizeof(book_entry), UpdateCallback);

           if (movedata[0] & FBK_CLOSE_LINE)
           {
              if (ExtractStack(&stack, &longitud))
                 while (longitud--)
                 {
                    CambiaTurno();
                    DesMueve();
                 }
           }
       }
       else {
          Print("Error reading move: %s\n",mvstr);
          PintaTablero();
          ClearStack(&stack);
          WB_Quit(0);
       }
    }

    ClearStack(&stack);
    PosicionInicial();
    InitPos(0);
    fclose(fp);
}

//-----------------------------------------------------------
void ImportGamesToBook(char *PGNFile, int maxply)
{
    FILE           *PGNfich;
    int             num_gam = 0;
    int             errors = 0;

    if (!(PGNfich = fopen(PGNFile, "rt"))) {
        Print("Error opening file\n");
        return;

    }

    MAX_BOOK_DEPTH = maxply;

    while (!feof(PGNfich)) {
        num_gam++;
        errors += ParsePGNGame(&PGNfich, BookDirectInsert);
        if (!(num_gam % 100))
            Print("\rFinished parsing GAME %d", num_gam);
    }

    DataBufferFlush(dbh);
    fclose(PGNfich);
    Print("\nDone.\n");
    if (errors)
        Print("WARNING! PGN File contained %d error(s). See pepito.log for details\n", errors);
}
//-----------------------------------------------------------
void ImportOpeningLines(char *PGNFile)
{
    FILE           *fich;
    int             num_gam = 0;
    char            line[16384], *pline;
    char            arg[255];
    int             move_type;
    entero32        jug;
    epd_t           position;
    int             errors = 0;

    if (!(fich = fopen(PGNFile, "rt"))) {
        Print("Error opening file\n");
        return;

    }

    while (LeeLinea(&fich, line, 4096)) {

       PosicionInicial();
       InitPos(0);

       pline = line;

       while (ExtraeArg(&pline, arg)) {

            switch (arg[strlen(arg) - 1]) {
               case '?':
                     move_type = BAD_MOVE;
                     arg[strlen(arg) - 1] = 0;
                     break;

               case '!':
                     move_type = GOOD_MOVE;
                     arg[strlen(arg) - 1] = 0;
                     break;

               default:
                     move_type = NORMAL_MOVE;
            }

           if (IsDigit(*arg)) {

               char           *pp;
               int             ii;

               for (pp = arg; *pp != '.' && *pp; pp++);
               if (*pp) {
                   for (ii = 0, pp++; *pp; ii++, pp++)
                       arg[ii] = *pp;
                   arg[ii] = 0;
               }

               if (*arg == 0)
                   continue;
            }


            if (LeeJugada(arg, &jug)) {

                 Mueve(jug);
                 CambiaTurno();
                 FillCompressedEPDInfo(&position, 0, 0);
                 BookDirectInsert(&position, UNKNOWN, move_type);
            }
            else
            {
                 Log("Error parsing move: '%s' at line %s\n", arg, line);
                 errors++;
            }
       }
    }

    if (errors)
        Print("WARNING! File contained %d error(s). See pepito.log for details\n", errors);

    fclose(fich);
}
//-----------------------------------------------------------
void BookClose()
{
    if (dbh)
    {
       CloseDatabase(dbh);
       dbh = NULL;
    }
}
//-----------------------------------------------------------
void BookOpen(char *name)
{
   dbh = OpenDatabase(name, 256);

   if (!dbh)
      tab.book_retry = 0;
   else 
      tab.book_retry = USE_BOOK;
}
//-----------------------------------------------------------
int BookLoaded()
{
    return (int)dbh;
}
//-----------------------------------------------------------
int BK_Badmin(int argc)
{
    char *dbname = *BOOK_FILE ? BOOK_FILE : "libro";

    prompt = "book-admin> ";

    if (BookLoaded())
        BookClose();

    dbh = OpenDatabase(dbname, 1024);

    if (!dbh)
    {
       fprintf(stderr,"Error reading database %s\n",dbname);
       dbh = CreateDatabase(dbname, 10, sizeof(book_entry), 1024);
       if (!dbh)
       {
          fprintf(stderr,"Error creating database %s\n",dbname);
          return 0;

       }
    }

    command_table = &bk_commands;

    if (tab.trans_tab != NULL)
       free(tab.trans_tab);
    if (tab.trans_tab_p != NULL)
       free(tab.trans_tab_p);
    if (tab.eval_cache != NULL)
       free(tab.eval_cache);

    Print("DB opened correctly\n");


    return 1;
}
//--------------------------------------------------------------------------------
int BK_Quit(int argc)
{
    prompt = "Pepito> ";
    command_table = &default_commands;
    CloseDatabase(dbh);
    dbh = NULL;
    Print("DB closed.\n");
    if ((tab.trans_tab = (entrada_hash *) malloc((long) sizeof(entrada_hash) * HASH_SIZE)) == NULL)
        exit(1);
    if ((tab.trans_tab_p = (entrada_hash_p *) malloc((long) sizeof(entrada_hash_p) * PAWN_HASH_SIZE)) == NULL)
        exit(1);
    if ((tab.eval_cache = (ecache *) malloc((long) sizeof(ecache) * CACHE_SIZE)) == NULL)
        exit(1);
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Stats(int argc)
{
    PrintTreeStats(dbh);
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Load(int argc)
{
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    if (ExtraeArg(&pstr, arg))
    {
        CloseDatabase(dbh);
        dbh = OpenDatabase(arg, 1024);
    }
    else
        fprintf(stderr, "Error: book file name required\n");
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Clear(int argc)
{
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    while (ExtraeArg(&pstr, arg))
    {
        if (!strcmp(arg,"nags"))
          UnNagBook();
        else if (!strcmp(arg,"cap"))
          UnCAPBook();
        else if (!strcmp(arg,"learninfo"))
          UnLearnBook();
    }

    return 1;
}
//--------------------------------------------------------------------------------
#define IMPORT_FBK_BOOK          3
#define IMPORT_BEOWULF_BOOK      2
#define IMPORT_OPENING_LINES     1
#define IMPORT_GAMES             0


int BK_Import(int argc)
{
    int maxply = 25, import_mode = IMPORT_GAMES;
    char numply[256];
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    if (ExtraeArg(&pstr, arg))
    {
        while (ExtraeArg(&pstr, numply))
        {
            if (!strcmp(numply,"-1line"))
                import_mode = IMPORT_OPENING_LINES;
            else if (!strcmp(numply,"-beowulf"))
                import_mode = IMPORT_BEOWULF_BOOK;
            else if (!strcmp(numply,"-fbk"))
                import_mode = IMPORT_FBK_BOOK;
            else
            {
               maxply = atoi(numply);
               Print("maxply set to: %d\n",maxply);
            }
        }

        switch(import_mode)
        {
           case IMPORT_GAMES:

             ImportGamesToBook(arg, maxply); break;

           case IMPORT_OPENING_LINES:

             ImportOpeningLines(arg); break;

           case IMPORT_BEOWULF_BOOK:

             ImportBeoFormatToBook(arg); break;

           case IMPORT_FBK_BOOK:

             ImportFbkFormatToBook(arg);
        }

    }
    else
      fprintf(stderr, "Error: PGN file name required\n");
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Cap(int argc)
{
   char line[4096], arg[256], *pstr = line;

   GetRestofInput(line);

   if (ExtraeArg(&pstr, arg))
     ImportEPDToBook(arg);

   return 1;
}
//--------------------------------------------------------------------------------
int BK_Build(int argc)
{
    int maxply = 25;
    char string[256];
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    if (ExtraeArg(&pstr, arg))
    {
       wpct = 20;

       while (ExtraeArg(&pstr, string))
       {
           if (!strcmp(string,"w"))
           {
              if (ExtraeArg(&pstr, string))
                  wpct =  atoi(string);
           }
           else
             maxply = atoi(string);
       }

       Print("maxply: %d, wpct: %d\n",maxply, wpct);
       BuildBook(arg, maxply);
    }
    else
       fprintf(stderr, "Error: PGN file name required\n");
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Probe(int argc)
{
    entero32 jug;
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    if (ExtraeArg(&pstr, arg) && LeeJugada(arg, &jug)) 
    {
       Mueve(jug);
       CambiaTurno();
       BookProbe(1);
       CambiaTurno();
       DesMueve();
    }
    else
       WB_Bk(argc);
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Edit(int argc)
{
    entero32 jug;
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    if (ExtraeArg(&pstr, arg) && LeeJugada(arg, &jug)) 
    {
       Mueve(jug);
       CambiaTurno();
       BookUpdateMove();
       CambiaTurno();
       DesMueve();
       printf("Ok\n");
    }
    else
      Print("This command requires a legal move\n");
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Del(int argc)
{
    entero32 jug;
    char line[4096], arg[256], *pstr = line;

    GetRestofInput(line);

    if (ExtraeArg(&pstr, arg) && LeeJugada(arg, &jug)) 
    {
       unsigned char key[10];
       Mueve(jug);
       CambiaTurno();

       memcpy(key,&Historial[NumJug-1].hash,2);
       memcpy(key+2,&HashGlob, 8);
       if (!DeleteEntry(dbh, key))
           Print("Ok\n");
       else
           Print("Move not in book\n");

       CambiaTurno();
       DesMueve();
    }
    else
      Print("This command requires a legal move\n");
    return 1;
}
//--------------------------------------------------------------------------------
int BK_Help(int argc)
{
    Print(

"\n\nOkay, time for a bit of bad wording from my side. If you have got here I\n"
"assume you either are trying to build/edit/view an opening book or you\n"
"typed a command at random. Available commands are:\n"

"\n\t- build PGN_file [num1]  [w num2] => Builds a book from the specified\n"
"\tsource file importing lines with <= num1 moves (err... half moves). If\n"
"\tno value is given Pepito will use 25 plys for the default. num2 is a\n"
"\tparameter to cull some positions (thus resulting in lower book size) if\n"
"\ttheir number of wins doesn't come over a certain margin: if nwins * 100\n"
"\t/ num2 < nlosses the position will be discarded.\n"

"\n\t- cap EPD_file => Imports data from the Chess Analysis Project (Dann\n"
"\tCorbit) into Pepito book. 'pm' will get a bonus for the book move\n"
"\tselection process as a function of search depth (I have to think of\n"
"\tsomething more clever).\n"

"\n\t- clear => Command to remove special info from book entries: CAP data\n"
"\tor '?' and '!' annotations ('clear cap' for the former, 'clear nags'\n"
"\tfor the latter and 'learninfo' to erase the marks Pepito adds when it\n"
"\tloses a game).\n\n");

    Print("Press <Enter> to continue...");
    fflush(stdout);
    ReadString(entrada);
    ClearIbuffer();

    Print(

"\n\t- del move => Removes a move from the book. File size will not shrink\n"
"\thowever so if you do many delete operations disk space will be wasted\n"
"\tso it's probably better to rebuild the book at that time\n"

"\n\t- edit move => Lets you insert a move manually or alter one stored\n"
"\tin the book. You will be prompted for the value of each field, always\n"
"\treserving the option to leave it as it is by just pressing Enter. When\n"
"\tqueried about the nag status you can either enter '!', '?' or '=' to\n"
"\tassign this move more/less/equal precedence than the rest. Mmmm, if\n"
"\tyou mark it with '?' it won't get played, actually.\n"

"\n\n\t- import PGN_file [num1] => Inserts the positions into the current book\n"
"\tdeleting what it's already there. It is done by doing B-tree inserts,\n"
"\tso the process is much slower than using 'build'. It is meant as a way\n"
"\tto patch an existing opening book. The cute thing about it is that you\n"
"\tcan import a PGN file up to a given maxply, then grab another\n"
"\tcollection of games and import them to a different maxply...\n"

"\n\t- load book_file => Closes current book and opens the one specified\n"
"\tby book_file (creates one if it doesn't exist). Any 'dirty' data being\n"
"\tcached is written back to disk.\n\n");

    Print("Press <Enter> to continue...");
    fflush(stdout);
    ReadString(entrada);
    ClearIbuffer();

    Print(

"\n\t- probe [move] => If given no argument, this command will print all the\n"
"\tavailable moves in the opening book from the current position. To see\n"
"\tdetailed information about a certain move you can type it as an\n"
"\targument.\n"

"\n\t- quit => Closes the opening book, writing to disk any buffered data\n"
"\tand exits book-admin mode.\n"

"\n\t- setboard FEN_string => Loads a position given in Forsyth-Edwards\n"
"\tNotation.\n"

"\n\t- stats => Lists the total number of positions stored in book and\n"
"\tthe ratio of ocupation in index pages. The higher this number, less\n"
"\twasted space in your HD (assumming all this isn't a waste in the first,\n"
"\tplace, of course :-O).\n"

"\n\t- tab => Displays the current board position.\n"

"\n\t- undo => Backs up one half move\n"

"\nFinally, you can introduce moves both in SAN or coordinate notation to move\n"
"around the chess forest. Good luck!\n\n");

    return 1;
}
