// vim: ts=4 sw=4 expandtab ft=c

// Copyright (C) 1998-2007 The University of Melbourne.
// Copyright (C) 2014, 2016, 2018 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.

// This module implements the mdb commands in the "exp" category.
//
// The structure of these files is:
//
// - all the #includes
// - local macros and declarations of local static functions
// - one function for each command in the category
// - any auxiliary functions
// - any command argument strings
// - option processing functions.

#include "mercury_std.h"
#include "mercury_getopt.h"
#include "mercury_runtime_util.h"

#include "mercury_trace_internal.h"
#include "mercury_trace_cmds.h"
#include "mercury_trace_cmd_exp.h"
#include "mercury_trace_cmd_parameter.h"
#include "mercury_trace_util.h"

#include "mdbcomp.slice_and_dice.mh"

#include <stdio.h>

////////////////////////////////////////////////////////////////////////////

// The default number of lines to display for a dice.

#define MR_DEFAULT_DICE_LINES   50

static  void        MR_trace_print_dice(char *pass_trace_counts_file,
                        char *fail_trace_counts_file, char *sort_str,
                        int number_of_lines, char *out_str, char *module);

static  MR_bool     MR_trace_options_dice(char **pass_trace_counts_file,
                        char **fail_trace_count_file, char **sort_str,
                        MR_Unsigned *number_of_lines, char **out_file,
                        char **module, char ***words, int *word_count);

////////////////////////////////////////////////////////////////////////////

MR_Next
MR_trace_cmd_histogram_all(char **words, int word_count,
    MR_TraceCmdInfo *cmd, MR_EventInfo *event_info, MR_Code **jumpaddr)
{
#ifdef  MR_TRACE_HISTOGRAM

    if (word_count == 2) {
        FILE    *fp;

        fp = fopen(words[1], "w");
        if (fp == NULL) {
            fflush(MR_mdb_out);
            fprintf(MR_mdb_err, "mdb: cannot open file `%s' for output: %s.\n",
                words[1], MR_strerror(errno, errbuf, sizeof(errbuf)));
        } else {
            MR_trace_print_histogram(fp, "All-inclusive",
                MR_trace_histogram_all,
                MR_trace_histogram_hwm);
            if (fclose(fp) != 0) {
                fflush(MR_mdb_out);
                fprintf(MR_mdb_err, "mdb: error closing file `%s': %s.\n",
                    words[1], MR_strerror(errno, errbuf, sizeof(errbuf)));
            }
        }
    } else {
        MR_trace_usage_cur_cmd();
    }

#else   // MR_TRACE_HISTOGRAM

    fprintf(MR_mdb_out, "mdb: the `histogram_all' command is available "
        "only when histogram gathering is enabled.\n");

#endif  // MR_TRACE_HISTOGRAM

    return KEEP_INTERACTING;
}

MR_Next
MR_trace_cmd_histogram_exp(char **words, int word_count,
    MR_TraceCmdInfo *cmd, MR_EventInfo *event_info, MR_Code **jumpaddr)
{
#ifdef  MR_TRACE_HISTOGRAM

    if (word_count == 2) {
        FILE    *fp;

        fp = fopen(words[1], "w");
        if (fp == NULL) {
            fflush(MR_mdb_out);
            fprintf(MR_mdb_err, "mdb: cannot open file `%s' for output: %s.\n",
                words[1], MR_strerror(errno, errbuf, sizeof(errbuf)));
        } else {
            MR_trace_print_histogram(fp, "Experimental",
                MR_trace_histogram_exp,
                MR_trace_histogram_hwm);
            if (fclose(fp) != 0) {
                fflush(MR_mdb_out);
                fprintf(MR_mdb_err, "mdb: error closing file `%s': %s.\n",
                    words[1], MR_strerror(errno, errbuf, sizeof(errbuf)));
            }
        }
    } else {
        MR_trace_usage_cur_cmd();
    }

#else   // MR_TRACE_HISTOGRAM

    fprintf(MR_mdb_out, "mdb: the `histogram_exp' command is available "
        "only when histogram gathering is enabled.\n");

#endif  // MR_TRACE_HISTOGRAM

    return KEEP_INTERACTING;
}

MR_Next
MR_trace_cmd_clear_histogram(char **words, int word_count,
    MR_TraceCmdInfo *cmd, MR_EventInfo *event_info, MR_Code **jumpaddr)
{
#ifdef  MR_TRACE_HISTOGRAM

    if (word_count == 1) {
        for (int i = 0; i <= MR_trace_histogram_hwm; i++) {
            MR_trace_histogram_exp[i] = 0;
        }
    } else {
        MR_trace_usage_cur_cmd();
    }

#else   // MR_TRACE_HISTOGRAM

    fprintf(MR_mdb_out, "mdb: the `clear_histogram' command is available "
        "only when histogram gathering is enabled.\n");

#endif  // MR_TRACE_HISTOGRAM

    return KEEP_INTERACTING;
}

MR_Next
MR_trace_cmd_dice(char **words, int word_count, MR_TraceCmdInfo *cmd,
    MR_EventInfo *event_info, MR_Code **jumpaddr)
{
    char        *pass_trace_counts_file;
    char        *fail_trace_counts_file;
    char        *sort_str;
    char        *out_file;
    char        *module;
    MR_Unsigned number_of_lines;

    sort_str = NULL;
    out_file = NULL;
    module = NULL;
    number_of_lines = MR_DEFAULT_DICE_LINES;

    pass_trace_counts_file = MR_dice_pass_trace_counts_file;
    fail_trace_counts_file = MR_dice_fail_trace_counts_file;

    if (! MR_trace_options_dice(&pass_trace_counts_file,
        &fail_trace_counts_file, &sort_str, &number_of_lines, &out_file,
        &module, &words, &word_count))
    {
        // The usage message has already been printed.
        ;
    } else if (word_count == 1) {
        if (pass_trace_counts_file == NULL) {
            fflush(MR_mdb_out);
            fprintf(MR_mdb_err, "mdb: No passing trace counts file specified."
                "\nmdb: Specify one with the -p option or using the `set' "
                "command.\n");
        } else if (fail_trace_counts_file == NULL) {
            fflush(MR_mdb_out);
            fprintf(MR_mdb_err, "mdb: No failing trace counts file specified."
                "\nmdb: Specify one with the -f option or using the `set' "
                "command.\n");
        } else {
            if (sort_str == NULL) {
                sort_str = MR_copy_string("");
            }

            if (module == NULL) {
                module = MR_copy_string("");
            }
            MR_trace_print_dice(pass_trace_counts_file, fail_trace_counts_file,
                sort_str, number_of_lines, out_file, module);
        }
    } else {
        MR_trace_usage_cur_cmd();
    }

    if (out_file != NULL) {
        free(out_file);
    }

    if (sort_str != NULL) {
        free(sort_str);
    }

    if (module != NULL) {
        free(module);
    }

    return KEEP_INTERACTING;
}

////////////////////////////////////////////////////////////////////////////

static  void
MR_trace_print_dice(char *pass_trace_counts_file,
    char *fail_trace_count_file, char *sort_str, int number_of_lines,
    char *out_file, char *module)
{
    MR_String   dice;
    MR_String   problem;
    MR_String   aligned_pass_trace_counts_file;
    MR_String   aligned_fail_trace_count_file;
    MR_String   aligned_sort_str;
    MR_String   aligned_module;
    FILE        *fp;
    char        errbuf[MR_STRERROR_BUF_SIZE];

    MR_TRACE_USE_HP(
        MR_make_aligned_string(aligned_pass_trace_counts_file,
            (MR_String) pass_trace_counts_file);
        MR_make_aligned_string(aligned_fail_trace_count_file,
            (MR_String) fail_trace_count_file);
        MR_make_aligned_string(aligned_sort_str, (MR_String) sort_str);
        if (module == NULL) {
            MR_make_aligned_string(aligned_module, (MR_String) "");
        } else {
            MR_make_aligned_string(aligned_module, (MR_String) module);
        }
    );

    MR_TRACE_CALL_MERCURY(
        MR_MDBCOMP_read_dice_to_string(aligned_pass_trace_counts_file,
            aligned_fail_trace_count_file, aligned_sort_str,
            number_of_lines, aligned_module, &dice, &problem);
    );

    // The string in dice is a sequence of complete lines.
    if (MR_streq(problem, "")) {
        if (out_file == NULL) {
            fprintf(MR_mdb_out, "%s", dice);
        } else {
            fp = fopen(out_file, "w");
            if (fp != NULL) {
                fprintf(fp, "%s", dice);
                if (fclose(fp) != 0) {
                    fflush(MR_mdb_out);
                    fprintf(MR_mdb_err, "mdb: Error closing file `%s': %s\n",
                        out_file, MR_strerror(errno, errbuf, sizeof(errbuf)));
                }
            } else {
                fflush(MR_mdb_out);
                fprintf(MR_mdb_err, "mdb: Error opening file `%s': %s\n",
                    out_file, MR_strerror(errno, errbuf, sizeof(errbuf)));
            }
        }
    } else {
        fflush(MR_mdb_out);
        fprintf(MR_mdb_err, "mdb: %s\n", problem);
    }
}

////////////////////////////////////////////////////////////////////////////

static struct MR_option MR_trace_dice_opts[] =
{
    { "pass-trace-counts",      MR_required_argument,   NULL,   'p' },
    { "pass-trace-count",       MR_required_argument,   NULL,   'p' },
    { "fail-trace-counts",      MR_required_argument,   NULL,   'f' },
    { "fail-trace-count",       MR_required_argument,   NULL,   'f' },
    { "sort",                   MR_required_argument,   NULL,   's' },
    { "top",                    MR_required_argument,   NULL,   'n' },
    { "output-to-file",         MR_required_argument,   NULL,   'o' },
    { "module",                 MR_required_argument,   NULL,   'm' },
    { NULL,                     MR_no_argument,         NULL,   0   }
};

static MR_bool
MR_trace_options_dice(char **pass_trace_counts_file,
    char **fail_trace_counts_file, char **sort_str, MR_Unsigned *n,
    char **out_file, char **module, char ***words, int *word_count)
{
    int c;

    MR_optind = 0;
    while ((c = MR_getopt_long(*word_count, *words, "p:f:s:n:o:m:",
        MR_trace_dice_opts, NULL)) != EOF)
    {
        switch (c) {

            case 'p':
                // Don't free *pass_trace_counts_file even if non-NULL,
                // since its initial value comes from a global variable,
                // and thus will still be used after the dice command.
                // The waste of not freeing of the string allocated by
                // MR_copy_string if this option is duplicated can be
                // easily lived with.

                *pass_trace_counts_file = MR_copy_string(MR_optarg);
                break;

            case 'f':
                // Don't free *fail_trace_counts_file even if non-NULL,
                // since its initial value comes from a global variable,
                // and thus will still be used after the dice command.
                // The waste of not freeing of the string allocated by
                // MR_copy_string if this option is duplicated can be
                // easily lived with.

                *fail_trace_counts_file = MR_copy_string(MR_optarg);
                break;

            case 's':
                *sort_str = MR_copy_string(MR_optarg);
                break;

            case 'n':
                if (! MR_trace_is_natural_number(MR_optarg, n)) {
                    MR_trace_usage_cur_cmd();
                    return MR_FALSE;
                }
                break;

            case 'o':
                *out_file = MR_copy_string(MR_optarg);
                break;

            case 'm':
                *module = MR_copy_string(MR_optarg);
                break;

            default:
                MR_trace_usage_cur_cmd();
                return MR_FALSE;
        }
    }

    *words = *words + MR_optind - 1;
    *word_count = *word_count - MR_optind + 1;
    return MR_TRUE;
}
