/* Copyright (c) 2000 Shlomi Fish
 *
 * 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.
 */
/*
 * test_multi_parallel.c - the Freecell Solver range solver.
 *
 * Also see:
 *  - fc_pro_range_solver.c - range solver that can count the moves in
 *  the solution and display the FC-Pro ones.
 *  - threaded_range_solver.c - solve different boards in several POSIX
 *  threads.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "alloc_wrap.h"
#include "portable_time.h"
#include "portable_int64.h"

#include "fcs_user.h"
#include "fcs_cl.h"
#include "unused.h"
#include "inline.h"
#include "bool.h"
#include "range_solvers_gen_ms_boards.h"
#include "output_to_file.h"
#include "handle_parsing.h"

#ifdef FCS_TRACE_MEM
#include "portable_time.h"
#include <unistd.h>
#include <sys/types.h>
#endif

static void my_iter_handler(
    void * user_instance,
    fcs_int_limit_t iter_num,
    int depth,
    void * ptr_state,
    fcs_int_limit_t parent_iter_num,
    void * lp_context
    )
{
    fc_solve_display_information_context_t * context;
    context = (fc_solve_display_information_context_t*)lp_context;

    fprintf(stdout, "Iteration: %li\n", (long)iter_num);
    fprintf(stdout, "Depth: %i\n", depth);
    if (context->display_parent_iter_num)
    {
        fprintf(stdout, "Parent Iteration: %li\n", (long)parent_iter_num);
    }
    fprintf(stdout, "\n");


    if (context->debug_iter_state_output)
    {
        char state_string[1000];
        freecell_solver_user_iter_state_stringify(
            user_instance,
            state_string,
            ptr_state
            FC_SOLVE__PASS_PARSABLE(context->parseable_output)
            , context->canonized_order_output
            FC_SOLVE__PASS_T(context->display_10_as_t)
        );
        printf("%s\n---------------\n\n\n", state_string);
    }

}

typedef struct {
    fc_solve_display_information_context_t display_context;
    void * instance;
}pack_item_t;

static int cmd_line_callback(
    void * instance,
    int argc GCC_UNUSED,
    freecell_solver_str_t argv[],
    int arg,
    int * num_to_skip,
    int * ret GCC_UNUSED,
    void * context
    )
{
    pack_item_t * item;
    fc_solve_display_information_context_t * dc;
    item = (pack_item_t * )context;
    dc = &(item->display_context);

    *num_to_skip = 0;

    if ((!strcmp(argv[arg], "-i")) || (!strcmp(argv[arg], "--iter-output")))
    {
        freecell_solver_user_set_iter_handler_long(
            instance,
            my_iter_handler,
            dc
            );
        dc->debug_iter_output_on = TRUE;
    }
    else if ((!strcmp(argv[arg], "-s")) || (!strcmp(argv[arg], "--state-output")))
    {
        dc->debug_iter_state_output = TRUE;
    }
    else if ((!strcmp(argv[arg], "-p")) || (!strcmp(argv[arg], "--parseable-output")))
    {
#ifndef FC_SOLVE_IMPLICIT_PARSABLE_OUTPUT
        dc->parseable_output = TRUE;
#endif
    }
    else if ((!strcmp(argv[arg], "-c")) || (!strcmp(argv[arg], "--canonized-order-output")))
    {
        dc->canonized_order_output = TRUE;
    }
    else if ((!strcmp(argv[arg], "-t")) || (!strcmp(argv[arg], "--display-10-as-t")))
    {
#ifndef FC_SOLVE_IMPLICIT_T_RANK
        dc->display_10_as_t = TRUE;
#endif
    }
    else if ((!strcmp(argv[arg], "-m")) || (!strcmp(argv[arg], "--display-moves")))
    {
        dc->display_moves = TRUE;
        dc->display_states = FALSE;
    }
    else if ((!strcmp(argv[arg], "-sn")) || (!strcmp(argv[arg], "--standard-notation")))
    {
        dc->standard_notation = TRUE;
    }
    else if ((!strcmp(argv[arg], "-sam")) || (!strcmp(argv[arg], "--display-states-and-moves")))
    {
        dc->display_moves = TRUE;
        dc->display_states = TRUE;
    }
    else if ((!strcmp(argv[arg], "-pi")) || (!strcmp(argv[arg], "--display-parent-iter")))
    {
        dc->display_parent_iter_num = TRUE;
    }
    else
    {
        fprintf(stderr, "Unknown option %s!\n", argv[arg]);
        exit(-1);
    }
    *num_to_skip = 1;
    return FCS_CMD_LINE_SKIP;
}

static const char * known_parameters[] = {
    "-i", "--iter-output",
    "-s", "--state-output",
    "-p", "--parseable-output",
    "-c", "--canonized-order-output",
    "-sn", "--standard-notation",
    "-snx", "--standard-notation-extended",
    "-sam", "--display-states-and-moves",
    "-t", "--display-10-as-t",
    "-pi", "--display-parent-iter",
    NULL
    };

#define BINARY_OUTPUT_NUM_INTS 16

typedef struct
{
    FILE * file;
    char * buffer;
    char * buffer_end;
    char * ptr;
} binary_output_t;

static void print_int(binary_output_t * bin, int val)
{
    unsigned char * buffer = (unsigned char *)bin->ptr;
    int p;

    for(p=0;p<4;p++)
    {
        buffer[p] = (unsigned char)(val & 0xFF);
        val >>= 8;
    }
    bin->ptr += 4;
    if (bin->ptr == bin->buffer_end)
    {
        fwrite(bin->buffer, 1, bin->ptr - bin->buffer, bin->file);
        fflush(bin->file);
        /* Reset ptr to the beginning */
        bin->ptr = bin->buffer;
    }
}

#define print_int_wrapper(i) { if (binary_output.file) { print_int(&binary_output, (i));  } }

static void print_help(void)
{
    printf("\n%s",
"freecell-solver-range-parallel-solve start end print_step\n"
"   [--binary-output-to filename] [--total-iterations-limit limit]\n"
"   [fc-solve Arguments...]\n"
"\n"
"Solves a sequence of boards from the Microsoft/Freecell Pro Deals\n"
"\n"
"start - the first board in the sequence\n"
"end - the last board in the sequence (inclusive)\n"
"print_step - at which division to print a status line\n"
"\n"
"--binary-output-to   filename\n"
"     Outputs statistics to binary file 'filename'\n"
"--total-iterations-limit  limit\n"
"     Limits each board for up to 'limit' iterations.\n"
          );
}

static int read_int(FILE * f, int * dest)
{
    unsigned char buffer[4];
    int num_read;

    num_read = fread(buffer, 1, 4, f);
    if (num_read != 4)
    {
        return 1;
    }
    *dest = (buffer[0]+((buffer[1]+((buffer[2]+((buffer[3])<<8))<<8))<<8));

    return 0;
}

int main(int argc, char * argv[])
{
    pack_item_t user = {.display_context = INITIAL_DISPLAY_CONTEXT};
    /* char buffer[2048]; */
    int ret;
    int board_num;
    int start_board, end_board, stop_at;
    fcs_portable_time_t mytime;

    fcs_int64_t total_num_iters = 0;

    fcs_bool_t was_total_iterations_limit_per_board_set = FALSE;
    fcs_int_limit_t total_iterations_limit_per_board = -1;

    char * binary_output_filename = NULL;
    fcs_state_string_t state_string;

    binary_output_t binary_output;
    const char * solutions_directory = NULL;
    char * solution_filename = NULL;

    int arg = 1;
#ifdef FCS_TRACE_MEM
    int rss_found = 0;
    long max_rss_delta_deal = -1;
    long long int prev_rss = -1, max_rss_delta = -1;
#endif

    if (argc < 4)
    {
        fprintf(stderr, "Not Enough Arguments!\n");
        print_help();
        exit(-1);
    }
    start_board = atoi(argv[arg++]);
    end_board = atoi(argv[arg++]);
    stop_at = atoi(argv[arg++]);

    if (stop_at <= 0)
    {
        fprintf(stderr, "print_step (the third argument) must be greater than 0.\n");
        print_help();
        exit(-1);

    }

    for (;arg < argc; arg++)
    {
        if (!strcmp(argv[arg], "--binary-output-to"))
        {
            arg++;
            if (arg == argc)
            {
                fprintf(stderr, "--binary-output-to came without an argument!\n");
                print_help();
                exit(-1);
            }
            binary_output_filename = argv[arg];
        }
        else if (!strcmp(argv[arg], "--total-iterations-limit"))
        {
            arg++;
            if (arg == argc)
            {
                fprintf(stderr, "--total-iterations-limit came without an argument!\n");
                print_help();
                exit(-1);
            }
            was_total_iterations_limit_per_board_set = TRUE;
            total_iterations_limit_per_board = (fcs_int_limit_t)atol(argv[arg]);
        }
        else if (!strcmp(argv[arg], "--solutions-directory"))
        {
            arg++;
            if (arg == argc)
            {
                fprintf(stderr, "--solutions-directory came without an argument!\n");
                print_help();
                exit(-1);
            }
            solutions_directory = argv[arg];
            solution_filename = SMALLOC(
                solution_filename,
                strlen(solutions_directory) + 1024
            );
        }
        else
        {
            break;
        }
    }

    FCS_PRINT_STARTED_AT(mytime);
    fflush(stdout);

    if (binary_output_filename)
    {
        FILE * in;

        binary_output.buffer = malloc(sizeof(int) * BINARY_OUTPUT_NUM_INTS);
        binary_output.ptr = binary_output.buffer;
        binary_output.buffer_end = binary_output.buffer + sizeof(int)*BINARY_OUTPUT_NUM_INTS;


        in = fopen(binary_output_filename, "rb");
        if (in == NULL)
        {
            binary_output.file = fopen(binary_output_filename, "wb");
            if (! binary_output.file)
            {
                fprintf(stderr, "Could not open \"%s\" for writing!\n", binary_output_filename);
                exit(-1);
            }

            print_int_wrapper(start_board);
            print_int_wrapper(end_board);
            print_int_wrapper((int)total_iterations_limit_per_board);
        }
        else
        {
            long file_len;
#define read_int_wrapper(var) \
            {       \
                if (read_int(in, &var))  \
                {        \
                    fprintf(stderr, "%s", \
                        "Output file is too short to deduce the configuration!\n"    \
                           );     \
                    exit(-1);       \
                }       \
            }
            read_int_wrapper(start_board);
            read_int_wrapper(end_board);
            {
                int val;
                read_int_wrapper(val);
                total_iterations_limit_per_board = val;
            }

            fseek(in, 0, SEEK_END);
            file_len = ftell(in);
            if (file_len % 4 != 0)
            {
                fprintf(stderr, "%s", "Output file has an invalid length. Terminating.\n");
                exit(-1);
            }
            start_board += (file_len-12)/4;
            if (start_board >= end_board)
            {
                fprintf(stderr, "%s", "Output file was already finished being generated.\n");
                exit(-1);
            }
            fclose(in);
            binary_output.file = fopen(binary_output_filename, "ab");
            if (! binary_output.file)
            {
                fprintf(stderr, "Could not open \"%s\" for writing!\n", binary_output_filename);
                exit(-1);
            }
        }
    }
    else
    {
        binary_output.file = NULL;
        binary_output.buffer
            = binary_output.ptr
            = binary_output.buffer_end
            = NULL;
    }

    user.instance = alloc_instance_and_parse(
        argc,
        argv,
        &arg,
        known_parameters,
        cmd_line_callback,
        &user,
        TRUE
    );

    ret = 0;

    for(board_num=start_board;board_num<=end_board;board_num++)
    {
        get_board(board_num, state_string);

        if (was_total_iterations_limit_per_board_set)
        {
            freecell_solver_user_limit_iterations_long(user.instance, total_iterations_limit_per_board);
        }

        ret =
            freecell_solver_user_solve_board(
                user.instance,
                state_string
                );

        if (ret == FCS_STATE_SUSPEND_PROCESS)
        {
            FCS_PRINT_INTRACTABLE_BOARD(mytime, board_num);
            fflush(stdout);
            print_int_wrapper(-1);
        }
        else if (ret == FCS_STATE_FLARES_PLAN_ERROR)
        {
            const char * flares_error_string;

            flares_error_string =
                freecell_solver_user_get_last_error_string(user.instance);

            printf("Flares Plan: %s\n", flares_error_string);

            break;
        }
        else if (ret == FCS_STATE_IS_NOT_SOLVEABLE)
        {
            FCS_PRINT_UNSOLVED_BOARD(mytime, board_num);
            print_int_wrapper(-2);
        }
        else
        {
            print_int_wrapper((int)freecell_solver_user_get_num_times_long(user.instance));
        }

        if (solutions_directory)
        {
            FILE * output_fh;
            sprintf(solution_filename, "%s%09d.sol",
                solutions_directory, board_num
            );

            output_fh = fopen(solution_filename, "wt");

            if (! output_fh)
            {
                fprintf(stderr,
                        "Could not open output file '%s' for writing!",
                        solution_filename
                       );
                return -1;
            }

            fc_solve_output_result_to_file(
                output_fh, user.instance, ret, &user.display_context
            );

            fclose(output_fh);
            output_fh = NULL;
        }

        total_num_iters += freecell_solver_user_get_num_times_long(user.instance);

        if (board_num % stop_at == 0)
        {
            FCS_PRINT_REACHED_BOARD(mytime, board_num, total_num_iters);
            fflush(stdout);
        }

        freecell_solver_user_recycle(user.instance);

#if 0
#elif defined(FCS_TRACE_MEM)
    {
#define STEP 100000
        long long int rss;
        unsigned long long unsigned_foo;
        char stat_fn[1024], foo_str[1024];
        FILE * stat;

        snprintf(stat_fn, sizeof(stat_fn), "/proc/%ld/stat", (long)(getpid()));

        {
            /* This was taken from:
             *
             * http://www.brokestream.com/procstat.html
             * */
            stat = fopen(stat_fn, "r");
#define readone(foo) (fscanf(stat, "%lld ", &rss))
#define readstr(foo) (fscanf(stat, "%1000s ", foo_str))
#define readchar(foo) (fscanf(stat, "%c ", foo_str))
#define readunsigned(foo) (fscanf(stat, "%llu ", &unsigned_foo))
            readone(&pid);
            readstr(tcomm);
            readchar(&state);
            readone(&ppid);
            readone(&pgid);
            readone(&sid);
            readone(&tty_nr);
            readone(&tty_pgrp);
            readone(&flags);
            readone(&min_flt);
            readone(&cmin_flt);
            readone(&maj_flt);
            readone(&cmaj_flt);
            readone(&utime);
            readone(&stimev);
            readone(&cutime);
            readone(&cstime);
            readone(&priority);
            readone(&nicev);
            readone(&num_threads);
            readone(&it_real_value);
            readunsigned(&start_time);
            readone(&vsize);
            readone(&rss);
#undef readone
#undef readunsigned
#undef readchar
#undef readstr

            fclose(stat);

            long long rss_delta = rss - prev_rss;
            if (rss_found == 1)
            {
                max_rss_delta = rss_delta;
                max_rss_delta_deal = board_num;
            }
            else if (rss_found == 2)
            {
                if (rss_delta > max_rss_delta)
                {
                    max_rss_delta = rss_delta;
                    max_rss_delta_deal = board_num;
                }
                printf("Max RSS delta %lld encountered at deal %ld\n",
                    max_rss_delta, max_rss_delta_deal
                );
            }
            prev_rss = rss;
            if (rss_found < 2)
            {
                rss_found++;
            }
        }
    }
#endif
    }

    freecell_solver_user_free(user.instance);

    if (binary_output_filename)
    {
        fwrite(binary_output.buffer, 1, binary_output.ptr - binary_output.buffer, binary_output.file);
        fflush(binary_output.file);
        fclose(binary_output.file);
    }

    free (solution_filename);
    solution_filename = NULL;

    return 0;
}
