/* Copyright (c) 2002 Tom Holroyd
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef FC_SOLVE_PATSOLVE_PATS_PLAY_H
#define FC_SOLVE_PATSOLVE_PATS_PLAY_H

#include "config.h"
#include "pat.h"
#include "inline.h"

#ifdef DEBUG
#include "msg.h"
#endif

#include "pats__print_msg.h"

static GCC_INLINE void fc_solve_pats__before_play(
    fcs_pats_thread_t * soft_thread
)
{
    /* Initialize the hash tables. */

    fc_solve_pats__init_buckets(soft_thread);
    fc_solve_pats__init_clusters(soft_thread);

    /* Reset stats. */

    soft_thread->num_checked_states = 0;
    soft_thread->num_states_in_collection = 0;
    soft_thread->num_solutions = 0;

    soft_thread->status = FCS_PATS__NOSOL;

    /* Go to it. */

    fc_solve_pats__initialize_solving_process(soft_thread);
}

static GCC_INLINE void fc_solve_pats__play(fcs_pats_thread_t * const soft_thread)
{
    fc_solve_pats__before_play(soft_thread);
    fc_solve_pats__do_it(soft_thread);
    if (soft_thread->status != FCS_PATS__WIN && !soft_thread->is_quiet)
    {
        if (soft_thread->status == FCS_PATS__FAIL)
        {
            printf("Out of memory.\n");
        }
        else if (soft_thread->Noexit && soft_thread->num_solutions > 0)
        {
            printf("No shorter solutions.\n");
        }
        else
        {
            printf("No solution.\n");
        }
#ifdef DEBUG
        printf("%d positions generated.\n", soft_thread->num_states_in_collection);
        printf("%d unique positions.\n", soft_thread->num_checked_states);
        printf("remaining_memory = %ld\n", soft_thread->remaining_memory);
#endif
    }
#ifdef DEBUG
    fc_solve_msg("remaining_memory = %ld\n", soft_thread->remaining_memory);
#endif
}

static void set_param(fcs_pats_thread_t * const soft_thread, const int param_num)
{
    soft_thread->pats_solve_params = freecell_solver_pats__x_y_params_preset[param_num];
    soft_thread->cutoff = soft_thread->pats_solve_params.x[FC_SOLVE_PATS__NUM_X_PARAM - 1];
}

static const int freecell_solver_user_set_sequences_are_built_by_type(
    fc_solve_instance_t * const instance,
    const int sequences_are_built_by
    )
{
#ifndef FCS_FREECELL_ONLY
    if ((sequences_are_built_by < 0) || (sequences_are_built_by > 2))
    {
        return 1;
    }

    instance->game_params.game_flags &= (~0x3);
    instance->game_params.game_flags |= sequences_are_built_by;

#endif

    return 0;
}

static const int freecell_solver_user_set_empty_stacks_filled_by(
    fc_solve_instance_t * const instance,
    const int empty_stacks_fill
    )
{

#ifndef FCS_FREECELL_ONLY
    if ((empty_stacks_fill < 0) || (empty_stacks_fill > 2))
    {
        return 1;
    }

    instance->game_params.game_flags &= (~(0x3 << 2));
    instance->game_params.game_flags |=
        (empty_stacks_fill << 2);
#endif

    return 0;
}

static GCC_INLINE const int get_idx_from_env(const char * const name)
{
    const char * const s = getenv(name);
    if (!s)
    {
        return -1;
    }
    else
    {
        return atoi(s);
    }
}


static GCC_INLINE void pats__init_soft_thread_and_instance(
    fcs_pats_thread_t * const soft_thread,
    fc_solve_instance_t * const instance
)
{
    fc_solve_pats__init_soft_thread(soft_thread, instance);
#ifndef FCS_FREECELL_ONLY
    instance->game_params.game_flags = 0;
    instance->game_params.game_flags |= FCS_SEQ_BUILT_BY_ALTERNATE_COLOR;
    instance->game_params.game_flags |= FCS_ES_FILLED_BY_ANY_CARD << 2;
    INSTANCE_DECKS_NUM = 1;
    INSTANCE_STACKS_NUM = 10;
    INSTANCE_FREECELLS_NUM = 4;
#endif
}

static GCC_INLINE void fc_solve_pats__configure_soft_thread(
    fcs_pats_thread_t * const soft_thread,
    fc_solve_instance_t * const instance,
    int * const argc_ptr,
    const char * * * const argv_ptr
)
{
    int argc = *argc_ptr;
    const char * * argv = *argv_ptr;

    pats__init_soft_thread_and_instance(soft_thread, instance);

    Progname = *argv;

    /* Parse args twice.  Once to get the operating mode, and the
    next for other options. */

    typeof(argc) argc0 = argc;
    typeof(argv) argv0 = argv;
    const char * curr_arg;
    while (--argc > 0 && **++argv == '-' && *(curr_arg = 1 + *argv)) {

        /* Scan current argument until a flag indicates that the rest
        of the argument isn't flags (curr_arg = NULL), or until
        the end of the argument is reached (if it is all flags). */

        int c;
        while (curr_arg != NULL && (c = *curr_arg++) != '\0') {
            switch (c) {

            case 's':
                freecell_solver_user_set_empty_stacks_filled_by(
                    instance,
                    FCS_ES_FILLED_BY_ANY_CARD
                );
                freecell_solver_user_set_sequences_are_built_by_type(
                    instance,
                    FCS_SEQ_BUILT_BY_SUIT
                );
#ifndef FCS_FREECELL_ONLY
                INSTANCE_STACKS_NUM = 10;
                INSTANCE_FREECELLS_NUM = 4;
#endif
                break;

            case 'f':
                freecell_solver_user_set_empty_stacks_filled_by(
                    instance,
                    FCS_ES_FILLED_BY_ANY_CARD
                );
                freecell_solver_user_set_sequences_are_built_by_type(
                    instance,
                    FCS_SEQ_BUILT_BY_ALTERNATE_COLOR
                );
#ifndef FCS_FREECELL_ONLY
                INSTANCE_STACKS_NUM = 8;
                INSTANCE_FREECELLS_NUM = 4;
#endif
                break;

            case 'k':
                freecell_solver_user_set_empty_stacks_filled_by(
                    instance,
                    FCS_ES_FILLED_BY_KINGS_ONLY
                );
                break;

            case 'a':
                freecell_solver_user_set_empty_stacks_filled_by(
                    instance,
                    FCS_ES_FILLED_BY_ANY_CARD
                );
                break;

            case 'S':
                soft_thread->to_stack = TRUE;
                break;

            case 'w':
#ifndef FCS_FREECELL_ONLY
                INSTANCE_STACKS_NUM = atoi(curr_arg);
#endif
                curr_arg = NULL;
                break;

            case 't':
#ifndef FCS_FREECELL_ONLY
                INSTANCE_FREECELLS_NUM = atoi(curr_arg);
#endif
                curr_arg = NULL;
                break;

            case 'X':
                argv += FC_SOLVE_PATS__NUM_X_PARAM - 1;
                argc -= FC_SOLVE_PATS__NUM_X_PARAM - 1;
                curr_arg = NULL;
                break;

            case 'Y':
                argv += FC_SOLVE_PATS__NUM_Y_PARAM;
                argc -= FC_SOLVE_PATS__NUM_Y_PARAM;
                curr_arg = NULL;
                break;

            default:
                break;
            }
        }
    }

    /* Set parameters. */

    if (!(GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) && !(INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) && !soft_thread->to_stack) {
        set_param(soft_thread, FC_SOLVE_PATS__PARAM_PRESET__FreecellBest);
    } else if (!(GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) && !(INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) && soft_thread->to_stack) {
        set_param(soft_thread, FC_SOLVE_PATS__PARAM_PRESET__FreecellSpeed);
    } else if ((GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) && !(INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) && !soft_thread->to_stack) {
        set_param(soft_thread, FC_SOLVE_PATS__PARAM_PRESET__SeahavenBest);
    } else if ((GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) && !(INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) && soft_thread->to_stack) {
        set_param(soft_thread, FC_SOLVE_PATS__PARAM_PRESET__SeahavenSpeed);
    } else if ((GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) && (INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) && !soft_thread->to_stack) {
        set_param(soft_thread, FC_SOLVE_PATS__PARAM_PRESET__SeahavenKing);
    } else if ((GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) && (INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY) && soft_thread->to_stack) {
        set_param(soft_thread, FC_SOLVE_PATS__PARAM_PRESET__SeahavenKingSpeed);
    } else {
        set_param(soft_thread, 0);   /* default */
    }

    /* Now get the other args, and allow overriding the parameters. */

    argc = argc0;
    argv = argv0;
    int c;
    while (--argc > 0 && **++argv == '-' && *(curr_arg = 1 + *argv)) {
        while (curr_arg != NULL && (c = *curr_arg++) != '\0') {
            switch (c) {

            case 's':
            case 'f':
            case 'k':
            case 'a':
            case 'S':
                break;

            case 'w':
            case 't':
                curr_arg = NULL;
                break;

            case 'E':
                soft_thread->Noexit = TRUE;
                break;

            case 'c':
                soft_thread->cutoff = atoi(curr_arg);
                curr_arg = NULL;
                break;

            case 'M':
                soft_thread->remaining_memory = atol(curr_arg) * 1000000;
                curr_arg = NULL;
                break;

            case 'v':
                soft_thread->is_quiet = FALSE;
                break;

            case 'q':
                soft_thread->is_quiet = TRUE;
                break;

            case 'X':

                /* use -c for the last X param */

                for (int i = 0; i < FC_SOLVE_PATS__NUM_X_PARAM - 1; i++) {
                    soft_thread->pats_solve_params.x[i] = atoi(argv[i + 1]);
                }
                argv += FC_SOLVE_PATS__NUM_X_PARAM - 1;
                argc -= FC_SOLVE_PATS__NUM_X_PARAM - 1;
                curr_arg = NULL;
                break;

            case 'Y':
                for (int i = 0; i < FC_SOLVE_PATS__NUM_Y_PARAM; i++) {
                    soft_thread->pats_solve_params.y[i] = atof(argv[i + 1]);
                }
                argv += FC_SOLVE_PATS__NUM_Y_PARAM;
                argc -= FC_SOLVE_PATS__NUM_Y_PARAM;
                curr_arg = NULL;
                break;

            case 'P':
                {
                    int i = atoi(curr_arg);
                    if (i < 0 || i > FC_SOLVE_PATS__PARAM_PRESET__LastParam) {
                        fatalerr("invalid parameter code");
                    }
                    set_param(soft_thread, i);
                    curr_arg = NULL;
                }
                break;

            default:
                print_msg("%s: unknown flag -%c\n", Progname, c);
                USAGE();
                exit(1);
            }
        }
    }
#if !defined(HARD_CODED_NUM_STACKS) || !defined(HARD_CODED_NUM_FREECELLS)
    const fcs_game_type_params_t game_params = soft_thread->instance->game_params;
#endif

    if (soft_thread->to_stack && soft_thread->Noexit) {
        fatalerr("-S and -E may not be used together.");
    }
    if (soft_thread->remaining_memory < BLOCKSIZE * 2) {
        fatalerr("-M too small.");
    }
    if (LOCAL_STACKS_NUM > MAX_NUM_STACKS) {
        fatalerr("too many w piles (max %d)", MAX_NUM_STACKS);
    }
    if (LOCAL_FREECELLS_NUM > MAX_NUM_FREECELLS) {
        fatalerr("too many t piles (max %d)", MAX_NUM_FREECELLS);
    }

    /* Process the named file, or stdin if no file given.
    The name '-' also specifies stdin. */


    /* Initialize the suitable() macro variables. */

#ifndef FCS_FREECELL_ONLY
    instance->game_variant_suit_mask = FCS_PATS__COLOR;
    instance->game_variant_desired_suit_value = FCS_PATS__COLOR;
    if ((GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT)) {
        instance->game_variant_suit_mask = FCS_PATS__SUIT;
        instance->game_variant_desired_suit_value = 0;
    }
#endif

    /* Announce which variation this is. */

    if (!soft_thread->is_quiet) {
        printf("%s", (GET_INSTANCE_SEQUENCES_ARE_BUILT_BY(instance) == FCS_SEQ_BUILT_BY_SUIT) ? "Seahaven; " : "Freecell; ");
        if ((INSTANCE_EMPTY_STACKS_FILL == FCS_ES_FILLED_BY_KINGS_ONLY)) {
            printf("%s", "only Kings are allowed to start a pile.\n");
        } else {
            printf("%s", "any card may start a pile.\n");
        }
        printf("%d work piles, %d temp cells.\n", LOCAL_STACKS_NUM, LOCAL_FREECELLS_NUM);
    }

    *argc_ptr = argc;
    *argv_ptr = argv;

    return;
}

#endif /* FC_SOLVE_PATSOLVE_PATS_PLAY_H */
