/*
    Copyright (C) 2011  ABRT Team
    Copyright (C) 2011  RedHat inc.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "internal_libreport.h"

GHashTable *g_event_config_list;
static GHashTable *g_event_config_symlinks;

invalid_option_t *new_invalid_option(void)
{
    return xzalloc(sizeof(invalid_option_t));
}

event_option_t *new_event_option(void)
{
    return xzalloc(sizeof(event_option_t));
}

event_config_t *new_event_config(const char *name)
{
    event_config_t *e = xzalloc(sizeof(event_config_t));
    e->info = new_config_info(name);
    return e;
}

config_item_info_t *ec_get_config_info(event_config_t * ec)
{
    return ec->info;
}

void ec_set_screen_name(event_config_t *ec, const char *screen_name)
{
    ci_set_screen_name(ec->info, screen_name);
}

const char *ec_get_screen_name(event_config_t *ec)
{
    return ci_get_screen_name(ec->info);
}

const char *ec_get_description(event_config_t *ec)
{
    return ci_get_description(ec->info);
}

void ec_set_description(event_config_t *ec, const char *description)
{
    ci_set_description(ec->info, description);
}

const char *ec_get_name(event_config_t *ec)
{
    return ci_get_name(ec->info);
}

const char *ec_get_long_desc(event_config_t *ec)
{
    return ci_get_long_desc(ec->info);
}

void ec_set_long_desc(event_config_t *ec, const char *long_descr)
{
    ci_set_long_desc(ec->info, long_descr);
}

bool ec_is_configurable(event_config_t* ec)
{
    return g_list_length(ec->options) > 0;
}

void ec_print(event_config_t *ec)
{
    printf("%s\n\t%s\n\t%s\n",
        ec_get_name(ec),
        ec_get_screen_name(ec),
        ec_get_description(ec)
        );
}

bool ec_restricted_access_enabled(event_config_t *ec)
{
    if (!ec->ec_supports_restricted_access)
    {
        if (ec->ec_restricted_access_option != NULL)
            log_warning("Event '%s' does not support restricted access but has the option", ec_get_name(ec));

        return false;
    }

    if (ec->ec_restricted_access_option == NULL)
    {
        log_debug("Event '%s' supports restricted access but is missing the option", ec_get_name(ec));
        return false;
    }

    event_option_t *eo = get_event_option_from_list(ec->ec_restricted_access_option, ec->options);
    if (eo == NULL)
    {
        log_warning("Event '%s' supports restricted access but the option is not defined", ec_get_name(ec));
        return false;
    }

    if (eo->eo_type != OPTION_TYPE_BOOL)
    {
        log_warning("Restricted option '%s' of Event '%s' is not of 'bool' type",
                    ec->ec_restricted_access_option, ec_get_name(ec));
        return false;
    }

    return eo->eo_value != NULL && string_to_bool(eo->eo_value);
}

void free_invalid_options(invalid_option_t *p)
{
    if (!p)
        return;
    free(p->invopt_name);
    free(p->invopt_error);
    free(p);
}

void free_event_option(event_option_t *p)
{
    if (!p)
        return;
    free(p->eo_name);
    free(p->eo_value);
    free(p->eo_label);
    free(p->eo_note_html);
    //free(p->eo_description);
    //free(p->eo_allowed_value);
    free(p);
}

void free_event_config(event_config_t *p)
{
    if (!p)
        return;

    free_config_info(p->info);
    free(p->ec_creates_items);
    free(p->ec_requires_items);
    free(p->ec_exclude_items_by_default);
    free(p->ec_include_items_by_default);
    free(p->ec_exclude_items_always);
    free(p->ec_restricted_access_option);
    g_list_free_full(p->ec_imported_event_names, free);
    g_list_free_full(p->options, (GDestroyNotify)free_event_option);

    free(p);
}


static int cmp_event_option_name_with_string(gconstpointer a, gconstpointer b)
{
    const event_option_t *evopt = a;
    return !evopt->eo_name || strcmp(evopt->eo_name, (char *)b) != 0;
}

event_option_t *get_event_option_from_list(const char *name, GList *options)
{
    GList *elem = g_list_find_custom(options, name, &cmp_event_option_name_with_string);
    if (elem)
        return (event_option_t *)elem->data;
    return NULL;
}

static void load_config_files(const char *dir_path)
{
    GList *conf_files = get_file_list(dir_path, "conf");
    while (conf_files != NULL)
    {
        file_obj_t *file = (file_obj_t *)conf_files->data;
        char *fullpath = file->fullpath;
        char *filename = file->filename;

        event_config_t *event_config = get_event_config(filename);
        bool new_config = (!event_config);
        if (new_config)
            event_config = new_event_config(filename);

        map_string_t *keys_and_values = new_map_string();

        load_conf_file(fullpath, keys_and_values, /*skipKeysWithoutValue:*/ false);

        /* Insert or replace every key/value from keys_and_values to event_config->option */
        map_string_iter_t iter;
        const char *name;
        const char *value;
        init_map_string_iter(&iter, keys_and_values);
        while (next_map_string_iter(&iter, &name, &value))
        {
            event_option_t *opt;
            GList *elem = g_list_find_custom(event_config->options, name,
                                            cmp_event_option_name_with_string);
            if (elem)
            {
                opt = elem->data;
                // log_warning("conf: replacing '%s' value:'%s'->'%s'", name, opt->value, value);
                free(opt->eo_value);
            }
            else
            {
                // log_warning("conf: new value %s='%s'", name, value);
                opt = new_event_option();
                opt->eo_name = xstrdup(name);
            }
            opt->eo_value = xstrdup(value);
            if (!elem)
                event_config->options = g_list_append(event_config->options, opt);
        }

        free_map_string(keys_and_values);

        if (new_config)
            g_hash_table_replace(g_event_config_list, xstrdup(ec_get_name(event_config)), event_config);

        free_file_obj(file);
        conf_files = g_list_delete_link(conf_files, conf_files);
    }
}

/* (Re)loads data from /etc/abrt/events/foo.{xml,conf} and $XDG_CACHE_HOME/abrt/events/foo.conf */
GHashTable *load_event_config_data(void)
{
    free_event_config_data();

    if (!g_event_config_list)
        g_event_config_list = g_hash_table_new_full(
                /*hash_func*/ g_str_hash,
                /*key_equal_func:*/ g_str_equal,
                /*key_destroy_func:*/ free,
                /*value_destroy_func:*/ (GDestroyNotify) free_event_config
        );
    if (!g_event_config_symlinks)
        g_event_config_symlinks = g_hash_table_new_full(
                /*hash_func*/ g_str_hash,
                /*key_equal_func:*/ g_str_equal,
                /*key_destroy_func:*/ free,
                /*value_destroy_func:*/ free
        );

    GList *event_files = get_file_list(EVENTS_DIR, "xml");
    while (event_files)
    {
        file_obj_t *file = (file_obj_t *)event_files->data;

        event_config_t *event_config = get_event_config(file->filename);
        bool new_config = (!event_config);
        if (new_config)
           event_config = new_event_config(file->filename);

        load_event_description_from_file(event_config, file->fullpath);

        if (new_config)
            g_hash_table_replace(g_event_config_list, xstrdup(ec_get_name(event_config)), event_config);

        free_file_obj(file);
        event_files = g_list_delete_link(event_files, event_files);
    }

    /* EVENTS_DIR      -> /usr/share/libreport/events/$EVENT_NAME.xml
     *   - event xml definition files
     *
     * EVENTS_CONF_DIR -> /etc/libreport/events/$EVENT_NAME.conf
     *   - default values for xml definitions
     *
     * https://fedorahosted.org/abrt/wiki/AbrtConfiguration#Adjustingpluginconfiguration
     */
    load_config_files(EVENTS_CONF_DIR);

    char *cachedir;
    cachedir = concat_path_file(g_get_user_cache_dir(), "abrt/events");
    load_config_files(cachedir);
    free(cachedir);

    return g_event_config_list;
}

/* Frees all loaded data */
void free_event_config_data(void)
{
    if (g_event_config_list)
    {
        g_hash_table_destroy(g_event_config_list);
        g_event_config_list = NULL;
    }
    if (g_event_config_symlinks)
    {
        g_hash_table_destroy(g_event_config_symlinks);
        g_event_config_symlinks = NULL;
    }
}

event_config_t *get_event_config(const char *name)
{
    if (!g_event_config_list)
        return NULL;
    if (g_event_config_symlinks)
    {
        char *link = g_hash_table_lookup(g_event_config_symlinks, name);
        if (link)
            name = link;
    }
    return g_hash_table_lookup(g_event_config_list, name);
}

GList *export_event_config(const char *event_name)
{
    GList *env_list = NULL;

    event_config_t *config = get_event_config(event_name);
    if (config)
    {
        GList *imported = config->ec_imported_event_names;
        while (imported)
        {
            GList *exported = export_event_config(/*Event name*/imported->data);
            while (exported)
            {
                if (!g_list_find_custom(env_list, exported->data, (GCompareFunc)strcmp))
                    /* It is not necessary to make a copy of opt->eo_name */
                    /* since its memory is owned by event_option_t and it */
                    /* has global scope */
                    env_list = g_list_prepend(env_list, exported->data);

                exported = g_list_remove_link(exported, exported);
            }

            imported = g_list_next(imported);
        }

        GList *lopt;
        for (lopt = config->options; lopt; lopt = lopt->next)
        {
            event_option_t *opt = lopt->data;
            if (!opt->eo_value)
                continue;

            log_debug("Exporting '%s=%s'", opt->eo_name, opt->eo_value);

            /* Add the exported key only if it is not in the list */
            if (!g_list_find_custom(env_list, opt->eo_name, (GCompareFunc)strcmp))
                /* It is not necessary to make a copy of opt->eo_name */
                /* since its memory is owned by opt and it has global scope */
                env_list = g_list_prepend(env_list, opt->eo_name);

            /* setenv() makes copies of strings */
            xsetenv(opt->eo_name, opt->eo_value);
        }
    }

    return env_list;
}

/*
 * Goes through given list and calls unsetnev() for each list item.
 *
 * Accepts a list of 'const char *' type items which contains names of exported
 * environment variables and which was returned from export_event_config()
 * function.
 */
void unexport_event_config(GList *env_list)
{
    while (env_list)
    {
        char *var_val = env_list->data;
        log_debug("Unexporting '%s'", var_val);
        safe_unsetenv(var_val);

        /* The list doesn't own memory of values: see export_event_config() */
        env_list = g_list_remove(env_list, var_val);
    }
}

/* return NULL if successful otherwise appropriate error message */
static char *validate_event_option(event_option_t *opt)
{
    if (!opt->eo_allow_empty && (!opt->eo_value || !opt->eo_value[0]))
        return xstrdup(_("Missing mandatory value"));

    /* if value is NULL and allow-empty yes than it doesn't make sence to check it */
    if (!opt->eo_value)
        return NULL;

    const gchar *s = NULL;
    if (!g_utf8_validate(opt->eo_value, -1, &s))
            return xasprintf(_("Invalid utf8 character '%c'"), *s);

    switch (opt->eo_type) {
    case OPTION_TYPE_TEXT:
    case OPTION_TYPE_PASSWORD:
        break;
    case OPTION_TYPE_NUMBER:
    {
        char *endptr;
        errno = 0;
        long r = strtol(opt->eo_value, &endptr, 10);
        (void) r;
        if (errno != 0 || endptr == opt->eo_value || *endptr != '\0')
            return xasprintf(_("Invalid number '%s'"), opt->eo_value);

        break;
    }
    case OPTION_TYPE_BOOL:
        /* note: should match strings which string_to_bool accepts */
        if (strcasecmp(opt->eo_value, "yes") != 0
         && strcasecmp(opt->eo_value, "no") != 0
         && strcasecmp(opt->eo_value, "on") != 0
         && strcasecmp(opt->eo_value, "off") != 0
         && strcasecmp(opt->eo_value, "true") != 0
         && strcasecmp(opt->eo_value, "false") != 0
         && strcmp(opt->eo_value, "1") != 0
         && strcmp(opt->eo_value, "0") != 0
        ) {
            return xasprintf(_("Invalid boolean value '%s'"), opt->eo_value);
        }
        break;
    case OPTION_TYPE_HINT_HTML:
        return NULL;
    default:
        return xstrdup(_("Unsupported option type"));
    };

    return NULL;
}

GList *get_options_with_err_msg(const char *event_name)
{
    INITIALIZE_LIBREPORT();

    event_config_t *config = get_event_config(event_name);
    if (!config)
        return NULL;

    GList *iter, *err_list = NULL;

    for (iter = config->options; iter; iter = iter->next)
    {
        event_option_t *opt = (event_option_t *)iter->data;
        char *err = validate_event_option(opt);
        if (err)
        {
            invalid_option_t *inv_opt = new_invalid_option();
            inv_opt->invopt_name = xstrdup(opt->eo_name);
            inv_opt->invopt_error = xstrdup(err);
            err_list = g_list_prepend(err_list, inv_opt);
        }
    }

    if (err_list != NULL)
        return g_list_reverse(err_list);

    return NULL;
}

/*
 * This function checks if a value of problem's backtrace rating is acceptable
 * for event according to event's required rating value.
 *
 * The solved problem seems to be pretty straightforward but there are some
 * pitfalls.
 *
 * 1. Is rating acceptable if event doesn't have configuration?
 * 2. Is rating acceptable if problem's data doesn't contains rating value?
 * 3. Is rating acceptable if rating value is not a number?
 * 4. What message show to user if there is a concern about usability?
 */
bool check_problem_rating_usability(const event_config_t *cfg,
                                    problem_data_t *pd,
                                    char **description,
                                    char **detail)
{
    INITIALIZE_LIBREPORT();

    char *tmp_desc = NULL;
    char *tmp_detail = NULL;
    bool result = true;

    if (!cfg)
        goto finish;

    const char *rating_str = problem_data_get_content_or_NULL(pd, FILENAME_RATING);

    if (!rating_str)
        goto finish;

    /* just to be sure */
    result = false;

    const long minimal_rating = cfg->ec_minimal_rating;
    char *endptr;
    errno = 0;
    const long rating = strtol(rating_str, &endptr, 10);
    if (errno != 0 || endptr == rating_str || *endptr != '\0')
    {
        tmp_desc = xasprintf(
                _("The problem cannot be reported due to an invalid data. " \
                  "'%s' file does not contain a number."),
                FILENAME_RATING);

        tmp_detail = xstrdup(_("Please report this problem to ABRT project developers."));

        result = false;
    }
    else if (rating == minimal_rating) /* bt is usable, but not complete, so show a warning */
    {
        tmp_desc = xstrdup(_("The backtrace is incomplete, please make sure you provide the steps to reproduce."));
        tmp_detail = xstrdup(_("The backtrace probably can't help developer to diagnose the bug."));

        result = true;
    }
    else if (rating < minimal_rating)
    {
        tmp_desc = xstrdup(_("Reporting disabled because the backtrace is unusable."));

        const char *package = problem_data_get_content_or_NULL(pd, FILENAME_PACKAGE);
        if (package && package[0])
            tmp_detail = xasprintf(_("Please try to install debuginfo manually using the command: \"debuginfo-install %s\" and try again."), package);
        else
            tmp_detail = xstrdup(_("A proper debuginfo is probably missing or the coredump is corrupted."));

        result = false;
    }
    else /* rating > minimal_rating */
        result = true;

finish:
    if (description)
        *description = tmp_desc;
    else
        free(tmp_desc);

    if (detail)
        *detail = tmp_detail;
    else
        free(tmp_detail);

    return result;
}
