/* 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

extern int      MAX_BOOK_DEPTH;

#define tipo_dat        entrada_hash
#define Comp(el1,el2)   ((el1).low > (el2).low)
#define Swapp(a,b)      {tipo_dat t = a; a = b; b = t; }

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

#define GOOD_MOVE   1
#define BAD_MOVE    2

void            WriteBook(char *file, int bsize, int istemp);
void            MergeBook(char *file_dest, char *file_orig1, char *file_orig2, int del_orig);


static int      tmp_files,
                num_heap;

//--------------------------------------------------------------------------------
void            LeePGN(FILE ** ppfich, int normal_pgn)
{
    char            linea[4096],
                   *pos_cad = linea;
    char            arg[255];
    int             game_over = 0;
    int             move_type = 0;
    int             num_lins = 0,
                    open = 0;
    entero32        jug;
    int             result = 3;
    int             bkscore[4] = {0, 2, 1, 1};

    /* lo más rudimentario: ignora toda la cabecera */

    PosicionInicial();

    InitPos(0);

    while (LeeLinea(ppfich, linea, 4096)) {
        if (*linea == '-' || *linea == '\0' || *linea == ';')
            continue;

        if (*linea == '[') {
            if (linea[1] == 'R') {
                ExtraeArg(&pos_cad, arg);
                ExtraeArg(&pos_cad, arg);
                pos_cad = linea;
                switch (arg[1]) {
                case '1':
                    result = 1;
                    if (arg[2] != '-')
                        result = 3;
                    break;

                case '0':
                    result = 0;
                    break;
                }
            }
            continue;
        }
        break;
    }
    ;

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

    game_over = 0;

    do {
        while (ExtraeArg(&pos_cad, arg)) {
            if (!strcmp(arg, "1/2-1/2") || !strcmp(arg, "1-0") || !strcmp(arg, "0-1") ||
                !strcmp(arg, "*"))
                game_over = 1;
            else if (open) {
                char           *aux = arg;
                while (*aux != '\0') {
                    if (*aux == '(' || *aux == '{')
                        open++;
                    else if (*aux == ')' || *aux == '}')
                        open--;
                    aux++;
                }
            } else if (*arg == '(' || *arg == '{')
                open++;
            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 = 0;
                    }
                    if (LeeJugada(arg, &jug)) {
                        Mueve(jug);
                        CambiaTurno();
                        if ((NumJug < MAX_BOOK_DEPTH && result) || move_type)
                            BookInsert(move_type, bkscore[result]);
                        result ^= 1;
                    } else {
                        Print("Error parsing move: '%s' at line %s\n", arg, linea);
                        PintaTablero();
                        game_over = 1;
                        exit(1);
                    }
                }
            }
        }
        pos_cad = linea;
        num_lins++;
    } while (!game_over && normal_pgn && LeeLinea(ppfich, linea, 4096) && *linea != '\0' && *linea != '-');
}
//------------------------------------------------------------------
void            Heap_Insert(tipo_dat heap[], int num_ele, tipo_dat * heap_ele)
{
    heap[num_ele] = *heap_ele;
    Pull_Up(heap, num_ele);
}
//------------------------------------------------------------------
void            Order_Heap(tipo_dat heap[], int num_ele)
{
    int             i;

    for (i = num_ele - 1; i; i--) {
        Swapp(heap[0], heap[i]);
        Push_Down(heap, i);
    }
}
//---------------------------------------------------
void            Pull_Up(tipo_dat heap[], int num_ele)
{
    while (num_ele && Comp(heap[num_ele], heap[(num_ele - 1) >> 1])) {
        Swapp(heap[num_ele], heap[(num_ele - 1) >> 1]);
        num_ele = (num_ele - 1) >> 1;
    }
}
//---------------------------------------------------
void            Push_Down(tipo_dat heap[], int num_ele)
{
    int             ipushed = 0;
    int             right,
                    left,
                    next;
    int             seguimos = 1;

    while (seguimos && ipushed < num_ele) {
        if ((next = left = (ipushed << 1) + 1) >= num_ele)
            break;

        right = left + 1;

        if (right < num_ele && Comp(heap[right], heap[left]))
            next = right;

        if (Comp(heap[ipushed], heap[next]))
            seguimos = 0;
        else {
            Swapp(heap[ipushed], heap[next]);
            ipushed = next;
        }
    }
}
//--------------------------------------------------
void            BookInsert(int mv_type, int score)
{
    entrada_hash    temp;

    temp.low = HashGlob;
    temp.high = (entero64) score | ((entero64) ((entero32)HashPeones) << 32);

    if (num_heap == HASH_SIZE) {
        char            fname[20];

        sprintf(fname, "file%d.tmp", ++tmp_files);
        WriteBook(fname, num_heap, 1);
        num_heap = 0;
    }
    Heap_Insert(tab.trans_tab, num_heap, &temp);
    num_heap++;
}
//--------------------------------------------------------
void            BuildBook(char *bookname, char *PGNFile, int maxply, int pgn_mode)
{
    FILE           *PGNfich;
    int             num_gam = 0;
    long            i;

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

    }
    tmp_files = num_heap = 0;

    while (!feof(PGNfich)) {
        num_gam++;
        LeePGN(&PGNfich, pgn_mode);
        if (!(num_gam % 100))
            Print("\rFinished GAME %d", num_gam);
    }

    xb_info.force = 1;

    fclose(PGNfich);

    Print("\rFinished parsing file. Total games: %d\n", num_gam);
    if (!tmp_files)
        WriteBook(bookname, num_heap, 0);
    else {
        char            fname[20],
                        fname2[20];
        int             inc = 1;

        sprintf(fname, "file%d.tmp", ++tmp_files);
        WriteBook(fname, num_heap, 0);
        while (inc < tmp_files) {
            for (i = 1; (i + inc) <= tmp_files; i += (inc << 1)) {
                sprintf(fname, "file%ld.tmp", i);
                sprintf(fname2, "file%d.tmp", i + inc);
                Print("Merging %s with %s...\n", fname, fname2);
                MergeBook(fname, fname, fname2, 1);
            }
            inc <<= 1;
        }
        rename("file1.tmp", bookname);
    }

}


//-----------------------------------------------------------
void            WriteBook(char *file, int bsize, int istemp)
{
    FILE           *book;
    unsigned long   i,
                    j,
                    rep;
    entrada_hash    htemp;

    if (istemp) {
        Print("Hash table full! Creating temporary file\n");
    }
    Print("Total positions: %ld\n", bsize);
    Print("Sorting book... ");
    Order_Heap(tab.trans_tab, bsize);
    Print("OK\n");
    Print("Saving book to disk & eliminating repeated positions... ");

    htemp = tab.trans_tab[0];
    rep = 0;

    for (i = 1, j = 0; i < bsize; i++) {
        if (htemp.low == tab.trans_tab[i].low &&
            (htemp.high & (entero64) ~ 0xffffffff) ==
            (tab.trans_tab[i].high & (entero64) ~ 0xffffffff)) {
            rep++;
            htemp.high += (tab.trans_tab[i].high & (entero64) ~ (BUENA_LIBRO | MALA_LIBRO | ~0xffffffff));
            htemp.high |= (tab.trans_tab[i].high & (entero64) (BUENA_LIBRO | MALA_LIBRO));
        } else {
            tab.trans_tab[j++] = htemp;
            htemp = tab.trans_tab[i];
        }
    }

    tab.trans_tab[j++] = htemp;
    book = fopen(file, "wb");
    if (!book) {
        Print("Error attempting to open file %s for writing", file);
        return;
    }
    fwrite(tab.trans_tab, sizeof(entrada_hash), j, book);
    fclose(book);

    Print("OK\n");
    Print("Total positions: %ld (%ld repeated)\n", bsize - rep, rep);
}

//------------------------------------------------------------
void            MergeBook(char *file_dest, char *file_orig1, char *file_orig2, int del_orig)
{
    long            size1,
                    size2;
    entrada_hash   *p1 = 0,
                   *p2 = 0,
                   *pd;
    FILE           *fich1,
                   *fich2,
                   *fdest;
    unsigned long   bufsize = HASH_SIZE / 3;
    int             vuelca;

    fich1 = fopen(file_orig1, "rb");
    fich2 = fopen(file_orig2, "rb");
    fdest = fopen("mergetmp", "wb");

    if (!fich1 || !fich2 || !fdest) {
        Print("Error al abrir fichero\n");
        return;
    }
    pd = tab.trans_tab + 2 * bufsize;

    size1 = size2 = 0;

    do {
        if (!size1) {
            Print("Reading from 1...\n");
            p1 = tab.trans_tab;
            if (!(size1 = fread((void *) p1, (size_t) sizeof(entrada_hash), bufsize, fich1))) {
                vuelca = 0;
                break;
            }
        }
        if (!size2) {
            Print("Reading from 2...\n");
            p2 = tab.trans_tab + bufsize;
            if (!(size2 = fread((void *) p2, (size_t) sizeof(entrada_hash), bufsize, fich2))) {
                vuelca = 1;
                break;
            }
        }
        while (size1 && size2) {
            if (p1->low == p2->low && (p1->high & (entero64) ~ 0xffffffff) ==
                (p2->high & (entero64) ~ 0xffffffff)) {
                p1->high += p2->high & (entero64) ~ (BUENA_LIBRO | MALA_LIBRO | ~0xffffffff);
                p1->high |= p2->high & (entero64) (BUENA_LIBRO | MALA_LIBRO);
                *pd = *p1;
                p1++;
                p2++;
                size1--;
                size2--;
            } else if (p1->low <= p2->low) {
                *pd = *p1;
                p1++;
                size1--;
            } else {
                *pd = *p2;
                p2++;
                size2--;
            }

            pd++;

            if (pd == (tab.trans_tab + HASH_SIZE)) {
                Print("Flushing buffer...\n");
                fwrite(tab.trans_tab + 2 * bufsize, sizeof(entrada_hash),
                       pd - (tab.trans_tab + 2 * bufsize), fdest);
                pd = tab.trans_tab + 2 * bufsize;
            }
        }
    }
    while (1);


    fwrite(tab.trans_tab + 2 * bufsize, sizeof(entrada_hash),
           pd - (tab.trans_tab + 2 * bufsize), fdest);


    if (vuelca == 1) {
        fwrite(tab.trans_tab, sizeof(entrada_hash), size1, fdest);

        while ((size1 = fread(tab.trans_tab, sizeof(entrada_hash), HASH_SIZE, fich1)))
            fwrite(tab.trans_tab, sizeof(entrada_hash), size1, fdest);
    } else {
        fwrite(tab.trans_tab + bufsize, sizeof(entrada_hash), size2, fdest);

        while ((size2 = fread(tab.trans_tab, sizeof(entrada_hash), HASH_SIZE, fich2)))
            fwrite(tab.trans_tab, sizeof(entrada_hash), size2, fdest);
    }

    fclose(fich1);
    fclose(fich2);
    fclose(fdest);
    if (del_orig) {
        remove(file_orig1);
        remove(file_orig2);
    }
    rename("mergetmp", file_dest);
}
