/***************************************************************************
*             __________               __   ___.
*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
*                     \/            \/     \/    \/            \/
*
* Copyright (C) 2007 Mauricio Peccorini
*
* 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
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/

#include "plugin.h"
#include "chessbox_pgn.h"

#define LOG_FILE  PLUGIN_GAMES_DATA_DIR  "/chessbox.log"
int loghandler;
const char *pgn_file = PLUGIN_GAMES_DATA_DIR  "/chessbox.pgn";

short kn_offs[8][2] = {{2,1},{2,-1},{-2,1},{-2,-1},{1,2},{1,-2},{-1,2},{-1,-2}};
short rk_offs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
short bp_offs[4][2] = {{1,1},{-1,1},{1,-1},{-1,-1}};

/* global vars for pl_malloc() */
void *bufptr = NULL;
size_t bufleft;

/* simple function to "allocate" memory in pluginbuffer.
 * (borrowed from dict.c)
 */
static void *pl_malloc(size_t size)
{
    void *ptr;
    ptr = bufptr;

    if (bufleft < size)
    {
        return NULL;
    }
    else
    {
        bufptr = (char*)(bufptr) + size;
        bufleft -= size;
        return ptr;
    }
}

/* init function for pl_malloc() */
static void pl_malloc_init(void)
{
    bufptr = rb->plugin_get_buffer(&bufleft);
}

static void process_tag(struct pgn_game_node* game, char* buffer){
    char tag_type[20];
    char tag_value[255];
    short pos=0, pos2=0;
    while (buffer[pos+1] != ' '){
        tag_type[pos] = buffer[pos+1];
        pos++;
    }
    tag_type[pos] = '\0';
    pos+=3;
    while (buffer[pos] != '"'){
        tag_value[pos2] = buffer[pos];
        pos++; pos2++;
    }

    /* truncate tag values that are too large */
    if (pos2 > 19){
        pos2 = 19;
    }
    tag_value[pos2] = '\0';

    if (rb->strcmp(tag_type,"White") == 0){
        rb->strcpy(game->white_player, tag_value);
    }
    if (rb->strcmp(tag_type,"Black") == 0){
        rb->strcpy(game->black_player, tag_value);
    }
    if (rb->strcmp(tag_type,"Result") == 0){
        rb->strcpy(game->result, tag_value);
    }
    if (rb->strcmp(tag_type,"Date") == 0){
        rb->strcpy(game->game_date, tag_value);
    }
}

static unsigned short get_next_token(const char* line_buffer, unsigned short initial_pos,
                              char* token_buffer){
    unsigned short pos, token_pos=0;
    for (pos = initial_pos;line_buffer[pos] == ' ' || line_buffer[pos] == '.';pos++);
    do {
        token_buffer[token_pos] = line_buffer[pos];
        pos++; token_pos++;
    } while (line_buffer[pos] != ' ' && line_buffer[pos] != '.'
             && line_buffer[pos] != '\0');
    /* ignore annotations */
    while (token_buffer[token_pos-1] == '!' || token_buffer[token_pos-1] == '?'){
        token_pos--;
    }
    token_buffer[token_pos] = '\0';
    return pos;
}

static unsigned short piece_from_pgn(char pgn_piece){
    switch (pgn_piece){
        case 'R':
            return rook;
        case 'N':
            return knight;
        case 'B':
            return bishop;
        case 'Q':
            return queen;
        case 'K':
            return king;
    }
    return no_piece;
}

static char pgn_from_piece(unsigned short piece, unsigned short color){
    char pgn_piece = ' ';
    switch (piece){
        case pawn:
            pgn_piece = 'P';
            break;
        case rook:
            pgn_piece = 'R';
            break;
        case knight:
            pgn_piece = 'N';
            break;
        case bishop:
            pgn_piece = 'B';
            break;
        case queen:
            pgn_piece = 'Q';
            break;
        case king:
            pgn_piece = 'K';
            break;
        case no_piece:
            pgn_piece = ' ';
            break;
    }
    if (color == black && pgn_piece != ' '){
        pgn_piece += 32;
    }
    return pgn_piece;
}

static bool pgn_to_coords(struct pgn_ply_node* ply){
    bool success = true;
    unsigned short str_length = rb->strlen(ply->pgn_text);
    char str[9];
    rb->strcpy(str,ply->pgn_text);
    ply->column_from = 0xFF;
    ply->row_from = 0xFF;
    ply->column_to = 0xFF;
    ply->row_to = 0xFF;
    ply->taken_piece = no_piece;
    ply->promotion_piece = no_piece;
    ply->enpassant = false;
    ply->castle = false;
    ply->promotion = false;

    unsigned short i, j, piece;
    bool found = false;

    if (str_length >= 3 && (str[0] == 'O' || str[0] == '0') && str[1] == '-'
        && (str[2] == 'O' || str[2] == '0')) {
        /* castling */
        ply->castle = true;
        if (str_length >= 5 && str[3] == '-' && (str[4] == 'O' || str[4] == '0')){
            /* castle queenside */
            if (ply->player == white){
                ply->row_from = 0; ply->column_from = 4;
                ply->row_to = 0; ply->column_to = 2;
                /* update the rook's position, the king's position will be updated later */
                board[locn[0][3]] = rook; color[locn[0][3]] = white;
                board[locn[0][0]] = no_piece; color[locn[0][0]] = neutral;
            } else {
                ply->row_from = 7; ply->column_from = 4;
                ply->row_to = 7; ply->column_to = 2;
                board[locn[7][3]] = rook; color[locn[7][3]] = black;
                board[locn[7][0]] = no_piece; color[locn[7][0]] = neutral;
            }
        } else {
            /* castle kingside */
            if (ply->player == white){
                ply->row_from = 0; ply->column_from = 4;
                ply->row_to = 0; ply->column_to = 6;
                board[locn[0][5]] = rook; color[locn[0][5]] = white;
                board[locn[0][7]] = no_piece; color[locn[0][7]] = neutral;
            } else {
                ply->row_from = 7; ply->column_from = 4;
                ply->row_to = 7; ply->column_to = 6;
                board[locn[7][5]] = rook; color[locn[7][5]] = black;
                board[locn[7][7]] = no_piece; color[locn[7][7]] = neutral;
            }
        }
    } else if (str[0] >= 'a' && str[0] <= 'h'){
        /* pawns */
        ply->column_from = str[0] - 'a';
        if (str[1] == 'x'){
            ply->row_from = str[3] - '1' + ((ply->player==white)?-1:1);
            ply->row_to = str[3] - '1';
            ply->column_to = str[2] - 'a';
            if (board[locn[ply->row_to][ply->column_to]] == no_piece){
                /* en-passant, remove the pawn */
                ply->enpassant = true;
                board[locn[ply->row_from][ply->column_to]] = no_piece;
                color[locn[ply->row_from][ply->column_to]] = neutral;
                ply->taken_piece = pawn;
            } else {
                ply->taken_piece = board[locn[ply->row_to][ply->column_to]];
            }
        } else {
            ply->column_to = ply->column_from;
            ply->row_from = str[1] - '1' + ((ply->player==white)?-1:1);
            ply->row_to = str[1] - '1';
        }
        if (board[locn[ply->row_from][ply->column_from]] == no_piece){
            /* the pawn moved two squares */
            ply->row_from += ((ply->player==white)?-1:1);
        }
        if (ply->row_to == 7 || ply->row_to == 0){
            /* promotion */
            if (str[2] == '='){
                ply->promotion_piece = piece_from_pgn(str[3]);
            } else {
                ply->promotion_piece = piece_from_pgn(str[5]);
            }
            /* change the piece in the original position and wait
             * for the code at the end to move it
             */
            board[locn[ply->row_from][ply->column_from]] = ply->promotion_piece;
        }
    } else {
        /* the other pieces */
        piece = piece_from_pgn(str[0]);
        if (str[2] == 'x'){
            /* taken a piece and move was ambiguous */
            ply->column_to = str[3] - 'a';
            ply->row_to = str[4] - '1';
            ply->taken_piece = board[locn[ply->row_to][ply->column_to]];
            if (str[1] >= 'a' && str[1] <= 'h') {
                ply->column_from = str[1] - 'a';
            } else {
                ply->row_from = str[1] - '1';
            }
        } else if (str[1] == 'x') {
            /* taken a piece */
            ply->column_to = str[2] - 'a';
            ply->row_to = str[3] - '1';
            ply->taken_piece = board[locn[ply->row_to][ply->column_to]];
        } else if (str_length >= 4 && str[3] >= '0' && str[3] <= '9'){
            /* no piece taken and move was ambiguous */
                ply->column_to = str[2] - 'a';
            ply->row_to = str[3] - '1';
            if (str[1] >= 'a' && str[1] <= 'h') {
                ply->column_from = str[1] - 'a';
            } else {
                ply->row_from = str[1] - '1';
            }
        } else {
            /* regular move */
            ply->column_to = str[1] - 'a';
            ply->row_to = str[2] - '1';
        }
        if (piece == knight) {
            for (i=0;i<8;i++){
                if (ply->row_to + kn_offs[i][0] >= 0 && ply->row_to + kn_offs[i][0] <= 7
                    && ply->column_to + kn_offs[i][1] >= 0 && ply->column_to + kn_offs[i][1] <= 7
                    && board[locn[ply->row_to + kn_offs[i][0]][ply->column_to + kn_offs[i][1]]] == knight
                    && color[locn[ply->row_to + kn_offs[i][0]][ply->column_to + kn_offs[i][1]]] == ply->player
                    && (ply->row_from == 0xFF || ply->row_from == ply->row_to + kn_offs[i][0])
                    && (ply->column_from == 0xFF || ply->column_from == ply->column_to + kn_offs[i][1])) {
                    ply->row_from = ply->row_to + kn_offs[i][0];
                    ply->column_from = ply->column_to + kn_offs[i][1];
                }
            }
        }
        if (piece == rook || piece == queen || piece == king){
            for (i=0;i<4;i++){
                j = 1;
                found = false;
                while (ply->row_to+(j*rk_offs[i][0]) >= 0 && ply->row_to+(j*rk_offs[i][0]) <= 7 &&
                       ply->column_to+(j*rk_offs[i][1]) >= 0 && ply->column_to+(j*rk_offs[i][1]) <= 7){
                    if (board[locn[ply->row_to+(j*rk_offs[i][0])][ply->column_to+(j*rk_offs[i][1])]] != no_piece) {
                        if (board[locn[ply->row_to+(j*rk_offs[i][0])][ply->column_to+(j*rk_offs[i][1])]] == piece &&
                            color[locn[ply->row_to+(j*rk_offs[i][0])][ply->column_to+(j*rk_offs[i][1])]] == ply->player &&
                            (ply->row_from == 0xFF || ply->row_from == ply->row_to+(j*rk_offs[i][0])) &&
                            (ply->column_from == 0xFF || ply->column_from == ply->column_to+(j*rk_offs[i][1]))) {
                            ply->row_from = ply->row_to+(j*rk_offs[i][0]);
                            ply->column_from = ply->column_to+(j*rk_offs[i][1]);
                            found = true;
                        }
                        break;
                    }
                    j++;
                }
                if (found) {
                    break;
                }
            }
        }
        if (piece == bishop || ((piece == queen || piece == king) && !found)){
            for (i=0;i<4;i++){
                j = 1;
                found = false;
                while (ply->row_to+(j*bp_offs[i][0]) >= 0 && ply->row_to+(j*bp_offs[i][0]) <= 7 &&
                       ply->column_to+(j*bp_offs[i][1]) >= 0 && ply->column_to+(j*bp_offs[i][1]) <= 7){
                    if (board[locn[ply->row_to+(j*bp_offs[i][0])][ply->column_to+(j*bp_offs[i][1])]] != no_piece) {
                        if (board[locn[ply->row_to+(j*bp_offs[i][0])][ply->column_to+(j*bp_offs[i][1])]] == piece &&
                            color[locn[ply->row_to+(j*bp_offs[i][0])][ply->column_to+(j*bp_offs[i][1])]] == ply->player &&
                            (ply->row_from == 0xFF || ply->row_from == ply->row_to+(j*bp_offs[i][0])) &&
                            (ply->column_from == 0xFF || ply->column_from == ply->column_to+(j*bp_offs[i][1]))) {
                            ply->row_from = ply->row_to+(j*bp_offs[i][0]);
                            ply->column_from = ply->column_to+(j*bp_offs[i][1]);
                            found = true;
                        }
                        break;
                    }
                    j++;
                }
                if (found) {
                    break;
                }
            }
        }
    }

    /* leave a very complete log of the parsing of the game while it gets stable */
    for (i=0;i<8;i++){
        for (j=0;j<8;j++) {

            rb->fdprintf(loghandler,"%c",pgn_from_piece(board[locn[7-i][j]],color[locn[7-i][j]]));
        }
        rb->fdprintf(loghandler,"\n");
    }
    /* check bounds of row and columns should be 0-7 bad .pgn returns 0xFF */
    if ((ply->row_to | ply->column_to | ply->row_from | ply->column_from) < 8)
    {
    /* update the board */
        board[locn[ply->row_to][ply->column_to]] =
                                   board[locn[ply->row_from][ply->column_from]];
        color[locn[ply->row_to][ply->column_to]] =
                                    color[locn[ply->row_from][ply->column_from]];
        board[locn[ply->row_from][ply->column_from]] = no_piece;
        color[locn[ply->row_from][ply->column_from]] = neutral;
    }
    else
        success = false; /*ERROR*/
    return success;
}

static void coords_to_pgn(struct pgn_ply_node* ply){
    int pos = 0,i,j;
    unsigned short moving_piece = board[locn[ply->row_from][ply->column_from]];
    char unambiguous_position;
    bool found = false;
    char alg_move[5];
    char move_buffer[10];
    short move;
    if (moving_piece == king){
        /* check castling */
        if (ply->column_from == 4 && ply->column_to == 6){
            /* castling kingside */
            rb->strcpy(ply->pgn_text,"O-O");
            ply->castle = true;
        } else if (ply->column_from == 4 && ply->column_to == 2){
            /* castling queenside */
            rb->strcpy(ply->pgn_text,"O-O-O");
        } else {
            if (board[locn[ply->row_to][ply->column_to]] != no_piece){
                rb->snprintf(ply->pgn_text,10,"Kx%c%c",'a'+ply->column_to,
                                   '1'+ply->row_to);
            } else {
                rb->snprintf(ply->pgn_text,10,"K%c%c",'a'+ply->column_to,
                                   '1'+ply->row_to);
            }
        }
    } else if (moving_piece == pawn){
        if (ply->column_from != ply->column_to){
            /* check enpassant */
            if (board[locn[ply->row_to][ply->column_to]] == no_piece){
                ply->enpassant = true;
            }
            /* check promotions when taking a piece */
            if (ply->row_to == 0 || ply->row_to == 7) {
                ply->promotion = true;
                ply->promotion_piece = queen;
                rb->snprintf(ply->pgn_text,10,"%cx%c%c=D", 'a'+ply->column_from,
                                   'a'+ply->column_to,'1'+ply->row_to);
            } else {
                rb->snprintf(ply->pgn_text,10,"%cx%c%c", 'a'+ply->column_from,
                                   'a'+ply->column_to,'1'+ply->row_to);
            }
        } else {
            /* check promotions when not taking a piece */
            if (ply->row_to == 0 || ply->row_to == 7) {
                ply->promotion = true;
                ply->promotion_piece = queen;
                rb->snprintf(ply->pgn_text,10,"%c%c=D", 'a'+ply->column_to,
                                   '1'+ply->row_to);
            } else {
                rb->snprintf(ply->pgn_text,10,"%c%c", 'a'+ply->column_to,
                                   '1'+ply->row_to);
            }
        }
    } else {
        /* verify ambiguous moves for the different kinds of pieces */
        unambiguous_position = '\0';
        if (moving_piece == knight){
            for (i=0;i<8;i++){
                if (ply->row_to + kn_offs[i][0] >= 0 && ply->row_to + kn_offs[i][0] <= 7
                    && ply->column_to + kn_offs[i][1] >= 0 && ply->column_to + kn_offs[i][1] <= 7
                    && board[locn[ply->row_to + kn_offs[i][0]][ply->column_to + kn_offs[i][1]]] == knight
                    && color[locn[ply->row_to + kn_offs[i][0]][ply->column_to + kn_offs[i][1]]] == ply->player
                    && (ply->row_to + kn_offs[i][0] != ply->row_from
                        || ply->column_to + kn_offs[i][1] != ply->column_from)){
                    if (ply->row_to + kn_offs[i][0] != ply->row_from){
                        unambiguous_position = '1' + ply->row_from;
                    } else {
                        unambiguous_position = 'a' + ply->column_from;
                    }
                    break;
                }
            }
        }
        if (moving_piece == rook || moving_piece == queen){
            found = false;
            for (i=0;i<4;i++){
                j=1;
                while (ply->row_to+(j*rk_offs[i][0]) >= 0 && ply->row_to+(j*rk_offs[i][0]) <= 7 &&
                       ply->column_to+(j*rk_offs[i][1]) >= 0 && ply->column_to+(j*rk_offs[i][1]) <= 7){
                    if (board[locn[ply->row_to+(j*rk_offs[i][0])][ply->column_to+(j*rk_offs[i][1])]] != no_piece) {
                        if (board[locn[ply->row_to+(j*rk_offs[i][0])][ply->column_to+(j*rk_offs[i][1])]] == moving_piece &&
                            color[locn[ply->row_to+(j*rk_offs[i][0])][ply->column_to+(j*rk_offs[i][1])]] == ply->player &&
                            (ply->row_to+(j*rk_offs[i][0]) != ply->row_from
                             || ply->column_to+(j*rk_offs[i][1]) != ply->column_from)) {
                            if (ply->row_to+(j*rk_offs[i][0]) != ply->row_from){
                                unambiguous_position = '1' + ply->row_from;
                            } else {
                                unambiguous_position = 'a' + ply->column_from;
                            }
                            found = true;
                        }
                        break;
                    }
                    j++;
                }
                if (found) {
                    break;
                }
            }
        }
        if (moving_piece == bishop || (moving_piece == queen && !found)){
            for (i=0;i<4;i++){
                j=1;
                while (ply->row_to+(j*bp_offs[i][0]) >= 0 && ply->row_to+(j*bp_offs[i][0]) <= 7 &&
                       ply->column_to+(j*bp_offs[i][1]) >= 0 && ply->column_to+(j*bp_offs[i][1]) <= 7){
                    if (board[locn[ply->row_to+(j*bp_offs[i][0])][ply->column_to+(j*bp_offs[i][1])]] != no_piece) {
                        if (board[locn[ply->row_to+(j*bp_offs[i][0])][ply->column_to+(j*bp_offs[i][1])]] == moving_piece &&
                            color[locn[ply->row_to+(j*bp_offs[i][0])][ply->column_to+(j*bp_offs[i][1])]] == ply->player &&
                            (ply->row_to+(j*bp_offs[i][0]) != ply->row_from
                             || ply->column_to+(j*bp_offs[i][1]) != ply->column_from)) {
                            if (ply->row_to+(j*bp_offs[i][0]) != ply->row_from){
                                unambiguous_position = '1' + ply->row_from;
                            } else {
                                unambiguous_position = 'a' + ply->column_from;
                            }
                            found = true;
                        }
                        break;
                    }
                    j++;
                }
                if (found) {
                    break;
                }
            }
        }
        /* generate the first portion of the PGN text
         * always as white so all uppercase, black/white considerations
         * will be useful for FEN notation but not in this case
         */
        if (unambiguous_position == '\0'){
            if (board[locn[ply->row_to][ply->column_to]] != no_piece){
                rb->snprintf(ply->pgn_text,10,"%cx%c%c",
                                   pgn_from_piece(moving_piece,white) ,
                                   'a'+ply->column_to, '1'+ply->row_to);
            } else {
                rb->snprintf(ply->pgn_text,10,"%c%c%c",
                                   pgn_from_piece(moving_piece,white) ,
                                   'a'+ply->column_to, '1'+ply->row_to);
            }
        } else {
            if (board[locn[ply->row_to][ply->column_to]] != no_piece){
                rb->snprintf(ply->pgn_text,10,"%c%cx%c%c",
                                   pgn_from_piece(moving_piece,white) ,
                                   unambiguous_position, 'a'+ply->column_to,
                                   '1'+ply->row_to);
            } else {
                rb->snprintf(ply->pgn_text,10,"%c%c%c%c",
                                   pgn_from_piece(moving_piece,white) ,
                                   unambiguous_position, 'a'+ply->column_to,
                                   '1'+ply->row_to);
            }
        }
    }
    /* update the board */
    rb->snprintf(alg_move,5,"%c%c%c%c",'a' + ply->column_from, '1' + ply->row_from,
                'a' + ply->column_to, '1' + ply->row_to);
    /* The function returns false if the move is invalid, but since we're
     * replaying the game, that should not be posible
     */
    VerifyMove (ply->player, alg_move , 0 , &move, move_buffer );

    /* add check/mate indicators */
    for (pos=0;ply->pgn_text[pos] != '\0';pos++);
    if (ply->checkmate) {
        ply->pgn_text[pos] = '#'; pos++;
        ply->pgn_text[pos] = '\0'; pos++;
    } else if (move_buffer[4] == '+'){
        ply->pgn_text[pos] = '+'; pos++;
        ply->pgn_text[pos] = '\0'; pos++;
    }
}

static struct pgn_game_node *get_game_info(int selected_item, void *data){
    int i;
    struct pgn_game_node *temp_node = (struct pgn_game_node *)data;

    for (i=0;i<selected_item && temp_node != NULL;i++){
        temp_node = temp_node->next_node;
    }

    return temp_node;
}

static const char* get_game_text(int selected_item, void *data,
                                 char *buffer, size_t buffer_len){
    struct pgn_game_node *temp_node = get_game_info(selected_item, data);

    if (temp_node == NULL){
        return NULL;
    }
    rb->snprintf(buffer, buffer_len,"%s vs. %s (%s)", temp_node->white_player,
                         temp_node->black_player, temp_node->game_date);

    return buffer;
}

static void say_player(const char *name, bool enqueue) {
    if (!rb->strcasecmp(name, "player"))
        rb->talk_id(VOICE_PLAYER, enqueue);
    else if (!rb->strcasecmp(name, "gnuchess"))
        rb->talk_id(VOICE_GNUCHESS, enqueue);
    else
        rb->talk_spell(name, enqueue);
}

static int speak_game_selection(int selected_item, void *data){
    struct pgn_game_node *temp_node = get_game_info(selected_item, data);

    if (temp_node != NULL){
        say_player(temp_node->white_player, false);
        say_player(temp_node->black_player, true);
        if (temp_node->game_date[0] != '?') {
            char buf[12];
            rb->strcpy(buf, temp_node->game_date);
            buf[4] = 0;
            buf[7] = 0;
            rb->talk_id(LANG_MONTH_JANUARY + rb->atoi(&(buf[5])) - 1, true);
            rb->talk_number(rb->atoi(&(buf[8])), true);
            rb->talk_number(rb->atoi(buf), true);
        }
    }

    return 0;
}

static void write_pgn_token(int fhandler, char *buffer, size_t *line_length){
    if (*line_length + rb->strlen(buffer) + 1 > 80){
        rb->fdprintf(fhandler,"\n");
        *line_length = 0;
    }
    rb->fdprintf(fhandler,"%s ",buffer);
    *line_length += (rb->strlen(buffer) + 1);
}

/* ---- api functions ---- */
struct pgn_game_node* pgn_list_games(const char* filename){
    int fhandler;
    char line_buffer[128];
    struct pgn_game_node size_node, *first_game = NULL;
    struct pgn_game_node *curr_node = NULL, *temp_node;
    unsigned short game_count = 1;
    int line_count = 0;
    bool header_start = true, game_start = false;

    if ( (fhandler = rb->open(filename, O_RDONLY)) < 0 ) return NULL;

    if (bufptr == NULL){
        pl_malloc_init();
    }
    while (rb->read_line(fhandler, line_buffer, sizeof line_buffer) > 0){
        line_count++;
        /* looking for a game header */
        if (header_start) {
            /* a new game header is found */
            if (line_buffer[0] == '['){
                temp_node = (struct pgn_game_node *)pl_malloc(sizeof size_node);
                if (!temp_node) return NULL;
                temp_node->next_node = NULL;
                if (curr_node == NULL) {
                    first_game = curr_node = temp_node;
                } else {
                    curr_node->next_node = temp_node;
                    curr_node = temp_node;
                }
                process_tag(curr_node, line_buffer);
                curr_node->game_number = game_count;
                curr_node->pgn_line = 0;
                game_count++;
                header_start = false;
                game_start = true;
            }
        } else {
            if (line_buffer[0] == '['){
                process_tag(curr_node, line_buffer);
            } else if (line_buffer[0] == '\r'
                       || line_buffer[0] == '\n'
                       || line_buffer[0] == '\0'){
                if (game_start) {
                    game_start = false;
                } else {
                    header_start = true;
                }
            } else {
                if (curr_node->pgn_line == 0) {
                    curr_node->pgn_line = line_count;
                }
            }
        }
    }
    rb->close(fhandler);
    return first_game;
}

struct pgn_game_node* pgn_show_game_list(struct pgn_game_node* first_game){
    int curr_selection = 0;
    int button;
    struct gui_synclist games_list;
    int i;
    struct pgn_game_node *temp_node = first_game;

    for (i=0;temp_node != NULL;i++){
        temp_node = temp_node->next_node;
    }


    rb->gui_synclist_init(&games_list, &get_game_text, first_game, false, 1, NULL);
    rb->gui_synclist_set_title(&games_list, rb->str(LANG_CHESSBOX_GAMES), NOICON);
    if (rb->global_settings->talk_menu)
        rb->gui_synclist_set_voice_callback(&games_list, speak_game_selection);
    rb->gui_synclist_set_nb_items(&games_list, i);
    rb->gui_synclist_select_item(&games_list, 0);

    rb->gui_synclist_draw(&games_list);
    rb->gui_synclist_speak_item(&games_list);

    while (true) {
        curr_selection = rb->gui_synclist_get_sel_pos(&games_list);
        button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
        if (rb->gui_synclist_do_button(&games_list, &button))
            continue;
        switch (button) {
            case ACTION_STD_OK:
                return get_game_info(curr_selection, first_game);
            break;
            case ACTION_STD_CANCEL:
                return NULL;
            break;
        }
    }
}

void pgn_parse_game(const char* filename,
                    struct pgn_game_node* selected_game){
    struct pgn_ply_node size_ply, *first_ply = NULL;
    struct pgn_ply_node *temp_ply = NULL, *curr_node = NULL;
    bool success = true;
    int fhandler, i;
    char line_buffer[128];
    char token_buffer[9];
    unsigned short pos;
    unsigned short curr_player = white;

    fhandler = rb->open(filename, O_RDONLY);

    /* seek the line where the pgn of the selected game starts */
    for (i=1;i<selected_game->pgn_line;i++){
        rb->read_line(fhandler, line_buffer, sizeof line_buffer);
    }

    loghandler = rb->open(LOG_FILE, O_WRONLY | O_CREAT, 0666);

    GNUChess_Initialize();

    while (rb->read_line(fhandler, line_buffer, sizeof line_buffer) > 0){
        if (line_buffer[0] == '\r' || line_buffer[0] == '\n' || line_buffer[0] == '\0'){
            break;
        }
        pos = 0;
        while (pos < rb->strlen(line_buffer)){
            pos = get_next_token(line_buffer, pos, token_buffer);
            if ((token_buffer[0] >= 'A' && token_buffer[0] <= 'Z')
                || (token_buffer[0] >= 'a' && token_buffer[0] <= 'z')
                || (token_buffer[0] == '0' && token_buffer[2] != '1')){
                temp_ply = (struct pgn_ply_node *)pl_malloc(sizeof size_ply);
                /* Null pointer can be returned from pl_malloc check for this */
                if (temp_ply)
                {
                    temp_ply->player = curr_player;
                    curr_player = (curr_player==white)?black:white;
                    rb->strcpy(temp_ply->pgn_text, token_buffer);
                    success = pgn_to_coords(temp_ply);
                    temp_ply->prev_node = NULL;
                    temp_ply->next_node = NULL;
                    if (first_ply == NULL && success) {
                        first_ply = curr_node = temp_ply;
                    } else if (success){
                        curr_node->next_node = temp_ply;
                        temp_ply->prev_node = curr_node;
                        curr_node = temp_ply;
                    } else{
                       /* bad .pgn break loop and notify user */
                       first_ply = NULL;
                        break;
                    }

                    rb->fdprintf(loghandler,
                        "player: %u; pgn: %s; from: %u,%u; to: %u,%u; taken: %u.\n",
                             temp_ply->player, temp_ply->pgn_text, temp_ply->row_from,
                             temp_ply->column_from, temp_ply->row_to,
                             temp_ply->column_to, temp_ply->taken_piece);
                }
                else
                    first_ply = NULL;
            }
        }
    }

    rb->close(loghandler);

    /* additional dummy ply to represent end of file without
     *loosing the previous node's pointer
     */
    if (first_ply != NULL){
        temp_ply = (struct pgn_ply_node *)pl_malloc(sizeof size_ply);
        if (temp_ply) {
            temp_ply->player = neutral;
            temp_ply->prev_node = curr_node;
            curr_node->next_node = temp_ply;
        }
    }
    selected_game->first_ply = first_ply;

    rb->close(fhandler);
}

struct pgn_game_node* pgn_init_game(void){
    struct pgn_game_node game_size, *game;
    struct pgn_ply_node ply_size, *ply;
    struct tm *current_time;

    if (bufptr == NULL){
        pl_malloc_init();
    }

    /* create an "end of game" dummy ply and assign defaults */
    ply = (struct pgn_ply_node *)pl_malloc(sizeof ply_size);
    if (!ply) return NULL;
    ply->player = neutral;
    ply->pgn_text[0] = '\0';
    ply->prev_node = NULL;
    ply->next_node = NULL;

    /* create the game and assign defaults */
    game = (struct pgn_game_node *)pl_malloc(sizeof game_size);
    if (!game) return NULL;

    game->game_number = 0;
    rb->strcpy(game->white_player,"Player");
    rb->strcpy(game->black_player,"GnuChess");
    current_time = rb->get_time();
    if (current_time->tm_year < 100){
        rb->snprintf(game->game_date,11,"????.??.??");
    } else {
        rb->snprintf(game->game_date,11,"%4u.%2u.%2u",current_time->tm_year + 1900,
                     current_time->tm_mon + 1, current_time->tm_mday);
    }
    rb->strcpy(game->result,"*");
    game->pgn_line = 0;
    game->first_ply = ply;
    game->next_node = NULL;

    return game;
}

void pgn_append_ply(struct pgn_game_node* game,
                    unsigned short ply_player, char *move_buffer, bool is_mate){
    struct pgn_ply_node ply_size, *ply, *temp;

    ply = (struct pgn_ply_node *)pl_malloc(sizeof ply_size);
    if (!ply) return;
    ply->player = ply_player;
    ply->column_from = move_buffer[0] - 'a';
    ply->row_from = move_buffer[1] - '1';
    ply->column_to = move_buffer[2] - 'a';
    ply->row_to = move_buffer[3] - '1';
    ply->castle = false;
    ply->promotion = false;
    ply->enpassant = false;
    ply->promotion_piece = no_piece;
    ply->taken_piece = no_piece;
    ply->draw = false;
    ply->checkmate = is_mate;

    /* move the pointer to the "end of game" marker ply */
    for (temp=game->first_ply;temp->next_node!=NULL;temp=temp->next_node);

    /* arrange the pointers to insert the ply before the marker */
    ply->next_node = temp;
    ply->prev_node = temp->prev_node;
    if (temp->prev_node == NULL){
        game->first_ply = ply;
    } else {
        temp->prev_node->next_node = ply;
    }

    temp->prev_node = ply;
}

void pgn_set_result(struct pgn_game_node* game,
                    bool is_mate){

    struct pgn_ply_node *ply;
    for(ply=game->first_ply;ply->next_node != NULL;ply=ply->next_node);
    if (is_mate){
        ply->prev_node->checkmate = true;
    } else {
        ply->prev_node->draw = true;
    }
}

void pgn_store_game(struct pgn_game_node* game){
    int fhandler;
    struct pgn_ply_node *ply;
    unsigned ply_count;
    size_t line_length=0;
    char buffer[10];

    GNUChess_Initialize();

    ply_count=0;
    ply=game->first_ply;
    while (ply->next_node!=NULL){
        coords_to_pgn(ply);
        if (ply->checkmate){
            if (ply->player == white){
                rb->strcpy(game->result,"1-0");
            } else {
                rb->strcpy(game->result,"0-1");
            }
        }
        if (ply->draw){
            rb->strcpy(game->result,"1/2-1/2");
        }
        ply=ply->next_node;
        ply_count++;
    }

    fhandler = rb->open(pgn_file, O_WRONLY|O_CREAT|O_APPEND, 0666);


    /* the first 7 tags are mandatory according to the PGN specification so we
     * have to include them even if the values don't make much sense
     */
    rb->fdprintf(fhandler,"[Event \"Casual Game\"]\n");
    rb->fdprintf(fhandler,"[Site \"?\"]\n");
    rb->fdprintf(fhandler,"[Date \"%s\"]\n",game->game_date);
    rb->fdprintf(fhandler,"[Round \"?\"]\n");
    rb->fdprintf(fhandler,"[White \"%s\"]\n",game->white_player);
    rb->fdprintf(fhandler,"[Black \"%s\"]\n",game->black_player);
    rb->fdprintf(fhandler,"[Result \"%s\"]\n",game->result);
    rb->fdprintf(fhandler,"[PlyCount \"%u\"]\n",ply_count);

    /* leave a blank line between the tag section and the game section */
    rb->fdprintf(fhandler,"\n");

    /* write the plies in several lines of up to 80 characters */
    for (ply_count=0, ply=game->first_ply;ply->next_node!=NULL;
         ply=ply->next_node,ply_count++){
        /* write the move number */
        if (ply->player == white){
            rb->snprintf(buffer,10,"%u.",(ply_count/2)+1);
            write_pgn_token(fhandler, buffer, &line_length);
        }
        /* write the actual move */
        write_pgn_token(fhandler,ply->pgn_text,&line_length);
        /* write the result of the game at the end */
        if (ply->checkmate){
            if (ply->player == white){
                write_pgn_token(fhandler,"1-0",&line_length);
            } else {
                write_pgn_token(fhandler,"0-1",&line_length);
            }
            break;
        } else if (ply->draw){
            write_pgn_token(fhandler,"1/2-1/2",&line_length);
            break;
        } else if (ply->next_node->player == neutral) {
            /* unknown end of the game */
            write_pgn_token(fhandler,"*",&line_length);
            break;
        }
    }

    /* leave a blank line between the tag section and the game section */
    rb->fdprintf(fhandler,"\n\n");

    rb->close(fhandler);
}
