/*
 * lib.c - library interface functions of Freecell Solver.
 *
 * Written by Shlomi Fish (shlomif@vipe.technion.ac.il), 2000
 *
 * This file is in the public domain (it's uncopyrighted).
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "card.h"
#include "fcs.h"
#include "preset.h"
#include "fcs_user.h"

#ifdef DMALLOC
#include "dmalloc.h"
#endif

struct fcs_instance_item_struct
{
    freecell_solver_instance_t * instance;
    int ret;
    int limit;
};

typedef struct fcs_instance_item_struct fcs_instance_item_t;

struct fcs_user_struct
{
    /* 
     * This is a list of several consecutive instances that are run
     * one after the other in case the previous ones could not solve
     * the board 
     * */
    fcs_instance_item_t * instances_list;
    int num_instances;
    int max_num_instances;
    
    int current_instance_idx;
    /* 
     * The global (sequence-wide) limit of the iterations. Used
     * by limit_iterations() and friends
     * */
    int current_iterations_limit;
    /*
     * The number of iterations this board started at.
     * */
    int iterations_board_started_at;
    /*
     * The number of iterations that the current instance started solving from.
     * */
    int init_num_times;
    /*
     * A pointer to the currently active instance out of the sequence
     * */
    freecell_solver_instance_t * instance;
    fcs_state_with_locations_t state;
    fcs_state_with_locations_t running_state;
    int ret;
    int state_validity_ret;
    fcs_card_t state_validity_card;
    freecell_solver_user_iter_handler_t iter_handler;
    void * iter_handler_context;

    freecell_solver_soft_thread_t * soft_thread;

#ifdef INDIRECT_STACK_STATES
    fcs_card_t indirect_stacks_buffer[MAX_NUM_STACKS << 7];
#endif
    char * state_string_copy;

    fcs_preset_t common_preset;
};

typedef struct fcs_user_struct fcs_user_t;

static void user_initialize(
        fcs_user_t * ret
        )
{
    const fcs_preset_t * freecell_preset;
    
    freecell_solver_get_preset_by_name(
        "freecell",
        &freecell_preset
        );

    fcs_duplicate_preset(ret->common_preset, *freecell_preset);
        
    ret->max_num_instances = 10;
    ret->instances_list = malloc(sizeof(ret->instances_list[0]) * ret->max_num_instances);
    ret->num_instances = 1;
    ret->current_instance_idx = 0;
    ret->instance = freecell_solver_alloc_instance();
    freecell_solver_apply_preset_by_ptr(ret->instance, &(ret->common_preset));
    ret->instances_list[ret->current_instance_idx].instance = ret->instance;
    ret->instances_list[ret->current_instance_idx].ret = ret->ret = FCS_STATE_NOT_BEGAN_YET;
    ret->instances_list[ret->current_instance_idx].limit = -1;
    ret->current_iterations_limit = -1;

    ret->soft_thread =
        freecell_solver_instance_get_soft_thread(
            ret->instance, 0,0
            );

    ret->state_string_copy = NULL;
    ret->iterations_board_started_at = 0;
}

void * freecell_solver_user_alloc(void)
{
    fcs_user_t * ret;

    ret = (fcs_user_t *)malloc(sizeof(fcs_user_t));

    user_initialize(ret);

    return (void*)ret;
}

int freecell_solver_user_apply_preset(
    void * user_instance,
    const char * preset_name)
{
    const fcs_preset_t * new_preset_ptr;
    fcs_user_t * user;
    int status;
    int i;

    user = (fcs_user_t*)user_instance;

    status = 
        freecell_solver_get_preset_by_name(
            preset_name,
            &new_preset_ptr
            );

    if (status != FCS_PRESET_CODE_OK)
    {
        return status;
    }

    for(i = 0 ; i < user->num_instances ; i++)
    {
        status = freecell_solver_apply_preset_by_ptr(
            user->instances_list[i].instance,
            new_preset_ptr
            );

        if (status != FCS_PRESET_CODE_OK)
        {
            return status;
        }
    }

    fcs_duplicate_preset(user->common_preset, *new_preset_ptr);

    return FCS_PRESET_CODE_OK;
}

void freecell_solver_user_limit_iterations(
    void * user_instance,
    int max_iters
    )
{
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;

    user->current_iterations_limit = max_iters;
}

void freecell_solver_user_limit_current_instance_iterations(
    void * user_instance,
    int max_iters
    )
{
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;

    user->instances_list[user->current_instance_idx].limit = max_iters;
}

#ifndef min
#define min(a,b) (((a)<(b))?(a):(b))
#endif

int freecell_solver_user_set_tests_order(
    void * user_instance,
    const char * tests_order,
    char * * error_string
    )
{
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;

    return
        freecell_solver_apply_tests_order(
            &(user->soft_thread->tests_order),
            tests_order,
            error_string
            );
}

int freecell_solver_user_solve_board(
    void * user_instance,
    const char * state_as_string
    )
{
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;

    user->state_string_copy = strdup(state_as_string);

    user->current_instance_idx = 0;

    return freecell_solver_user_resume_solution(user_instance);
}

static void recycle_instance(
    fcs_user_t * user,
    int i
    )
{
    if (user->instances_list[i].ret == FCS_STATE_WAS_SOLVED)
    {
        fcs_move_stack_destroy(user->instance->solution_moves);
        user->instance->solution_moves = NULL;
    }
    else if (user->instances_list[i].ret == FCS_STATE_SUSPEND_PROCESS)
    {
        freecell_solver_unresume_instance(user->instances_list[i].instance);
    }

    if (user->instances_list[i].ret != FCS_STATE_NOT_BEGAN_YET)
    {
        freecell_solver_recycle_instance(user->instances_list[i].instance);
        /*
         * We have to initialize init_num_times to 0 here, because it may not 
         * get initialized again, and now the num_times of the instance
         * is equal to 0.
         * */
        user->init_num_times = 0;
    }

    user->instances_list[i].ret = FCS_STATE_NOT_BEGAN_YET;    
}

int freecell_solver_user_resume_solution(
    void * user_instance
    )
{
    int init_num_times;
    int run_for_first_iteration = 1;
    int ret;
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;
    
    /* 
     * I expect user->current_instance_idx to be initialized at some value.
     * */
    for( ; 
        run_for_first_iteration || ((user->current_instance_idx < user->num_instances) && (ret == FCS_STATE_IS_NOT_SOLVEABLE)) ;
        recycle_instance(user, user->current_instance_idx), user->current_instance_idx++
       )
    {
        run_for_first_iteration = 0;
        
        user->instance = user->instances_list[user->current_instance_idx].instance;

        if (user->instances_list[user->current_instance_idx].ret == FCS_STATE_NOT_BEGAN_YET)
        {
            int status;
            status = freecell_solver_initial_user_state_to_c(
                user->state_string_copy,
                &(user->state),
                user->instance->freecells_num,
                user->instance->stacks_num,
                user->instance->decks_num
#ifdef FCS_WITH_TALONS
                ,user->instance->talon_type
#endif
#ifdef INDIRECT_STACK_STATES
                ,user->indirect_stacks_buffer
#endif
                );

            if (status != FCS_USER_STATE_TO_C__SUCCESS)
            {
                user->ret = FCS_STATE_INVALID_STATE;
                user->state_validity_ret = FCS_STATE_VALIDITY__PREMATURE_END_OF_INPUT;
                return user->ret;
            }

            user->state_validity_ret = freecell_solver_check_state_validity(
                &user->state,
                user->instance->freecells_num,
                user->instance->stacks_num,
                user->instance->decks_num,
#ifdef FCS_WITH_TALONS
                FCS_TALON_NONE,
#endif
                &(user->state_validity_card));

            if (user->state_validity_ret != 0)
            {
                user->ret = FCS_STATE_INVALID_STATE;
                return user->ret;
            }

        
            /* running_state is a normalized state. So I'm duplicating
             * state to it before state is canonized
             * */
            fcs_duplicate_state(user->running_state, user->state);

            fcs_canonize_state(
                &user->state,
                user->instance->freecells_num,
                user->instance->stacks_num
                );

            freecell_solver_init_instance(user->instance);

#define global_limit() \
        (user->instance->num_times + user->current_iterations_limit - user->iterations_board_started_at)
#define local_limit()  \
        (user->instances_list[user->current_instance_idx].limit)
#define min(a,b) (((a)<(b))?(a):(b))
#define calc_max_iters() \
        {          \
            if (user->instances_list[user->current_instance_idx].limit < 0)  \
            {\
                if (user->current_iterations_limit < 0)\
                {\
                    user->instance->max_num_times = -1;\
                }\
                else\
                {\
                    user->instance->max_num_times = global_limit();\
                }\
            }\
            else\
            {\
                if (user->current_iterations_limit < 0)\
                {\
                    user->instance->max_num_times = local_limit();\
                }\
                else\
                {\
                    int a, b;\
                    \
                    a = global_limit();\
                    b = local_limit();\
        \
                    user->instance->max_num_times = min(a,b);\
                }\
            }\
        }


            calc_max_iters();

            user->init_num_times = init_num_times = user->instance->num_times;

            ret = user->ret = 
                user->instances_list[user->current_instance_idx].ret = 
                freecell_solver_solve_instance(user->instance, &user->state);
        }
        else
        {

            calc_max_iters();
    
            user->init_num_times = init_num_times = user->instance->num_times;
            
            ret = user->ret = 
                user->instances_list[user->current_instance_idx].ret =
                freecell_solver_resume_instance(user->instance);
        }
        
        user->iterations_board_started_at += user->instance->num_times - init_num_times;
        user->init_num_times = user->instance->num_times;

        if (user->ret == FCS_STATE_WAS_SOLVED)
        {
            freecell_solver_move_stack_normalize(
                user->instance->solution_moves,
                &(user->state),
                user->instance->freecells_num,
                user->instance->stacks_num,
                user->instance->decks_num
                );

            break;
        }
        else if (user->ret == FCS_STATE_SUSPEND_PROCESS)
        {
            /* 
             * First - check if we exceeded our limit. If so - we must terminate
             * and return now.
             * */
            if ((user->current_iterations_limit >= 0) &&
                (user->iterations_board_started_at >= user->current_iterations_limit))
            {
                break;
            }
            
            /* 
             * Determine if we exceeded the instance-specific quota and if
             * so, designate it as unsolvable.
             * */
            if ((local_limit() >= 0) &&
                (user->instance->num_times >= local_limit())
               )
            {
                ret = FCS_STATE_IS_NOT_SOLVEABLE;
            }
        }
    }

    return ret;
}

int freecell_solver_user_get_next_move(
    void * user_instance,
    fcs_move_t * move
    )
{
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;
    if (user->ret == FCS_STATE_WAS_SOLVED)
    {
        int ret;

        ret = fcs_move_stack_pop(
            user->instance->solution_moves,
            move
            );

        if (ret == 0)
        {
            freecell_solver_apply_move(
                &(user->running_state),
                *move,
                user->instance->freecells_num,
                user->instance->stacks_num,
                user->instance->decks_num
                );
        }
        return ret;
    }
    else
    {
        return 1;
    }
}

char * freecell_solver_user_current_state_as_string(
    void * user_instance,
    int parseable_output,
    int canonized_order_output,
    int display_10_as_t
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    return
        freecell_solver_state_as_string(
            &(user->running_state),
            user->instance->freecells_num,
            user->instance->stacks_num,
            user->instance->decks_num,
            parseable_output,
            canonized_order_output,
            display_10_as_t
            );
}

static void user_free_resources(
    fcs_user_t * user
    )
{
    int i;
    
    for(i=0;i<user->num_instances;i++)
    {
        int ret_code = user->instances_list[i].ret;
        
        if (ret_code == FCS_STATE_WAS_SOLVED)
        {
            fcs_move_stack_destroy(user->instance->solution_moves);
            user->instance->solution_moves = NULL;
        }
        else if (ret_code == FCS_STATE_SUSPEND_PROCESS)
        {
            freecell_solver_unresume_instance(user->instances_list[i].instance);
        }

        if (ret_code != FCS_STATE_NOT_BEGAN_YET)
        {
            if (ret_code != FCS_STATE_INVALID_STATE)
            {
                freecell_solver_finish_instance(user->instances_list[i].instance);
            }
        }

        freecell_solver_free_instance(user->instances_list[i].instance);
    }

    free(user->instances_list);

    if (user->state_string_copy != NULL)
    {
        free(user->state_string_copy);
        user->state_string_copy = NULL;
    }
}

void freecell_solver_user_free(
    void * user_instance
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user_free_resources(user);

    free(user);
}

int freecell_solver_user_get_current_depth(
    void * user_instance
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    return (user->soft_thread->num_solution_states - 1);
}

void freecell_solver_user_set_solving_method(
    void * user_instance,
    int method
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->soft_thread->method = method;
}

#define set_for_all_instances(what) \
    { \
    for(i = 0 ; i < user->num_instances ; i++)        \
    {                    \
        user->instances_list[i].instance->what = what;       \
    }        \
    user->common_preset.what = what;     \
    }
    
int freecell_solver_user_set_num_freecells(
    void * user_instance,
    int freecells_num
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    if ((freecells_num < 0) || (freecells_num > MAX_NUM_FREECELLS))
    {     
        return 1;     
    }    

    set_for_all_instances(freecells_num);

    return 0;
}

int freecell_solver_user_set_num_stacks(
    void * user_instance,
    int stacks_num
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    if ((stacks_num < 0) || (stacks_num > MAX_NUM_STACKS))
    {
        return 1;
    }
    set_for_all_instances(stacks_num);

    return 0;
}

int freecell_solver_user_set_num_decks(
    void * user_instance,
    int decks_num
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    if ((decks_num < 0) || (decks_num > MAX_NUM_DECKS))
    {
        return 1;
    }
    set_for_all_instances(decks_num);

    return 0;
}


int freecell_solver_user_set_game(
    void * user_instance,
    int freecells_num,
    int stacks_num,
    int decks_num,
    int sequences_are_built_by,
    int unlimited_sequence_move,
    int empty_stacks_fill
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    if (freecell_solver_user_set_num_freecells(user_instance, freecells_num))
    {
        return 1;
    }
    if (freecell_solver_user_set_num_stacks(user_instance, stacks_num))
    {
        return 2;
    }
    if (freecell_solver_user_set_num_decks(user_instance, decks_num))
    {
        return 3;
    }
    if (freecell_solver_user_set_sequences_are_built_by_type(user_instance, sequences_are_built_by))
    {
        return 4;
    }
    if (freecell_solver_user_set_sequence_move(user_instance, unlimited_sequence_move))
    {
        return 5;
    }
    if (freecell_solver_user_set_empty_stacks_filled_by(user_instance, empty_stacks_fill))
    {
        return 6;
    }

    return 0;
}

int freecell_solver_user_get_num_times(void * user_instance)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    return user->iterations_board_started_at + user->instance->num_times - user->init_num_times;
}

int freecell_solver_user_get_limit_iterations(void * user_instance)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    return user->instance->max_num_times;
}

int freecell_solver_user_get_moves_left(void * user_instance)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;
    if (user->ret == FCS_STATE_WAS_SOLVED)
        return user->instance->solution_moves->num_moves;
    else
        return 0;
}

void freecell_solver_user_set_solution_optimization(
    void * user_instance,
    int optimize
)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->instance->optimize_solution_path = optimize;
}

char * freecell_solver_user_move_to_string(
    fcs_move_t move,
    int standard_notation
    )
{
    return freecell_solver_move_to_string(move, standard_notation);
}

char * freecell_solver_user_move_to_string_w_state(
    void * user_instance,
    fcs_move_t move,
    int standard_notation
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;
    
    return 
        freecell_solver_move_to_string_w_state(
            &(user->running_state), 
            user->instance->freecells_num, 
            user->instance->stacks_num, 
            user->instance->decks_num, 
            move, 
            standard_notation
            );
}

void freecell_solver_user_limit_depth(
    void * user_instance,
    int max_depth
)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->instance->max_depth = max_depth;
}

int freecell_solver_user_get_max_num_freecells(void)
{
    return MAX_NUM_FREECELLS;
}

int freecell_solver_user_get_max_num_stacks(void)
{
    return MAX_NUM_STACKS;
}

int freecell_solver_user_get_max_num_decks(void)
{
    return MAX_NUM_DECKS;
}


char * freecell_solver_user_get_invalid_state_error_string(
    void * user_instance,
    int print_ts
    )
{
    fcs_user_t * user;
    char string[80], card_str[10];

    user = (fcs_user_t *)user_instance;

    if (user->state_validity_ret == FCS_STATE_VALIDITY__OK)
    {
        return strdup("");
    }
    fcs_card_perl2user(user->state_validity_card, card_str, print_ts);

    if (user->state_validity_ret == FCS_STATE_VALIDITY__EMPTY_SLOT)
    {
        sprintf(string, "%s",
            "There's an empty slot in one of the stacks."
            );
    }
    else if ((user->state_validity_ret == FCS_STATE_VALIDITY__EXTRA_CARD) ||
           (user->state_validity_ret == FCS_STATE_VALIDITY__MISSING_CARD)
          )
    {
        sprintf(string, "%s%s.",
            ((user->state_validity_ret == FCS_STATE_VALIDITY__EXTRA_CARD)? "There's an extra card: " : "There's a missing card: "),
            card_str
        );
    }
    else if (user->state_validity_ret == FCS_STATE_VALIDITY__PREMATURE_END_OF_INPUT)
    {
        sprintf(string, "%s.", "Not enough input");
    }
    return strdup(string);
}

int freecell_solver_user_set_sequences_are_built_by_type(
    void * user_instance,
    int sequences_are_built_by
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    if ((sequences_are_built_by < 0) || (sequences_are_built_by > 2))
    {
        return 1;
    }
    set_for_all_instances(sequences_are_built_by)

    return 0;
}

int freecell_solver_user_set_sequence_move(
    void * user_instance,
    int unlimited_sequence_move
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    set_for_all_instances(unlimited_sequence_move);

    return 0;
}

int freecell_solver_user_set_empty_stacks_filled_by(
    void * user_instance,
    int empty_stacks_fill
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    if ((empty_stacks_fill < 0) || (empty_stacks_fill > 2))
    {
        return 1;
    }
    set_for_all_instances(empty_stacks_fill);

    return 0;
}

int freecell_solver_user_set_a_star_weight(
    void * user_instance,
    int index,
    double weight
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    if ((index < 0) || (index >= (int)(sizeof(user->soft_thread->a_star_weights)/sizeof(user->soft_thread->a_star_weights[0]))))
    {
        return 1;
    }
    if (weight < 0)
    {
        return 2;
    }

    user->soft_thread->a_star_weights[index] = weight;

    return 0;

}

static void freecell_solver_user_iter_handler_wrapper(
    void * user_instance,
    int iter_num,
    int depth,
    void * lp_instance,
    fcs_state_with_locations_t * ptr_state_with_locations,
    int parent_iter_num
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->iter_handler(
        user_instance,
        iter_num,
        depth,
        (void *)ptr_state_with_locations,
        parent_iter_num,
        user->iter_handler_context
        );

    (void)lp_instance;
    return;
}

void freecell_solver_user_set_iter_handler(
void * user_instance,
freecell_solver_user_iter_handler_t iter_handler,
void * iter_handler_context
)
{
fcs_user_t * user;

user = (fcs_user_t *)user_instance;

if (iter_handler == NULL)
{
    user->instance->debug_iter_output = 0;
}
else
{
    /* Disable it temporarily while we change the settings */
    user->instance->debug_iter_output = 0;
    user->iter_handler = iter_handler;
    user->iter_handler_context = iter_handler_context;
    user->instance->debug_iter_output_context = user;
    user->instance->debug_iter_output_func = freecell_solver_user_iter_handler_wrapper;
    user->instance->debug_iter_output = 1;
}
}

char * freecell_solver_user_iter_state_as_string(
void * user_instance,
void * ptr_state,
int parseable_output,
int canonized_order_output,
int display_10_as_t
)
{
fcs_user_t * user;

user = (fcs_user_t *)user_instance;

return
    freecell_solver_state_as_string(
        ptr_state,
        user->instance->freecells_num,
        user->instance->stacks_num,
        user->instance->decks_num,
        parseable_output,
        canonized_order_output,
        display_10_as_t
        );
}

void freecell_solver_user_set_random_seed(
void * user_instance,
int seed
)
{
fcs_user_t * user;

user = (fcs_user_t *)user_instance;

freecell_solver_rand_srand(user->soft_thread->rand_gen, (user->soft_thread->rand_seed = seed));
}

int freecell_solver_user_get_num_states_in_collection(void * user_instance)
{
fcs_user_t * user;

user = (fcs_user_t *)user_instance;

return user->instance->num_states_in_collection;
}

void freecell_solver_user_limit_num_states_in_collection(
void * user_instance,
int max_num_states
    )
{
    fcs_user_t * user;

    user = (fcs_user_t*)user_instance;

    user->instance->max_num_states_in_collection = max_num_states;
}

int freecell_solver_user_next_soft_thread(
    void * user_instance
    )
{
    fcs_user_t * user;
    freecell_solver_soft_thread_t * soft_thread;

    user = (fcs_user_t *)user_instance;

    soft_thread = freecell_solver_new_soft_thread(user->soft_thread);

    if (soft_thread == NULL)
    {
        return 1;
    }

    user->soft_thread = soft_thread;

    return 0;
}

extern void freecell_solver_user_set_soft_thread_step(
    void * user_instance,
    int num_times_step
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->soft_thread->num_times_step = num_times_step;
}

int freecell_solver_user_next_hard_thread(
    void * user_instance
    )
{
    fcs_user_t * user;
    freecell_solver_soft_thread_t * soft_thread;

    user = (fcs_user_t *)user_instance;

    soft_thread = freecell_solver_new_hard_thread(user->instance);

    if (soft_thread == NULL)
    {
        return 1;
    }

    user->soft_thread = soft_thread;

    return 0;
}

int freecell_solver_user_get_num_soft_threads_in_instance(
    void * user_instance
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    return user->instance->next_soft_thread_id;
}

void freecell_solver_user_set_calc_real_depth(
    void * user_instance,
    int calc_real_depth
)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->instance->calc_real_depth = calc_real_depth;
}

void freecell_solver_user_set_soft_thread_name(
    void * user_instance,
    char * name
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    if (user->soft_thread->name != NULL)
    {
        free(user->soft_thread->name);
    }
    user->soft_thread->name = strdup(name);    
}

int freecell_solver_user_set_hard_thread_prelude(
    void * user_instance,
    char * prelude
    )
{
    fcs_user_t * user;
    freecell_solver_hard_thread_t * hard_thread;

    user = (fcs_user_t *)user_instance;

    hard_thread = user->soft_thread->hard_thread;

    if (hard_thread->prelude_as_string != NULL)
    {
        free(hard_thread->prelude_as_string);
        hard_thread->prelude_as_string = NULL;
    }
    hard_thread->prelude_as_string = strdup(prelude);

    return 0;
}

void freecell_solver_user_recycle(
    void * user_instance
    )
{
    fcs_user_t * user;
    int i;

    user = (fcs_user_t *)user_instance;

    for(i=0;i<user->num_instances;i++)
    {
        recycle_instance(user, i);
    }
    user->current_iterations_limit = -1;
    user->iterations_board_started_at = 0;
    if (user->state_string_copy != NULL)
    {
        free(user->state_string_copy);
        user->state_string_copy = NULL;
    }
}

int freecell_solver_user_set_optimization_scan_tests_order(
    void * user_instance,
    const char * tests_order,
    char * * error_string
    )
{
    fcs_user_t * user;
    int ret;

    user = (fcs_user_t*)user_instance;

    if (user->instance->opt_tests_order.tests)
    {
        free(user->instance->opt_tests_order.tests);
        user->instance->opt_tests_order.tests = NULL;
    }

    user->instance->opt_tests_order_set = 0;

    ret = 
        freecell_solver_apply_tests_order(
            &(user->instance->opt_tests_order),
            tests_order,
            error_string
            );    

    if (!ret)
    {
        user->instance->opt_tests_order_set = 1;
    }

    return ret;
}

void freecell_solver_user_set_reparent_states(
    void * user_instance,
    int to_reparent_states
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->instance->to_reparent_states = to_reparent_states;
}

void freecell_solver_user_set_scans_synergy(
    void * user_instance,
    int synergy
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user->instance->scans_synergy = synergy;
}

int freecell_solver_user_next_instance(
    void * user_instance
    )
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;
    
    user->num_instances++;
    if (user->num_instances == user->max_num_instances)
    {
        user->max_num_instances += 10;
        user->instances_list = 
            realloc(
                user->instances_list, 
                sizeof(user->instances_list[0])*user->max_num_instances
                );
    }
    user->current_instance_idx = user->num_instances-1;
    user->instance = freecell_solver_alloc_instance();

    freecell_solver_apply_preset_by_ptr(user->instance, &(user->common_preset));

    /* 
     * Switch the soft_thread variable so it won't refer to the old
     * instance
     * */
    user->soft_thread = 
        freecell_solver_instance_get_soft_thread(
            user->instance, 0, 0
            );

    user->instances_list[user->current_instance_idx].instance = user->instance;
    user->instances_list[user->current_instance_idx].ret = user->ret = FCS_STATE_NOT_BEGAN_YET;
    user->instances_list[user->current_instance_idx].limit = -1;
    
    return 0;
}

int freecell_solver_user_reset(void * user_instance)
{
    fcs_user_t * user;

    user = (fcs_user_t *)user_instance;

    user_free_resources(user);

    user_initialize(user);

    return 0;
}