/*
 * intrface.c - instance interface functions for Freecell Solver
 *
 * Written by Shlomi Fish (shlomif@vipe.technion.ac.il), 2000-2001
 *
 * This file is in the public domain (it's uncopyrighted).
 */

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <math.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define NUM_TIMES_STEP 50

#include "fcs_config.h"

/* So the FCS_STATE_STORAGE macros would be defined */
#if FCS_STATE_STORAGE==FCS_STATE_STORAGE_LIBREDBLACK_TREE
#include <search.h>
#endif

#include "state.h"
#include "card.h"
#include "fcs_dm.h"
#include "fcs.h"

#include "fcs_isa.h"

#include "caas.h"

#include "preset.h"

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

/*
    General use of this interface:
    1. freecell_solver_alloc_instance()
    2. Set the parameters of the game
    3. If you wish to revert, go to step #11.
    4. freecell_solver_init_instance()
    5. Call freecell_solver_solve_instance() with the initial board.
    6. If it returns FCS_STATE_SUSPEND_PROCESS and you wish to proceed,
       then increase the iteration limit and call
       freecell_solver_resume_instance().
    7. Repeat Step #6 zero or more times.
    8. If the last call to solve_instance() or resume_instance() returned
       FCS_STATE_SUSPEND_PROCESS then call
       freecell_solver_unresume_instance().
    9. If the solving was successful you can use the move stacks or the
       intermediate stacks. (Just don't destory them in any way).
    10. Call freecell_solver_finish_instance().
    11. Call freecell_solver_free_instance().

    The library functions inside lib.c (a.k.a fcs_user()) give an
    easier approach for embedding Freecell Solver into your library. The
    intent of this comment is to document the code, rather than to be
    a guideline for the user.
*/

#if 0
static const double freecell_solver_a_star_default_weights[5] = {0.5,0,0.5,0,0};
#else
static const double freecell_solver_a_star_default_weights[5] = {0.5,0,0.3,0,0.2};
#endif







static void freecell_solver_initialize_bfs_queue(freecell_solver_soft_thread_t * soft_thread)
{
    /* Initialize the BFS queue. We have one dummy element at the beginning
       in order to make operations simpler. */
    soft_thread->bfs_queue = (fcs_states_linked_list_item_t*)malloc(sizeof(fcs_states_linked_list_item_t));
    soft_thread->bfs_queue->next = (fcs_states_linked_list_item_t*)malloc(sizeof(fcs_states_linked_list_item_t));
    soft_thread->bfs_queue_last_item = soft_thread->bfs_queue->next;
    soft_thread->bfs_queue_last_item->next = NULL;
}

static void foreach_soft_thread(
    freecell_solver_instance_t * instance,
    void (*soft_thread_callback)(
        freecell_solver_soft_thread_t * soft_thread,
        void * context
        ),
    void * context
    )

{
    int ht_idx, st_idx;
    freecell_solver_hard_thread_t * hard_thread;
    int num_soft_threads;
    freecell_solver_soft_thread_t * * ht_soft_threads;
    for(ht_idx = 0 ; ht_idx<instance->num_hard_threads; ht_idx++)
    {
        hard_thread = instance->hard_threads[ht_idx];
        num_soft_threads = hard_thread->num_soft_threads;
        ht_soft_threads = hard_thread->soft_threads;
        for(st_idx = 0 ; st_idx < num_soft_threads; st_idx++)
        {
            soft_thread_callback(ht_soft_threads[st_idx], context);
        }
    }

    if (instance->optimization_thread)
    {
        soft_thread_callback(instance->optimization_thread->soft_threads[0], context);
    }
}



static void soft_thread_clean_soft_dfs(
    freecell_solver_soft_thread_t * soft_thread,
    void * context
    )
{
    int num_solution_states;
    int dfs_max_depth;
    fcs_soft_dfs_stack_item_t * soft_dfs_info, * info_ptr;
    /* Check if a Soft-DFS-type scan was called in the first place */
    if (soft_thread->soft_dfs_info == NULL)
    {
        /* If not - do nothing */
        return;
    }

    (void)context;
    soft_dfs_info = soft_thread->soft_dfs_info;
    num_solution_states = soft_thread->num_solution_states;
    dfs_max_depth = soft_thread->dfs_max_depth;
    /* De-allocate the Soft-DFS specific stacks */
    {
        int depth;
        info_ptr = soft_dfs_info;
        for(depth=0;depth<num_solution_states-1;depth++)
        {
            free(info_ptr->derived_states_list.states);
            free(info_ptr->derived_states_random_indexes);
            info_ptr++;
        }
        for(;depth<dfs_max_depth;depth++)
        {
            if (info_ptr->derived_states_list.max_num_states)
            {
                free(info_ptr->derived_states_list.states);
                free(info_ptr->derived_states_random_indexes);
            }
            info_ptr++;
        }
        
        free(soft_dfs_info);

        soft_thread->soft_dfs_info = NULL;

        soft_thread->dfs_max_depth = 0;

    }
}

static void clean_soft_dfs(
        freecell_solver_instance_t * instance
        )
{
    foreach_soft_thread(instance, soft_thread_clean_soft_dfs, NULL);
}

static freecell_solver_soft_thread_t * alloc_soft_thread(
        freecell_solver_hard_thread_t * hard_thread
        )
{
    freecell_solver_soft_thread_t * soft_thread;
    unsigned int a;

    /* Make sure we are not exceeding the maximal number of soft threads
     * for an instance. */
    if (hard_thread->instance->next_soft_thread_id == MAX_NUM_SCANS)
    {
        return NULL;
    }

    soft_thread = malloc(sizeof(freecell_solver_soft_thread_t));

    soft_thread->hard_thread = hard_thread;

    soft_thread->id = (hard_thread->instance->next_soft_thread_id)++;

    soft_thread->dfs_max_depth = 0;

    soft_thread->tests_order.num = 0;
    soft_thread->tests_order.tests = NULL;
    soft_thread->tests_order.max_num = 0;
    

    /* Initialize all the Soft-DFS stacks to NULL */
    soft_thread->soft_dfs_info = NULL;

    /* The default solving method */
    soft_thread->method = FCS_METHOD_SOFT_DFS;

    soft_thread->orig_method = FCS_METHOD_NONE;

    freecell_solver_initialize_bfs_queue(soft_thread);

    /* Initialize the priotity queue of the A* scan */
    soft_thread->a_star_pqueue = malloc(sizeof(PQUEUE));
    freecell_solver_PQueueInitialise(
        soft_thread->a_star_pqueue,
        1024
        );

    /* Set the default A* weigths */
    for(a=0;a<(sizeof(soft_thread->a_star_weights)/sizeof(soft_thread->a_star_weights[0]));a++)
    {
        soft_thread->a_star_weights[a] = freecell_solver_a_star_default_weights[a];
    }

    soft_thread->rand_gen = freecell_solver_rand_alloc(soft_thread->rand_seed = 24);

    soft_thread->initialized = 0;

    soft_thread->num_times_step = NUM_TIMES_STEP;

#if 0
    {
        char * no_use;
        freecell_solver_apply_tests_order(soft_thread, "[01][23456789]", &no_use);
    }
#else
    soft_thread->tests_order.num = soft_thread->hard_thread->instance->instance_tests_order.num;
    soft_thread->tests_order.tests = 
        malloc(sizeof(soft_thread->tests_order.tests[0]) * soft_thread->tests_order.num);
    memcpy(soft_thread->tests_order.tests, 
        soft_thread->hard_thread->instance->instance_tests_order.tests, 
        sizeof(soft_thread->tests_order.tests[0]) * soft_thread->tests_order.num
        );
    soft_thread->tests_order.max_num = soft_thread->tests_order.num;
#endif

    soft_thread->is_finished = 0;

    soft_thread->name = NULL;

    return soft_thread;
}

static freecell_solver_hard_thread_t * alloc_hard_thread(
        freecell_solver_instance_t * instance
        )
{
    freecell_solver_hard_thread_t * hard_thread;

    /* Make sure we are not exceeding the maximal number of soft threads
     * for an instance. */
    if (instance->next_soft_thread_id == MAX_NUM_SCANS)
    {
        return NULL;
    }

    hard_thread = malloc(sizeof(freecell_solver_hard_thread_t));

    hard_thread->instance = instance;

    hard_thread->num_times = 0;

    hard_thread->num_soft_threads = 1;

    hard_thread->soft_threads =
        malloc(sizeof(hard_thread->soft_threads[0]) *
               hard_thread->num_soft_threads
        );

    hard_thread->soft_threads[0] = alloc_soft_thread(hard_thread);

    /* Set a limit on the Hard-Thread's scan. */
    hard_thread->num_times_step = NUM_TIMES_STEP;

    hard_thread->ht_max_num_times = hard_thread->num_times_step;

    hard_thread->max_num_times = -1;

    hard_thread->num_soft_threads_finished = 0;

#ifdef INDIRECT_STACK_STATES
    hard_thread->stacks_allocator = 
        freecell_solver_compact_allocator_new();
#endif
    hard_thread->move_stacks_allocator =
        freecell_solver_compact_allocator_new();

    fcs_move_stack_alloc_into_var(hard_thread->reusable_move_stack);

    hard_thread->prelude_as_string = NULL;
    hard_thread->prelude = NULL;
    hard_thread->prelude_num_items = 0;
    hard_thread->prelude_idx = 0;

    return hard_thread;
}


/*
    This function allocates a Freecell Solver instance struct and set the
    default values in it. After the call to this function, the program can
    set parameters in it which are different from the default.

    Afterwards freecell_solver_init_instance() should be called in order
    to really prepare it for solving.
  */
freecell_solver_instance_t * freecell_solver_alloc_instance(void)
{
    freecell_solver_instance_t * instance;

    instance = malloc(sizeof(freecell_solver_instance_t));

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INDIRECT)
    instance->num_indirect_prev_states = 0;
    instance->max_num_indirect_prev_states = 0;
#endif

    instance->num_times = 0;

    instance->num_states_in_collection = 0;

    instance->max_num_times = -1;
    instance->max_depth = -1;
    instance->max_num_states_in_collection = -1;

    instance->instance_tests_order.num = 0;
    instance->instance_tests_order.tests = NULL;
    instance->instance_tests_order.max_num = 0;

    instance->opt_tests_order_set = 0;

    instance->opt_tests_order.num = 0;
    instance->opt_tests_order.tests = NULL;
    instance->opt_tests_order.max_num = 0;
    
    

#ifdef FCS_WITH_TALONS
    instance->talon_type = FCS_TALON_NONE;
#endif

    instance->num_hard_threads = 0;

    freecell_solver_apply_preset_by_name(instance, "freecell");

    /****************************************/

    instance->debug_iter_output = 0;

    instance->next_soft_thread_id = 0;

    instance->num_hard_threads = 1;

    instance->hard_threads = malloc(sizeof(instance->hard_threads[0]) * instance->num_hard_threads);

    instance->hard_threads[0] = alloc_hard_thread(instance);

    instance->solution_moves = NULL;

    instance->optimize_solution_path = 0;

#ifdef FCS_WITH_MHASH
    instance->mhash_type = MHASH_MD5;
#endif

    instance->optimization_thread = NULL;

    instance->num_hard_threads_finished = 0;

    instance->calc_real_depth = 0;

    instance->to_reparent_states = 0;

    /* Make the 1 the default, because otherwise scans will not cooperate
     * with one another. */
    instance->scans_synergy = 1;

    return instance;
}





static void free_bfs_queue(freecell_solver_soft_thread_t * soft_thread)
{
    /* Free the BFS linked list */
    fcs_states_linked_list_item_t * item, * next_item;
    item = soft_thread->bfs_queue;
    while (item != NULL)
    {
        next_item = item->next;
        free(item);
        item = next_item;
    }
}

static void free_instance_soft_thread_callback(freecell_solver_soft_thread_t * soft_thread, void * context)
{
    (void)context;
    free_bfs_queue(soft_thread);
    freecell_solver_rand_free(soft_thread->rand_gen);

    freecell_solver_PQueueFree(soft_thread->a_star_pqueue);
    free(soft_thread->a_star_pqueue);

    free(soft_thread->tests_order.tests);

    if (soft_thread->name != NULL)
    {
        free(soft_thread->name);
    }
    /* The data-structure itself was allocated */
    free(soft_thread);
}

static void free_instance_hard_thread_callback(freecell_solver_hard_thread_t * hard_thread)
{
    if (hard_thread->prelude_as_string)
    {
        free (hard_thread->prelude_as_string);
    }
    if (hard_thread->prelude)
    {
        free (hard_thread->prelude);
    }
    fcs_move_stack_destroy(hard_thread->reusable_move_stack);
                
    free(hard_thread->soft_threads);

    if (hard_thread->move_stacks_allocator)
    {
        freecell_solver_compact_allocator_finish(hard_thread->move_stacks_allocator);
    }
#ifdef INDIRECT_STACK_STATES
    if (hard_thread->stacks_allocator)
    {
        freecell_solver_compact_allocator_finish(hard_thread->stacks_allocator);
    }
#endif
    free(hard_thread);    
}

/*
    This function is the last function that should be called in the
    sequence of operations on instance, and it is meant for de-allocating
    whatever memory was allocated by alloc_instance().
  */
void freecell_solver_free_instance(freecell_solver_instance_t * instance)
{
    int ht_idx;

    foreach_soft_thread(instance, free_instance_soft_thread_callback, NULL);

    for(ht_idx=0; ht_idx < instance->num_hard_threads; ht_idx++)
    {
        free_instance_hard_thread_callback(instance->hard_threads[ht_idx]);
    }
    free(instance->hard_threads);
    if (instance->optimization_thread)
    {
        free_instance_hard_thread_callback(instance->optimization_thread);
    }

    free(instance->instance_tests_order.tests);

    if (instance->opt_tests_order_set)
    {
        free(instance->opt_tests_order.tests);
    }

    free(instance);
}


static void normalize_a_star_weights(
    freecell_solver_soft_thread_t * soft_thread,
    void * context
    )
{
    /* Normalize the A* Weights, so the sum of all of them would be 1. */
    double sum;
    unsigned int a;
    sum = 0;
    for(a=0;a<(sizeof(soft_thread->a_star_weights)/sizeof(soft_thread->a_star_weights[0]));a++)
    {
        if (soft_thread->a_star_weights[a] < 0)
        {
            soft_thread->a_star_weights[a] = freecell_solver_a_star_default_weights[a];
        }
        sum += soft_thread->a_star_weights[a];
    }
    if (sum == 0)
    {
        sum = 1;
    }
    for(a=0;a<(sizeof(soft_thread->a_star_weights)/sizeof(soft_thread->a_star_weights[0]));a++)
    {
        soft_thread->a_star_weights[a] /= sum;
    }
    (void)context;
}

static void accumulate_tests_order(
    freecell_solver_soft_thread_t * soft_thread,
    void * context
    )
{
    int * tests_order = (int *)context;
    int a;
    for(a=0;a<soft_thread->tests_order.num;a++)
    {
        *tests_order |= (1 << (soft_thread->tests_order.tests[a] & FCS_TEST_ORDER_NO_FLAGS_MASK));
    }
}

static void determine_scan_completeness(
    freecell_solver_soft_thread_t * soft_thread,
    void * context
    )
{
    int global_tests_order = *(int *)context;
    int tests_order = 0;
    int a;
    for(a=0;a<soft_thread->tests_order.num;a++)
    {
        tests_order |= (1 << (soft_thread->tests_order.tests[a] & FCS_TEST_ORDER_NO_FLAGS_MASK));
    }
    soft_thread->is_a_complete_scan = (tests_order == global_tests_order);
}

enum FCS_COMPILE_PRELUDE_ERRORS_T
{
    FCS_COMPILE_PRELUDE_OK,
    FCS_COMPILE_PRELUDE_NO_AT_SIGN,
    FCS_COMPILE_PRELUDE_UNKNOWN_SCAN_ID
};

static int compile_prelude(
    freecell_solver_hard_thread_t * hard_thread
    )
{
    char * p_quota, * p_scan, * p;
    char * string;
    int last_one = 0;
    int num_items = 0;
    int max_num_items = 16;
    fcs_prelude_item_t * prelude;
    int st_idx;

    prelude = malloc(sizeof(prelude[0]) * max_num_items);
    string = hard_thread->prelude_as_string;

    p = string;
    
    while (! last_one)
    {
        p_quota = p;
        while((*p) && isdigit(*p))
        {
            p++;
        }
        if (*p != '@')
        {
            free(prelude);
            return FCS_COMPILE_PRELUDE_NO_AT_SIGN;
        }
        *p = '\0';
        p++;
        p_scan = p;
        while((*p) && ((*p) != ','))
        {
            p++;
        }
        if ((*p) == '\0')
        {
            last_one = 1;
        }
        *p = '\0';
        p++;
        
        for(st_idx = 0; st_idx < hard_thread->num_soft_threads ; st_idx++)
        {
            if (!strcmp(hard_thread->soft_threads[st_idx]->name, p_scan))
            {
                break;
            }
        }
        if (st_idx == hard_thread->num_soft_threads)
        {
            free(prelude);
            return FCS_COMPILE_PRELUDE_UNKNOWN_SCAN_ID;
        }
        prelude[num_items].scan_idx = st_idx;
        prelude[num_items].quota = atoi(p_quota);
        num_items++;
        if (num_items == max_num_items)
        {
            max_num_items += 16;
            prelude = realloc(prelude, sizeof(prelude[0]) * max_num_items);
        }
    }

    hard_thread->prelude = prelude;
    hard_thread->prelude_num_items = num_items;
    hard_thread->prelude_idx = 0;

    return FCS_COMPILE_PRELUDE_OK;    
}


void freecell_solver_init_instance(freecell_solver_instance_t * instance)
{
    int ht_idx;
    freecell_solver_hard_thread_t * hard_thread;
#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INDIRECT)
    instance->num_prev_states_margin = 0;

    instance->max_num_indirect_prev_states = PREV_STATES_GROW_BY;

    instance->indirect_prev_states = (fcs_state_with_locations_t * *)malloc(sizeof(fcs_state_with_locations_t *) * instance->max_num_indirect_prev_states);
#endif

    /* Initialize the state packs */
    for(ht_idx=0;ht_idx<instance->num_hard_threads;ht_idx++)
    {
        hard_thread = instance->hard_threads[ht_idx];
        if (hard_thread->prelude_as_string)
        {
            compile_prelude(hard_thread);
        }
        hard_thread->num_times_left_for_soft_thread =
            hard_thread->soft_threads[0]->num_times_step;
        freecell_solver_state_ia_init(hard_thread);
    }

    /* Normalize the A* Weights, so the sum of all of them would be 1. */
    foreach_soft_thread(instance, normalize_a_star_weights, NULL);

    {
        int total_tests = 0;
        foreach_soft_thread(instance, accumulate_tests_order, &total_tests);
        foreach_soft_thread(instance, determine_scan_completeness, &total_tests);
        if (instance->opt_tests_order_set == 0)
        {
            /*
             *
             * What this code does is convert the bit map of total_tests
             * to a valid tests order.
             * 
             * */
            int bit_idx, num_tests = 0;
            int * tests = malloc(sizeof(total_tests)*8*sizeof(tests[0]));
                        
            for(bit_idx=0; total_tests != 0; bit_idx++, total_tests >>= 1)
            {
                if ((total_tests & 0x1) != 0)
                {
                    tests[num_tests++] = bit_idx;
                }
            }
            tests = realloc(tests, num_tests*sizeof(tests[0]));
            instance->opt_tests_order.tests = tests;
            instance->opt_tests_order.num =
                instance->opt_tests_order.max_num =
                num_tests;
            instance->opt_tests_order_set = 1;
        }
    }


}




/* These are all stack comparison functions to be used for the stacks
   cache when using INDIRECT_STACK_STATES
*/
#if defined(INDIRECT_STACK_STATES)

extern int freecell_solver_stack_compare_for_comparison(const void * v_s1, const void * v_s2);

#if ((FCS_STACK_STORAGE != FCS_STACK_STORAGE_GLIB_TREE) && (FCS_STACK_STORAGE != FCS_STACK_STORAGE_GLIB_HASH))
static int fcs_stack_compare_for_comparison_with_context(
    const void * v_s1,
    const void * v_s2,
#if (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBREDBLACK_TREE)
    const
#endif
    void * context

    )
{
    (void)context;
    return freecell_solver_stack_compare_for_comparison(v_s1, v_s2);
}
#endif





#if (FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_HASH)
/* A hash calculation function for use in glib's hash */
static guint freecell_solver_glib_hash_stack_hash_function (
    gconstpointer key
    )
{
    guint hash_value_int;
    /* Calculate the hash value for the stack */
    /* This hash function was ripped from the Perl source code.
     * (It is not derived work however). */
    const char * s_ptr = (char*)key;
    const char * s_end = s_ptr+fcs_standalone_stack_len((fcs_card_t *)key)+1;
    hash_value_int = 0;
    while (s_ptr < s_end)
    {
        hash_value_int += (hash_value_int << 5) + *(s_ptr++);
    }
    hash_value_int += (hash_value_int >> 5);

}





static gint freecell_solver_glib_hash_stack_compare (
    gconstpointer a,
    gconstpointer b
)
{
    return !(fcs_stack_compare_for_comparison(a,b));
}
#endif /* (FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_HASH) */





#endif /* defined(INDIRECT_STACK_STATES) */





#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_GLIB_HASH)
/*
 * This hash function is defined in caas.c
 *
 * */
extern guint freecell_solver_hash_function(gconstpointer key);
#endif

/*
 * This function traces the solution from the final state down
 * to the initial state
 * */
static void trace_solution(
    freecell_solver_instance_t * instance
    )
{
    /*
        Trace the solution.
    */
    fcs_state_with_locations_t * s1;
    fcs_move_stack_t * solution_moves;
    int move_idx;
    fcs_move_stack_t * stack;
    fcs_move_t * moves;

    if (instance->solution_moves != NULL)
    {
        fcs_move_stack_destroy(instance->solution_moves);
        instance->solution_moves = NULL;
    }

    fcs_move_stack_alloc_into_var(solution_moves);
    instance->solution_moves = solution_moves;

    s1 = instance->final_state;

    /* Retrace the step from the current state to its parents */
    while (s1->parent != NULL)
    {
        /* Mark the state as part of the non-optimized solution */
        s1->visited |= FCS_VISITED_IN_SOLUTION_PATH;
        /* Duplicate the move stack */
        {
            stack = s1->moves_to_parent;
            moves = stack->moves;
            for(move_idx=stack->num_moves-1;move_idx>=0;move_idx--)
            {
                fcs_move_stack_push(solution_moves, moves[move_idx]);
            }            
        }
        /* Duplicate the state to a freshly malloced memory */

        /* Move to the parent state */
        s1 = s1->parent;
    }
    /* There's one more state than there are move stacks */
    s1->visited |= FCS_VISITED_IN_SOLUTION_PATH;
}


static fcs_tests_order_t tests_order_dup(fcs_tests_order_t * orig)
{
    fcs_tests_order_t ret;

    ret.max_num = ret.num = orig->num;
    ret.tests = malloc(sizeof(ret.tests[0]) * ret.num);
    memcpy(ret.tests, orig->tests, sizeof(ret.tests[0]) * ret.num);
    
    return ret;
}

/*
    This function optimizes the solution path using a BFS scan on the
    states in the solution path.
*/
static int freecell_solver_optimize_solution(
    freecell_solver_instance_t * instance
    )
{
    freecell_solver_hard_thread_t * optimization_thread;
    freecell_solver_soft_thread_t * soft_thread;
    
    optimization_thread = alloc_hard_thread(instance);
    instance->optimization_thread = optimization_thread;

    soft_thread = optimization_thread->soft_threads[0];

    if (instance->opt_tests_order_set)
    {
        if (soft_thread->tests_order.tests != NULL)
        {
            free(soft_thread->tests_order.tests);
        }
        
        soft_thread->tests_order = 
            tests_order_dup(&(instance->opt_tests_order));
    }
    
    soft_thread->method = FCS_METHOD_OPTIMIZE;

    soft_thread->is_a_complete_scan = 1;

    /* Initialize the optimization hard-thread and soft-thread */
    optimization_thread->num_times_left_for_soft_thread = 1000000;
    freecell_solver_state_ia_init(optimization_thread);

    /* Instruct the optimization hard thread to run indefinitely AFA it
     * is concerned */
    optimization_thread->max_num_times = -1;
    optimization_thread->ht_max_num_times = -1;

    return 
        freecell_solver_a_star_or_bfs_do_solve_or_resume(
            optimization_thread->soft_threads[0],
            instance->state_copy_ptr,
            0
            );

}


extern void freecell_solver_cache_talon(
    freecell_solver_instance_t * instance,
    fcs_state_with_locations_t * new_state
    );

/*
    This function starts the solution process _for the first time_. If one
    wishes to proceed after the iterations limit was reached, one should
    use freecell_solver_resume_instance.

  */
int freecell_solver_solve_instance(
    freecell_solver_instance_t * instance,
    fcs_state_with_locations_t * init_state
    )
{
    fcs_state_with_locations_t * state_copy_ptr;

    /* Allocate the first state and initialize it to init_state */
    fcs_state_ia_alloc_into_var(state_copy_ptr, instance->hard_threads[0]);

    fcs_duplicate_state(*state_copy_ptr, *init_state);

    {
        int a;
        for(a=0;a<instance->stacks_num;a++)
        {
            fcs_copy_stack(*state_copy_ptr, a, instance->hard_threads[0]->indirect_stacks_buffer);
        }
    }

    /* Initialize the state to be a base state for the game tree */
    state_copy_ptr->depth = 0;
    state_copy_ptr->moves_to_parent = NULL;
    state_copy_ptr->visited = 0;
    state_copy_ptr->parent = NULL;
    memset(&(state_copy_ptr->scan_visited), '\0', sizeof(state_copy_ptr->scan_visited));

    instance->state_copy_ptr = state_copy_ptr;

    /* Initialize the data structure that will manage the state collection */
#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBREDBLACK_TREE)
    instance->tree = rbinit(freecell_solver_state_compare_with_context, NULL);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBAVL_AVL_TREE)
    instance->tree = avl_create(freecell_solver_state_compare_with_context, NULL);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBAVL_REDBLACK_TREE)
    instance->tree = rb_create(freecell_solver_state_compare_with_context, NULL);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_GLIB_TREE)
    instance->tree = g_tree_new(freecell_solver_state_compare);
#endif

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_GLIB_HASH)
    instance->hash = g_hash_table_new(
        freecell_solver_hash_function,
        freecell_solver_state_compare_equal
        );
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INTERNAL_HASH)
    instance->hash = freecell_solver_hash_init(
            2048,
            freecell_solver_state_compare_with_context,
            NULL
       );
#endif

    /****************************************************/

#ifdef INDIRECT_STACK_STATES
    /* Initialize the data structure that will manage the stack
       collection */
#if FCS_STACK_STORAGE == FCS_STACK_STORAGE_INTERNAL_HASH
    instance->stacks_hash = freecell_solver_hash_init(
            2048,
            fcs_stack_compare_for_comparison_with_context,
            NULL
        );
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBAVL_AVL_TREE)
    instance->stacks_tree = avl_create(
            fcs_stack_compare_for_comparison_with_context,
            NULL
            );
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBAVL_REDBLACK_TREE)
    instance->stacks_tree = rb_create(
            fcs_stack_compare_for_comparison_with_context,
            NULL
            );
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBREDBLACK_TREE)
    instance->stacks_tree = rbinit(
        fcs_stack_compare_for_comparison_with_context,
        NULL
        );
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_TREE)
    instance->stacks_tree = g_tree_new(fcs_stack_compare_for_comparison);
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_HASH)
    instance->stacks_hash = g_hash_table_new(
        freecell_solver_glib_hash_stack_hash_function,
        freecell_solver_glib_hash_stack_compare
        );
#endif
#endif

    /***********************************************/

#ifdef FCS_WITH_TALONS
    /* Initialize the Talon's Cache */
    if (instance->talon_type == FCS_TALON_KLONDIKE)
    {
        instance->talons_hash = freecell_solver_hash_init(
            512,
            fcs_talon_compare_with_context,
            NULL
            );

        freecell_solver_cache_talon(instance, instance->state_copy_ptr);
    }
#endif

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_DB_FILE)
    /* Not working - ignore */
    db_open(
        NULL,
        DB_BTREE,
        O_CREAT|O_RDWR,
        0777,
        NULL,
        NULL,
        &(instance->db)
        );
#endif

    {
        fcs_state_with_locations_t * no_use;

        freecell_solver_check_and_add_state(
            instance->hard_threads[0]->soft_threads[0],
            state_copy_ptr,
            &no_use
            );

    }

    instance->ht_idx = 0;
    {
        int ht_idx;
        for(ht_idx=0; ht_idx < instance->num_hard_threads ; ht_idx++)
        {
            freecell_solver_hard_thread_t * hard_thread;
            hard_thread = instance->hard_threads[ht_idx];
            
            if (hard_thread->prelude != NULL)
            {
                hard_thread->prelude_idx = 0;
                hard_thread->st_idx = hard_thread->prelude[hard_thread->prelude_idx].scan_idx;
                hard_thread->num_times_left_for_soft_thread = hard_thread->prelude[hard_thread->prelude_idx].quota;
                hard_thread->prelude_idx++;
            }
            else
            {
                hard_thread->st_idx = 0;
            }
        }
    }

    return freecell_solver_resume_instance(instance);
}


static int run_hard_thread(freecell_solver_hard_thread_t * hard_thread)
{
    freecell_solver_soft_thread_t * soft_thread;
    int num_times_started_at;
    int ret;
    freecell_solver_instance_t * instance = hard_thread->instance;
    /* 
     * Again, making sure that not all of the soft_threads in this 
     * hard thread are finished.
     * */

    ret = FCS_STATE_SUSPEND_PROCESS;
    while(hard_thread->num_soft_threads_finished < hard_thread->num_soft_threads)
    {
        soft_thread = hard_thread->soft_threads[hard_thread->st_idx];
        /*
         * Move to the next thread if it's already finished
         * */
        if (soft_thread->is_finished)
        {
            /*
             * Hmmpf - duplicate code. That's ANSI C for you.
             * A macro, anyone?
             * */

#define switch_to_next_soft_thread() \
            /*      \
             * Switch to the next soft thread in the hard thread,   \
             * since we are going to call continue and this is     \
             * a while loop     \
                             * */    \
            if ((hard_thread->prelude != NULL) &&    \
                (hard_thread->prelude_idx < hard_thread->prelude_num_items))   \
            {      \
                hard_thread->st_idx = hard_thread->prelude[hard_thread->prelude_idx].scan_idx; \
                hard_thread->num_times_left_for_soft_thread = hard_thread->prelude[hard_thread->prelude_idx].quota; \
                hard_thread->prelude_idx++; \
            }    \
            else       \
            {       \
                hard_thread->st_idx++;      \
                if (hard_thread->st_idx == hard_thread->num_soft_threads)     \
                {       \
                    hard_thread->st_idx = 0;    \
                }      \
                hard_thread->num_times_left_for_soft_thread = hard_thread->soft_threads[hard_thread->st_idx]->num_times_step;  \
            }



            switch_to_next_soft_thread();
            
            continue;
        }

        /*
         * Keep record of the number of iterations since this 
         * thread started.
         * */
        num_times_started_at = hard_thread->num_times;
        /*
         * Calculate a soft thread-wise limit for this hard
         * thread to run.
         * */
        hard_thread->max_num_times = hard_thread->num_times + hard_thread->num_times_left_for_soft_thread;



        /* 
         * Call the resume or solving function that is specific 
         * to each scan
         * 
         * This switch-like construct calls for declaring a class
         * that will abstract a scan. But it's not critical since
         * I don't support user-defined scans.
         * */
        switch(soft_thread->method)
        {
            case FCS_METHOD_HARD_DFS:
                
            if (! soft_thread->initialized)
            {
                ret = freecell_solver_hard_dfs_solve_for_state(
                    soft_thread,
                    instance->state_copy_ptr,
                    0,
                    0);

                soft_thread->initialized = 1;
            }
            else
            {
                ret = freecell_solver_hard_dfs_resume_solution(soft_thread, 0);
            }
            break;

            case FCS_METHOD_SOFT_DFS:

            if (! soft_thread->initialized)
            {
                ret = 
                    freecell_solver_soft_dfs_or_random_dfs_do_solve_or_resume(
                        soft_thread,
                        instance->state_copy_ptr,
                        0,
                        0
                        );
                soft_thread->initialized = 1;
            }
            else
            {
                ret = 
                    freecell_solver_soft_dfs_or_random_dfs_do_solve_or_resume(
                        soft_thread,
                        NULL,
                        1,
                        0
                        );
            }
            break;

            case FCS_METHOD_RANDOM_DFS:

            if (! soft_thread->initialized)
            {
                ret = 
                    freecell_solver_soft_dfs_or_random_dfs_do_solve_or_resume(
                        soft_thread,
                        instance->state_copy_ptr,
                        0,
                        1
                        );

                soft_thread->initialized = 1;
            }
            else
            {
                ret = 
                    freecell_solver_soft_dfs_or_random_dfs_do_solve_or_resume(
                        soft_thread,
                        NULL,
                        1,
                        1
                        );
            }
            break;

            case FCS_METHOD_BFS:
            case FCS_METHOD_A_STAR:
            case FCS_METHOD_OPTIMIZE:
                        if (! soft_thread->initialized)
            {
                if (soft_thread->method == FCS_METHOD_A_STAR)
                {
                    freecell_solver_a_star_initialize_rater(
                        soft_thread,
                        instance->state_copy_ptr
                        );
                }

                ret = freecell_solver_a_star_or_bfs_do_solve_or_resume(
                    soft_thread,
                    instance->state_copy_ptr,
                    0
                );

                soft_thread->initialized = 1;
            }
            else
            {
                ret =
                    freecell_solver_a_star_or_bfs_do_solve_or_resume(
                        soft_thread,
                        soft_thread->first_state_to_check,
                        1
                        );
            }
            break;
            
            default:
            ret = FCS_STATE_IS_NOT_SOLVEABLE;
            break;
        }
        /*
         * Determine how much iterations we still have left
         * */
        hard_thread->num_times_left_for_soft_thread -= (hard_thread->num_times - num_times_started_at);

        /*
         * I use <= instead of == because it is possible that 
         * there will be a few more iterations than what this
         * thread was allocated, due to the fact that
         * check_and_add_state is only called by the test 
         * functions.
         *
         * It's a kludge, but it works.
         * */
        if (hard_thread->num_times_left_for_soft_thread <= 0)
        {
            switch_to_next_soft_thread();
            /* 
             * Reset num_times_left_for_soft_thread 
             * */
            
        }

        /*
         * It this thread indicated that the scan was finished,
         * disable the thread or even stop searching altogether.
         * */
        if (ret == FCS_STATE_IS_NOT_SOLVEABLE)
        {
            soft_thread->is_finished = 1;
            hard_thread->num_soft_threads_finished++;
            if (hard_thread->num_soft_threads_finished == hard_thread->num_soft_threads)
            {
                instance->num_hard_threads_finished++;
            }
            /*
             * Check if this thread is a complete scan and if so,
             * terminate the search
             * */
            if (soft_thread->is_a_complete_scan)
            {
                return FCS_STATE_IS_NOT_SOLVEABLE;
            }
            else
            {
                /* 
                 * Else, make sure ret is something more sensible
                 * */
                ret = FCS_STATE_SUSPEND_PROCESS;
            }
        }

        if ((ret == FCS_STATE_WAS_SOLVED) ||
            (
                (ret == FCS_STATE_SUSPEND_PROCESS) &&
                /* There's a limit to the scan only 
                 * if max_num_times is greater than 0 */
                (
                    (
                        (instance->max_num_times > 0) &&
                        (instance->num_times >= instance->max_num_times)
                    ) ||
                    (
                        (instance->max_num_states_in_collection > 0) &&
                        (instance->num_states_in_collection >= instance->max_num_states_in_collection)
                        
                    )
                )
            )
           )
        {
            return ret;
        }
        else if ((ret == FCS_STATE_SUSPEND_PROCESS) &&
            (hard_thread->num_times >= hard_thread->ht_max_num_times))
        {
            hard_thread->ht_max_num_times += hard_thread->num_times_step;
            break;
        }
    }

    return ret;
}


/* Resume a solution process that was stopped in the middle */
int freecell_solver_resume_instance(
    freecell_solver_instance_t * instance
    )
{
    int ret = FCS_STATE_SUSPEND_PROCESS;
    freecell_solver_hard_thread_t * hard_thread;

    /*
     * If the optimization thread is defined, it means we are in the 
     * optimization phase of the total scan. In that case, just call
     * its scanning function.
     *
     * Else, proceed with the normal total scan.
     * */
    if (instance->optimization_thread)
    {
        ret =
            freecell_solver_a_star_or_bfs_do_solve_or_resume(
                instance->optimization_thread->soft_threads[0],
                instance->optimization_thread->soft_threads[0]->first_state_to_check,
                1
                );
    }
    else
    {
        /*
         * instance->num_hard_threads_finished signals to us that
         * all the incomplete soft threads terminated. It is necessary
         * in case the scan only contains incomplete threads.
         *
         * I.e: 01235 and 01246, where no thread contains all tests.
         * */
        while(instance->num_hard_threads_finished < instance->num_hard_threads)
        {
            /*
             * A loop on the hard threads.
             * Note that we do not initialize instance->ht_idx because:
             * 1. It is initialized before the first call to this function.
             * 2. It is reset to zero below.
             * */
            for(;
                instance->ht_idx < instance->num_hard_threads ;
                    instance->ht_idx++)
            {
                hard_thread = instance->hard_threads[instance->ht_idx];

                ret = run_hard_thread(hard_thread);
                if ((ret == FCS_STATE_IS_NOT_SOLVEABLE) ||
                    (ret == FCS_STATE_WAS_SOLVED) ||
                    (
                        (ret == FCS_STATE_SUSPEND_PROCESS) &&
                        /* There's a limit to the scan only 
                         * if max_num_times is greater than 0 */
                        (
                            (
                                (instance->max_num_times > 0) &&
                                (instance->num_times >= instance->max_num_times)
                            ) ||
                            (
                                (instance->max_num_states_in_collection > 0) &&
                                (instance->num_states_in_collection >= instance->max_num_states_in_collection)
                                
                            )
                        )
                    )

                   )
                {
                    goto end_of_hard_threads_loop;
                }
            }
            /* 
             * Avoid over-flow 
             * */
            if (instance->ht_idx == instance->num_hard_threads)
            {
                instance->ht_idx = 0;
            }
        }

        end_of_hard_threads_loop:

        /*
         * If all the incomplete scans finished, then terminate.
         * */
        if (instance->num_hard_threads_finished == instance->num_hard_threads)
        {
            ret = FCS_STATE_IS_NOT_SOLVEABLE;
        }

        if (ret == FCS_STATE_WAS_SOLVED)
        {
            /* Create solution_moves in the first place */
            trace_solution(instance);
        }
    }


    if (ret == FCS_STATE_WAS_SOLVED)
    {
        if (instance->optimize_solution_path)
        {
            /* Call optimize_solution only once. Make sure that if
             * it has already run - we retain the old ret. */
            if (! instance->optimization_thread)
            {
                ret = freecell_solver_optimize_solution(instance);
            }
            if (ret == FCS_STATE_WAS_SOLVED)
            {
                /* Create the solution_moves in the first place */
                trace_solution(instance);
            }
        }
    }

    return ret;
}



/*
    Clean up a solving process that was terminated in the middle.
    This function does not substitute for later calling
    finish_instance() and free_instance().
  */
void freecell_solver_unresume_instance(
    freecell_solver_instance_t * instance
    )
{
    /*
     * Do nothing - since finish_instance() can take care of solution_states
     * and proto_solution_moves as they were created by these scans, then
     * I don't need to do it here, too
     *
     * */
    (void)instance;
}


#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBAVL_AVL_TREE) || (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBAVL_REDBLACK_TREE)

static void freecell_solver_tree_do_nothing(void * data, void * context)
{
}

#endif


/* A function for freeing a stack for the cleanup of the
   stacks collection
*/
#ifdef INDIRECT_STACK_STATES
#if (FCS_STACK_STORAGE == FCS_STACK_STORAGE_INTERNAL_HASH) || (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBAVL_AVL_TREE) || (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBAVL_REDBLACK_TREE)
#if 0
static void freecell_solver_stack_free(void * key, void * context)
{
    free(key);
}
#endif

#elif FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBREDBLACK_TREE
static void freecell_solver_libredblack_walk_destroy_stack_action
(
    const void * nodep,
    const VISIT which,
    const int depth,
    void * arg
 )
{
    if ((which == leaf) || (which == preorder))
    {
        free((void*)nodep);
    }
}
#elif FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_TREE
static gint freecell_solver_glib_tree_walk_destroy_stack_action
(
    gpointer key,
    gpointer value,
    gpointer data
)
{
    free(key);

    return 0;
}

#elif FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_HASH
static void freecell_solver_glib_hash_foreach_destroy_stack_action
(
    gpointer key,
    gpointer value,
    gpointer data
)
{
    free(key);
}
#endif

#endif

/***********************************************************/




void freecell_solver_destroy_move_stack_of_state(
        fcs_state_with_locations_t * ptr_state_with_locations,
        void * context
        )
{
    (void)context;
    if (ptr_state_with_locations->moves_to_parent != NULL)
    {
        fcs_move_stack_destroy(ptr_state_with_locations->moves_to_parent);
    }
}

/*
    This function should be called after the user has retrieved the
    results generated by the scan as it will destroy them.
  */
void freecell_solver_finish_instance(
    freecell_solver_instance_t * instance
    )
{
    int ht_idx;
    freecell_solver_hard_thread_t * hard_thread;

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INDIRECT)
    free(instance->indirect_prev_states);
#endif

    /* De-allocate the state packs */
    for(ht_idx=0;ht_idx<instance->num_hard_threads;ht_idx++)
    {
        hard_thread = instance->hard_threads[ht_idx];
        freecell_solver_state_ia_finish(hard_thread);

#ifdef INDIRECT_STACK_STATES
        freecell_solver_compact_allocator_finish(hard_thread->stacks_allocator);
        hard_thread->stacks_allocator = NULL;
#endif
        freecell_solver_compact_allocator_finish(hard_thread->move_stacks_allocator);
        hard_thread->move_stacks_allocator = NULL;
        
    }

    if (instance->optimization_thread)
    {
        freecell_solver_state_ia_finish(instance->optimization_thread);
    }


    /* De-allocate the state collection */
#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBREDBLACK_TREE)
    rbdestroy(instance->tree);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBAVL_AVL_TREE)
    avl_destroy(instance->tree, freecell_solver_tree_do_nothing);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_LIBAVL_REDBLACK_TREE)
    rb_destroy(instance->tree, freecell_solver_tree_do_nothing);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_GLIB_TREE)
    g_tree_destroy(instance->tree);
#endif

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_GLIB_HASH)
    g_hash_table_destroy(instance->hash);
#elif (FCS_STATE_STORAGE == FCS_STATE_STORAGE_INTERNAL_HASH)
    freecell_solver_hash_free(instance->hash);
#endif



    /* De-allocate the stack collection while free()'ing the stacks
    in the process */
#ifdef INDIRECT_STACK_STATES
#if FCS_STACK_STORAGE == FCS_STACK_STORAGE_INTERNAL_HASH
#if 0
    freecell_solver_hash_free_with_callback(instance->stacks_hash, freecell_solver_stack_free);
#else
    freecell_solver_hash_free(instance->stacks_hash);
#endif
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBAVL_AVL_TREE)
#if 0
    avl_destroy(instance->stacks_tree, freecell_solver_stack_free);
#else
    avl_destroy(instance->stacks_tree, NULL);
#endif
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBAVL_REDBLACK_TREE)
#if 0
    rb_destroy(instance->stacks_tree, freecell_solver_stack_free);
#else
    rb_destroy(instance->stacks_tree, NULL);
#endif
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_LIBREDBLACK_TREE)
#if 0
    rbwalk(instance->stacks_tree,
        freecell_solver_libredblack_walk_destroy_stack_action,
        NULL
        );
#endif
    rbdestroy(instance->stacks_tree);
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_TREE)
#if 0
    g_tree_traverse(
        instance->stacks_tree,
        freecell_solver_glib_tree_walk_destroy_stack_action,
        G_IN_ORDER,
        NULL
        );
#endif
    g_tree_destroy(instance->stacks_tree);
#elif (FCS_STACK_STORAGE == FCS_STACK_STORAGE_GLIB_HASH)
#if 0
    g_hash_table_foreach(
        instance->stacks_hash,
        freecell_solver_glib_hash_foreach_destroy_stack_action,
        NULL
        );
#endif
    g_hash_table_destroy(instance->stacks_hash);
#endif
#endif

#if (FCS_STATE_STORAGE == FCS_STATE_STORAGE_DB_FILE)
    instance->db->close(instance->db,0);
#endif


    clean_soft_dfs(instance);    
}

freecell_solver_soft_thread_t * freecell_solver_instance_get_soft_thread(
        freecell_solver_instance_t * instance,
        int ht_idx,
        int st_idx
        )
{
    if (ht_idx >= instance->num_hard_threads)
    {
        return NULL;
    }
    else
    {
        freecell_solver_hard_thread_t * hard_thread;
        hard_thread = instance->hard_threads[ht_idx];
        if (st_idx >= hard_thread->num_soft_threads)
        {
            return NULL;
        }
        else
        {
            return hard_thread->soft_threads[st_idx];
        }
    }
}

freecell_solver_soft_thread_t * freecell_solver_new_soft_thread(
    freecell_solver_soft_thread_t * soft_thread
    )
{
    freecell_solver_soft_thread_t * ret;
    freecell_solver_hard_thread_t * hard_thread;

    hard_thread = soft_thread->hard_thread;
    ret = alloc_soft_thread(hard_thread);

    /* Exceeded the maximal number of Soft-Threads in an instance */
    if (ret == NULL)
    {
        return NULL;
    }

    hard_thread->soft_threads = realloc(hard_thread->soft_threads, sizeof(hard_thread->soft_threads[0])*(hard_thread->num_soft_threads+1));
    hard_thread->soft_threads[hard_thread->num_soft_threads] = ret;
    hard_thread->num_soft_threads++;

    return ret;
}

freecell_solver_soft_thread_t * freecell_solver_new_hard_thread(
    freecell_solver_instance_t * instance
    )
{
    freecell_solver_hard_thread_t * ret;

    /* Exceeded the maximal number of Soft-Threads in an instance */
    ret = alloc_hard_thread(instance);

    if (ret == NULL)
    {
        return NULL;
    }

    instance->hard_threads =
        realloc(
            instance->hard_threads,
            (sizeof(instance->hard_threads[0]) * (instance->num_hard_threads+1))
            );

    instance->hard_threads[instance->num_hard_threads] = ret;

    instance->num_hard_threads++;

    return ret->soft_threads[0];
}

void freecell_solver_recycle_instance(
    freecell_solver_instance_t * instance
        )
{
    int ht_idx, st_idx;
    freecell_solver_hard_thread_t * hard_thread;
    freecell_solver_soft_thread_t * soft_thread;
    
    freecell_solver_finish_instance(instance);

    instance->num_times = 0;

    instance->num_hard_threads_finished = 0;

    for(ht_idx = 0;  ht_idx < instance->num_hard_threads; ht_idx++)
    {
        hard_thread = instance->hard_threads[ht_idx];
        hard_thread->num_times = 0;
        hard_thread->ht_max_num_times = hard_thread->num_times_step;
        hard_thread->max_num_times = -1;
        hard_thread->num_soft_threads_finished = 0;
        hard_thread->move_stacks_allocator =
            freecell_solver_compact_allocator_new();
#ifdef INDIRECT_STACK_STATES
        hard_thread->stacks_allocator = 
            freecell_solver_compact_allocator_new();
#endif
        for(st_idx = 0; st_idx < hard_thread->num_soft_threads ; st_idx++)
        {
            soft_thread = hard_thread->soft_threads[st_idx];
            soft_thread->is_finished = 0;
            soft_thread->initialized = 0;

            freecell_solver_rand_srand(soft_thread->rand_gen, soft_thread->rand_seed);
            /* Reset the priority queue */
            soft_thread->a_star_pqueue->CurrentSize = 0;
        }
    }
}
