/*
 *  This file is part of pgn-extract: a Portable Game Notation (PGN) extractor.
 *  Copyright (C) 1994-2019 David J. Barnes
 *
 *  pgn-extract is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  pgn-extract 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 pgn-extract. If not, see <http://www.gnu.org/licenses/>.
 *
 *  David J. Barnes may be contacted as d.j.barnes@kent.ac.uk
 *  https://www.cs.kent.ac.uk/people/staff/djb/
 */

/***
 * These routines are concerned with gathering moves of
 * the various sorts of variations specified by the -v
 * and -x flags.  In the former case, there are also functions
 * for checking the moves of a game against the variation
 * lists that are wanted.  Checking of the variations specified
 * by the -x flag is handled elsewhere by apply_move_list().
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "bool.h"
#include "mymalloc.h"
#include "lines.h"
#include "defs.h"
#include "typedef.h"
#include "map.h"
#include "lists.h"
#include "tokens.h"
#include "taglist.h"
#include "lex.h"
#include "moves.h"
#include "apply.h"
#include "decode.h"
#include "fenmatcher.h"

/* Define a character that can be used in the variations file to
 * mean that we don't mind what move was played at this point.
 * So: 
 *        * b6
 * means look for all games in which Black playes 1...b6, regardless
 * of White's first move.
 */
#define ANY_MOVE '*'
/* Define a character that can be used in the variations file to
 * mean that we do not wish to match a particular move.
 * So:
 *        e4 c5 !Nf3
 * means look for games in which Black does not play 2. Nf3 against
 * the Sicilian defence.
 */
#define DISALLOWED_MOVE '!'

/* Hold details of a single move within a variation. */
typedef struct {
    /* Characters of the move.
     * Alternative notations for the same move are separated by
     * a non-move character, e.g.:
     *        cxd|cxd4|c5xd4
     * could all be alternatives for the same basic pawn capture.
     */
    char *move;
    /* If we are interested in matching the moves in any order,
     * then we need to record whether or not the current move has
     * been matched or not.
     */
    Boolean matched;
} variant_move;

/* Hold details of a single variation, with a pointer to
 * an alternative variation.
 */
typedef struct variation_list {
    /* The list of moves. */
    variant_move *moves;
    /* Keep a count of how many ANY_MOVE moves there are in the move
     * list for each colour.  If these are non-zero then one is used
     * when a match fails when looking for permutations.
     */
    unsigned num_white_any_moves;
    unsigned num_black_any_moves;
    /* Keep a count of how many DISALLOWED_MOVE moves there are in the move
     * list for each colour.  If these are non-zero then the game
     * must be searched for them when looking for permutations before
     * the full search is made.
     */
    unsigned num_white_disallowed_moves;
    unsigned num_black_disallowed_moves;
    /* How many half-moves in the variation? */
    unsigned length;
    struct variation_list *next;
} variation_list;

/* The head of the variations-of-interest list. */
static variation_list *games_to_keep = NULL;

static Boolean textual_variation_match(const char *variation_move,
        const unsigned char *actual_move);

/*** Functions concerned with reading details of the variations
 *** of interest.
 ***/

/* Remove any move number prefix from str.
 * Return NULL if there is no move (only number)
 * otherwise return the head of the move portion.
 */
static char *
strip_move_number(char *str)
{
    while (isdigit((int) *str)) {
        str++;
    }
    while (*str == '.') {
        str++;
    }
    if (*str != '\0') {
        return str;
    }
    else {
        return (char *) NULL;
    }
}



/* Define values for malloc/realloc allocation. */
#define INIT_MOVE_NUMBER 10
#define MOVE_INCREMENT 5

/* Break up a single line of moves into a list of moves
 * comprising a variation.
 */
static variation_list *
compose_variation(char *line)
{
    variation_list *variation;
    variant_move *move_list;
    /* Keep track of the number of moves extracted from line. */
    unsigned num_moves = 0;
    unsigned max_moves = 0;
    char *move;

    variation = (variation_list *) malloc_or_die(sizeof (variation_list));
    /* We don't yet know how many ANY_MOVEs or DISALLOWED_MOVES there
     * will be in this variation.
     */
    variation->num_white_any_moves = 0;
    variation->num_black_any_moves = 0;
    variation->num_white_disallowed_moves = 0;
    variation->num_black_disallowed_moves = 0;
    /* Allocate an initial number of pointers for the moves of the variation. */
    move_list = (variant_move *) malloc_or_die(INIT_MOVE_NUMBER *
            sizeof (*move_list));
    max_moves = INIT_MOVE_NUMBER;
    /* Find the first move. */
    move = strtok(line, " ");
    while (move != NULL) {
        if ((move = strip_move_number(move)) == NULL) {
            /* Only a move number. */
        }
        else {
            /* See if we need some more space. */
            if (num_moves == max_moves) {
                move_list = (variant_move *) realloc_or_die((void *) move_list,
                        (max_moves + MOVE_INCREMENT) * sizeof (*move_list));
                if (move_list == NULL) {
                    /* Catastrophic failure. */
                    free((void *) variation);
                    return NULL;
                }
                else {
                    max_moves += MOVE_INCREMENT;
                }
            }
            /* Keep the move and initialise the matched field for
             * when we start matching games.
             */
            move_list[num_moves].move = move;
            move_list[num_moves].matched = FALSE;
            /* Keep track of moves that will match anything. */
            if (*move == ANY_MOVE) {
                /* Odd numbered half-moves in the variant list are Black. */
                if (num_moves & 0x01) {
                    variation->num_black_any_moves++;
                }
                else {
                    variation->num_white_any_moves++;
                }
                /* Beware of the potential for false matches. */
                if (strlen(move) > 1) {
                    fprintf(GlobalState.logfile,
                            "Warning: %c in %s should not be followed by additional move text.\n",
                            *move, move);
                    fprintf(GlobalState.logfile, "It could give false matches.\n");
                }
            }
            else if (*move == DISALLOWED_MOVE) {
                /* Odd numbered half-moves in the variant list are Black. */
                if (num_moves & 0x01) {
                    variation->num_black_disallowed_moves++;
                }
                else {
                    variation->num_white_disallowed_moves++;
                }
            }
            else {
                /* Unadorned move. */
            }
            num_moves++;
        }
        move = strtok((char *) NULL, " ");
    }
    variation->moves = move_list;
    variation->length = num_moves;
    variation->next = NULL;
    return variation;
}

/* Read each line of input and decompose it into a variation
 * to be placed in the games_to_keep list.
 */
void
add_textual_variations_from_file(FILE *fpin)
{
    char *line;

    while ((line = read_line(fpin)) != NULL) {
        add_textual_variation_from_line(line);
    }
}

/* Add the text of the given line to the list of games_to_keep.
 */
void
add_textual_variation_from_line(char *line)
{
    if (non_blank_line(line)) {
        variation_list *next_variation = compose_variation(line);
        if (next_variation != NULL) {
            next_variation->next = games_to_keep;
            games_to_keep = next_variation;
        }
    }
}

/*** Functions concerned with reading details of the positional
 *** variations of interest.
 ***/

/* Break up a single line of moves into a list of moves
 * comprising a positional variation.
 * In doing so, set GlobalState.depth_of_positional_search
 * if this variation is longer than the default.
 */
static Move *
compose_positional_variation(char *line)
{
    char *move;
    /* Build a linked list of the moves of the variation. */
    Move *head = NULL, *tail = NULL;
    Boolean Ok = TRUE;
    /* Keep track of the ply depth. */
    unsigned depth = 0;

    move = strtok(line, " ");
    while (Ok && (move != NULL) && (*move != '*')) {
        if ((move = strip_move_number(move)) == NULL) {
            /* Only a move number. */
        }
        else {
            Move *next = decode_move((unsigned char *) move);

            if (next == NULL) {
                fprintf(GlobalState.logfile, "Failed to identify %s\n", move);
                Ok = FALSE;
            }
            else {
                /* Chain it on to the list. */
                if (tail == NULL) {
                    head = next;
                    tail = next;
                }
                else {
                    tail->next = next;
                    tail = next;
                }
                next->next = NULL;
            }
            depth++;
        }
        /* Pick up the next likely move. */
        move = strtok(NULL, " ");
    }
    if (Ok) {
        /* Determine whether the depth of this variation exceeds
         * the current default.
         * Depth is counted in ply.
         * Add some extras, in order to catch transpositions.
         */
        depth += 8;
        if (depth > GlobalState.depth_of_positional_search) {
            GlobalState.depth_of_positional_search = depth;
        }
    }
    else {
        if (head != NULL) {
            free_move_list(head);
        }
        head = NULL;
    }
    return head;
}

/* Read each line of input and decompose it into a positional variation
 * to be placed in the list of required hash values.
 */
void
add_positional_variations_from_file(FILE *fpin)
{
    char *line;

    while ((line = read_line(fpin)) != NULL) {
        add_positional_variation_from_line(line);
    }
}

void
add_positional_variation_from_line(char *line)
{
    if (non_blank_line(line)) {
        Move *next_variation = compose_positional_variation(line);
        if (next_variation != NULL) {
            /* We need a NULL fen string, because this is from
             * the initial position.
             */
            store_hash_value(next_variation, (const char *) NULL);
            free_move_list(next_variation);
            /* We need to know globally that positional variations
             * are of interest.
             */
            GlobalState.positional_variations = TRUE;
        }
    }
}

/* Treat fen_string as being a position to be matched.
 */
void
add_fen_positional_match(const char *fen_string)
{
    store_hash_value((Move *) NULL, fen_string);
    GlobalState.positional_variations = TRUE;
}

/* Treat fen_pattern as being a position to be matched.
 */
void
add_fen_pattern_match(const char *fen_pattern, Boolean add_reverse, const char *label)
{
    add_fen_pattern(fen_pattern, add_reverse, label);
    GlobalState.positional_variations = TRUE;
}

/* Roughly define a move character for the purposes of textual
 * matching.
 */
static Boolean
move_char(char c)
{
    return (Boolean) isalpha((int) c) || isdigit((int) c) || (c == '-');
}

/* Return TRUE if there is a match for actual_move in variation_move.
 * A match means that the string in actual_move is found surrounded
 * by non-move characters in variation_move. For instance,
 *    variation_move == "Nc6|Nf3|f3" would match
 *    actual_move == "f3" but not actual_move == "c6".
 */
static Boolean
textual_variation_match(const char *variation_move, const unsigned char *actual_move)
{
    const char *match_point;
    Boolean found = FALSE;

    for (match_point = variation_move; !found && (match_point != NULL);) {
        /* Try for a match from where we are. */
        match_point = strstr(match_point, (const char *) actual_move);
        if (match_point != NULL) {
            /* A possible match. Make sure that the match point
             * is surrounded by non-move characters so as to be sure
             * that we haven't picked up part way through a variation string.
             * Assume success.
             */
            found = TRUE;
            if (match_point != variation_move) {
                if (move_char(match_point[-1])) {
                    found = FALSE;
                }
            }
            if (move_char(match_point[strlen((const char *) actual_move)])) {
                found = FALSE;
            }
            if (!found) {
                /* Move on the match point and try again. */
                while (move_char(*match_point)) {
                    match_point++;
                }
            }
        }
    }
    return found;

}

/*** Functions concerned with matching the moves of the current game
 *** against the variations of interest.
 ***/

/* Do the moves of the current game match the given variation?
 * Go for a straight 1-1 match in the ordering, without considering
 * permutations.
 * Return TRUE if so, FALSE otherwise.
 */
static Boolean
straight_match(Move *current_game_head, variation_list variation)
{
    variant_move *moves_of_the_variation;
    /* Which is the next move that we wish to match. */
    Move *next_move;
    unsigned move_index = 0;
    /* Assume that it matches. */
    Boolean matches = TRUE;

    /* Access the head of the current game. */
    next_move = current_game_head;
    /* Go for a straight move-by-move match in the order in which
     * the variation is listed.
     * Point at the head of the moves list.
     */
    moves_of_the_variation = variation.moves;
    move_index = 0;
    while (matches && (next_move != NULL) && (move_index < variation.length)) {
        Boolean this_move_matches;

        if (*(moves_of_the_variation[move_index].move) == ANY_MOVE) {
            /* Still matching as we don't care what the actual move is. */
        }
        else {
            this_move_matches =
                    textual_variation_match(moves_of_the_variation[move_index].move,
                    next_move->move);
            if (this_move_matches) {
                /* We found a match, check that it isn't disallowed. */
                if (*moves_of_the_variation[move_index].move == DISALLOWED_MOVE) {
                    /* This move is disallowed and implies failure. */
                    matches = FALSE;
                }
            }
            else {
                if (*moves_of_the_variation[move_index].move != DISALLOWED_MOVE) {
                    /* No match found for this move. */
                    matches = FALSE;
                }
                else {
                    /* This is ok, because we didn't want a match. */
                }
            }

        }
        /* If we are still matching, go on to the next move. */
        if (matches) {
            move_index++;
            next_move = next_move->next;
        }
    }
    /* The game could be shorter than the variation, so don't rely
     * on the fact that matches is still true.
     */
    matches = (move_index == variation.length);
    return matches;
}

/* Do the moves of the current game match the given variation?
 * Try all possible orderings for the moves, within the
 * constraint of proper WHITE/BLACK moves.
 * The parameter variation is passed as a copy because we modify
 * the num_ fields within it.
 * Note that there is a possibility of a false match in this
 * function if a variant move is specified in a form such as:
 *                *|c4
 * This is because the num_ field is set from this on the basis of the
 * ANY_MOVE character at the start.  However, this could also match a
 * normal move with its c4 component.  If it is used for the latter
 * purpose then it should not count as an any_ move.  There is a warning
 * issued about this when variations are read in.
 * Return TRUE if we match, FALSE otherwise.
 *
 * The DISALLOWED_MOVE presents some problems with permutation matches
 * because an ANY_MOVE could match an otherwise disallowed move. The
 * approach that has been taken is to cause matching of a single disallowed
 * move to result in complete failure of the current match.
 */
static Boolean
permutation_match(Move *current_game_head, variation_list variation)
{
    variant_move *moves_of_the_variation;
    /* Which is the next move that we wish to match? */
    Move *next_move;
    unsigned variant_index = 0;
    /* Assume that it matches. */
    Boolean matches = TRUE;
    /* How many moves have we matched?
     * When this reaches variation.length we have a full match.
     */
    unsigned matched_moves = 0;
    Boolean white_to_move = TRUE;

    moves_of_the_variation = variation.moves;
    /* Clear all of the matched fields of the variation. */
    for (variant_index = 0; variant_index < variation.length; variant_index++) {
        moves_of_the_variation[variant_index].matched = FALSE;
    }
    /* Access the head of the current game. */
    next_move = current_game_head;

    /*** Stage One.
     * The first task is to ensure that there are no DISALLOWED_MOVEs in
     * the current game.
     */
    if ((variation.num_white_disallowed_moves > 0) ||
            (variation.num_black_disallowed_moves > 0)) {
        unsigned tested_moves = 0;
        Boolean disallowed_move_found = FALSE;

        /* Keep going as long as we still have not found a diallowed move,
         * we haven't matched the whole variation, and we haven't reached the end of
         * the game.
         */
        while ((!disallowed_move_found) && (tested_moves < variation.length) &&
                (next_move != NULL)) {
            /* We want to see if next_move is a disallowed move of the variation. */
            if (white_to_move) {
                /* White; start with the first move. */
                variant_index = 0;
            }
            else {
                /* For a Black move, start at the second half-move in the list, if any. */
                variant_index = 1;
            }
            /* Try each move of the variation in turn, until a match is found. */
            while ((!disallowed_move_found) && (variant_index < variation.length)) {
                if ((*moves_of_the_variation[variant_index].move ==
                        DISALLOWED_MOVE) &&
                        (textual_variation_match(
                        moves_of_the_variation[variant_index].move, next_move->move))) {
                    /* Found one. */
                    disallowed_move_found = TRUE;
                }
                if (!disallowed_move_found) {
                    /* Move on to the next available move -- 2 half moves along. */
                    variant_index += 2;
                }
            }
            if (!disallowed_move_found) {
                /* Ok so far, so move on. */
                tested_moves++;
                white_to_move = !white_to_move;
                next_move = next_move->next;
            }
        }
        if (disallowed_move_found) {
            /* This rules out the whole match. */
            matches = FALSE;
        }
        else {
            /* In effect, each DISALLOWED_MOVE now becomes an ANY_MOVE. */
            for (variant_index = 0; variant_index < variation.length; variant_index++) {
                if (*moves_of_the_variation[variant_index].move == DISALLOWED_MOVE) {
                    moves_of_the_variation[variant_index].matched = TRUE;
                    if ((variant_index & 1) == 0) {
                        variation.num_white_any_moves++;
                    }
                    else {
                        variation.num_black_any_moves++;
                    }
                }
            }
        }
    }

    /*** Stage Two.
     * Having eliminated moves which have been disallowed, try permutations
     * of the variation against the moves of the current game.
     */
    /* Access the head of the current game. */
    next_move = current_game_head;
    white_to_move = TRUE;
    /* Keep going as long as we still have matches, we haven't
     * matched the whole variation, and we haven't reached the end of
     * the game.
     */
    while (matches && (matched_moves < variation.length) && (next_move != NULL)) {
        /* Assume failure. */
        matches = FALSE;
        /* We want to find next_move in an unmatched move of the variation. */
        if (white_to_move) {
            /* Start with the first move. */
            variant_index = 0;
        }
        else {
            /* For a Black move, start at the second half-move in the list, if any. */
            variant_index = 1;
        }
        /* Try each move of the variation in turn, until a match is found. */
        while ((!matches) && (variant_index < variation.length)) {
            if (moves_of_the_variation[variant_index].matched) {
                /* We can't try this. */
            }
            else {
                Boolean this_move_matches = textual_variation_match(
                        moves_of_the_variation[variant_index].move,
                        next_move->move);
                if (this_move_matches) {
                    /* Found it. */
                    moves_of_the_variation[variant_index].matched = TRUE;
                    matches = TRUE;
                }
            }
            if (!matches) {
                /* Move on to the next available move -- 2 half moves along. */
                variant_index += 2;
            }
        }
        /* See if we made a straight match. */
        if (!matches) {
            /* See if we have some ANY_MOVEs available. */
            if (white_to_move && (variation.num_white_any_moves > 0)) {
                matches = TRUE;
                variation.num_white_any_moves--;
            }
            else if (!white_to_move && (variation.num_black_any_moves > 0)) {
                matches = TRUE;
                variation.num_black_any_moves--;
            }
            else {
                /* No slack. */
            }
        }
        /* We have tried everything, did we succeed? */
        if (matches) {
            /* Yes, so move on. */
            matched_moves++;
            next_move = next_move->next;
            white_to_move = !white_to_move;
        }
    }
    if (matches) {
        /* Ensure that we completed the variation. */
        matches = matched_moves == (variation.length);
    }
    return matches;
}

/* Determine whether or not the current game is wanted.
 * It will be if we are either not looking for checkmate-only
 * games, or if we are and the games does end in checkmate.
 */
Boolean
check_for_only_checkmate(const Game *game_details)
{
    if (GlobalState.match_only_checkmate) {
        const Move *moves = game_details->moves;
        /* Check that the final move is checkmate. */
        while (moves != NULL && moves->check_status != CHECKMATE) {
            moves = moves->next;
        }
        if (moves == NULL) {
            return FALSE;
        }
        else {
            return TRUE;
        }
    }
    else {
        /* No restriction to a checkmate game. */
        return TRUE;
    }
}

/* Determine whether or not the current game is wanted.
 * It will be if we are either not looking for stalemate-only
 * games, or if we are and the games does end in stalemate.
 */
Boolean
check_for_only_stalemate(const Board *board, const Move *moves)
{
    if (GlobalState.match_only_stalemate) {
        return is_stalemate(board, moves);
    }
    else {
        /* No restriction to a stalemate game. */
        return TRUE;
    }
}

/*
 * Determine whether the final position on the given board
 * is stalemate or not.
 */
Boolean
is_stalemate(const Board *board, const Move *moves)
{
    if (moves != NULL) {
        /* Check that the final move is not check or checkmate. */
        const Move *move = moves;
        while (move->next != NULL) {
            move = move->next;
        }
        if (move->check_status != NOCHECK) {
            /* Cannot be stalemate. */
            return FALSE;
        }
    }
    return !at_least_one_move(board, board->to_move);
}

/* Determine whether or not the current game is wanted.
 * It will be if it matches one of the current variations
 * and its tag details match those that we are interested in.
 */
Boolean
check_textual_variations(const Game *game_details)
{
    Boolean wanted = FALSE;
    variation_list *variation;

    if (games_to_keep != NULL) {
        for (variation = games_to_keep; (variation != NULL) && !wanted;
                variation = variation->next) {
            if (GlobalState.match_permutations) {
                wanted = permutation_match(game_details->moves, *variation);
            }
            else {
                wanted = straight_match(game_details->moves, *variation);
            }
        }
    }
    else {
        /* There are no variations, assume that selection is done
         * on the basis of the Details.
         */
        wanted = TRUE;
    }
    return wanted;
}

/* Determine whether the number of ply in this game
 * is within the bounds of what we want.
 */
Boolean
check_move_bounds(unsigned plycount)
{

    if (GlobalState.check_move_bounds) {
        return (GlobalState.lower_move_bound <= plycount) &&
                (plycount <= GlobalState.upper_move_bound);
    }
    else {
        // No restriction.
        return TRUE;
    }
}
