/*
    INI LIBRARY

    Check based unit test for ini_config_augment.

    Copyright (C) Alexander Scheel <ascheel@redhat.com> 2017

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

    INI Library 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with INI Library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "config.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <check.h>

/* #define TRACE_LEVEL 7 */
#define TRACE_HOME
#include "trace.h"
#include "ini_configobj.h"
#include "ini_config_priv.h"

static int write_to_file(char *path, char *text)
{
    FILE *f = fopen(path, "w");
    int bytes = 0;
    if (f == NULL)
        return 1;

    bytes = fprintf(f, "%s", text);
    if (bytes != strlen(text)) {
        return 1;
    }

    return fclose(f);
}

static int exists_array(const char *needle, char **haystack, uint32_t count)
{
    uint32_t i = 0;

    for (i = 0; i < count; i++) {
        fprintf(stderr, "%s == %s?\n", needle, haystack[i]);
        if (strcmp(needle, haystack[i]) == 0) {
            return 1;
        }
    }

    return 0;
}

START_TEST(test_ini_augment_merge_sections)
{
    char base_path[PATH_MAX];
    char augment_path[PATH_MAX];

    char config_base[] =
        "[section]\n"
        "key1 = first\n"
        "key2 = exists\n";

    char config_augment[] =
        "[section]\n"
        "key1 = augment\n"
        "key3 = exists\n";

    const char *builddir;

    uint32_t flags[3] = { INI_MS_DETECT , INI_MS_DETECT | INI_MS_PRESERVE,
                          INI_MS_DETECT | INI_MS_OVERWRITE };

    int expected_attributes_counts[3] = { 3, 2, 2 };
    const char *test_sections[3] = { "section", "section", "section" };
    const char *test_attributes[3] = { "key3", "key1", "key1" };
    const char *test_attribute_values[3] = {"exists", "first", "augment" };

    int ret;
    int iter;

    builddir = getenv("builddir");
    if (builddir == NULL) {
        builddir = ".";
    }

    snprintf(base_path, PATH_MAX, "%s/tmp_augment_base.conf", builddir);
    snprintf(augment_path, PATH_MAX, "%s/tmp_augment_augment.conf", builddir);

    ret = write_to_file(base_path, config_base);
    fail_unless(ret == 0, "Failed to write %s: ret %d.\n", base_path, ret);

    write_to_file(augment_path, config_augment);
    fail_unless(ret == 0, "Failed to write %s: ret %d.\n", augment_path, ret);

    for (iter = 0; iter < 3; iter++) {
        uint32_t merge_flags = flags[iter];
        int expected_attributes_count = expected_attributes_counts[iter];
        const char *test_section = test_sections[iter];
        const char *test_attribute = test_attributes[iter];
        const char *test_attribute_value = test_attribute_values[iter];
        struct ini_cfgobj *in_cfg;
        struct ini_cfgobj *result_cfg;
        struct ini_cfgfile *file_ctx;
        struct ref_array *error_list;
        struct ref_array *success_list;

        char **sections;
        int sections_count;

        char **attributes;
        int attributes_count;

        struct value_obj *val;
        char *val_str;

        /* Match only augment.conf */
        const char *m_patterns[] = { "^tmp_augment_augment.conf$", NULL };

        /* Match all sections */
        const char *m_sections[] = { ".*", NULL };

        /* Create config collection */
        ret = ini_config_create(&in_cfg);
        fail_unless(ret == EOK, "Failed to create collection. Error %d\n",
                    ret);

        /* Open base.conf */
        ret = ini_config_file_open(base_path, 0, &file_ctx);
        fail_unless(ret == EOK, "Failed to open file. Error %d\n", ret);

        /* Seed in_cfg with base.conf */
        ret = ini_config_parse(file_ctx, 1, 0, 0, in_cfg);
        fail_unless(ret == EOK, "Failed to parse file context. Error %d\n",
                    ret);

        /* Update base.conf with augment.conf */
        ret = ini_config_augment(in_cfg,
                                 builddir,
                                 m_patterns,
                                 m_sections,
                                 NULL,
                                 INI_STOP_ON_NONE,
                                 0,
                                 INI_PARSE_NOSPACE|INI_PARSE_NOTAB,
                                 merge_flags,
                                 &result_cfg,
                                 &error_list,
                                 &success_list);
        /* We always expect EEXIST due to DETECT being set. */
        fail_unless(ret == EEXIST,
                    "Failed to augment context. Error %d\n", ret);

        if (result_cfg) {
            ini_config_destroy(in_cfg);
            in_cfg = result_cfg;
            result_cfg = NULL;
        }

        /* Get a list of sections from the resulting cfg. */
        sections = ini_get_section_list(in_cfg, &sections_count, &ret);
        fail_unless(ret == EOK, "Failed to get section list. Error %d\n", ret);

        /* Validate that the tested section exists. */
        ret = exists_array(test_section, sections, sections_count);
        fail_if(ret == 0, "Failed to find expected section.\n");

        /* Get a list of attributes from the resulting cfg. */
        attributes = ini_get_attribute_list(in_cfg, test_section,
                                            &attributes_count,
                                            &ret);
        fail_unless(ret == EOK, "Failed to get attribute list. Error %d\n",
                    ret);

        /* Validate that the expected number of attributes exist. This
         * distinguishes MERGE from PRESERVE/OVERWRITE. */
        fail_unless(expected_attributes_count == attributes_count,
                    "Expected %d attributes, but received %d.\n",
                    expected_attributes_count, attributes_count);

        /* Validate that the test attribute exists. This distinguishes
         * PRESERVE from OVERWRITE. */
        ret = exists_array(test_attribute, attributes, attributes_count);
        fail_if(ret == 0, "Failed to find expected attribute.\n");

        ret = ini_get_config_valueobj(test_section, test_attribute, in_cfg,
                                        0, &val);
        fail_unless(ret == EOK, "Failed to load value object. Error %d\n",
                    ret);

        val_str = ini_get_string_config_value(val, &ret);
        fail_unless(ret == EOK, "Failed to get config value. Error %d\n", ret);

        /* Validate the value of the test attribute. */
        ret = strcmp(val_str, test_attribute_value);

        fail_unless(ret == 0, "Attribute %s didn't have expected value of "
                    "(%s): saw %s\n", test_attribute, test_attribute_value,
                    val_str);

        /* Cleanup */
        free(val_str);
        ini_free_attribute_list(attributes);
        ini_free_section_list(sections);
        ref_array_destroy(error_list);
        ini_config_file_destroy(file_ctx);
        ref_array_destroy(success_list);
        ini_config_destroy(in_cfg);
        ini_config_destroy(result_cfg);
    }

    remove(base_path);
    remove(augment_path);
}
END_TEST

START_TEST(test_ini_augment_empty_dir)
{
    int ret;
    struct ini_cfgobj *ini_cfg;
    struct ini_cfgfile *file_ctx;
    struct value_obj *vo;
    const char *patterns[] = { ".*", NULL };
    const char *sections[] = { ".*", NULL };
    char **section_list;
    char **attrs_list;
    struct ini_cfgobj *result_cfg = NULL;
    int size;
    char empty_dir_path[PATH_MAX] = {0};
    const char *builddir;
    int32_t val;
    char base_cfg[] =
        "[section_one]\n"
        "one = 1\n";

    builddir = getenv("builddir");
    if (builddir == NULL) {
        builddir = ".";
    }

    ret = snprintf(empty_dir_path, PATH_MAX, "%s/tmp_empty_dir", builddir);
    fail_if(ret > PATH_MAX || ret < 0, "snprintf failed\n");

    ret = ini_config_file_from_mem(base_cfg, strlen(base_cfg),
                                   &file_ctx);
    fail_unless(ret == EOK, "Failed to load config. Error %d.\n", ret);

    ret = ini_config_create(&ini_cfg);
    fail_unless(ret == EOK, "Failed to create config. Error %d.\n", ret);
    ret = ini_config_parse(file_ctx, INI_STOP_ON_ERROR, INI_MV1S_ALLOW, 0,
                           ini_cfg);
    fail_unless(ret == EOK, "Failed to parse configuration. Error %d.\n", ret);

    /* Create an empty directory */
    ret = mkdir(empty_dir_path, 0700);
    if (ret == -1) {
        ret = errno;
        fail_if(ret != EEXIST,
                "Failed to create empty directory. Error %d.\n", errno);
    }

    ret = ini_config_augment(ini_cfg,
                             empty_dir_path,
                             patterns,
                             sections,
                             NULL,
                             INI_STOP_ON_ANY,
                             INI_MV1S_OVERWRITE,
                             INI_PARSE_NOWRAP,
                             INI_MV2S_OVERWRITE,
                             &result_cfg,
                             NULL,
                             NULL);

    fail_unless(ret == EOK);

    /* If the snippet directory is empty, result_cfg should be the original
     * ini_cfg and not NULL */
    fail_if(result_cfg == NULL);

    /* Now check if the content of result_cfg is what we expected */
    section_list = ini_get_section_list(result_cfg, &size, NULL);
    fail_unless(size == 1);
    fail_unless(strcmp(section_list[0], "section_one") == 0);

    attrs_list = ini_get_attribute_list(result_cfg, section_list[0],
                                        &size, NULL);
    fail_unless(size == 1);
    fail_unless(strcmp(attrs_list[0], "one") == 0);

    ret = ini_get_config_valueobj(section_list[0],
                                  attrs_list[0],
                                  result_cfg,
                                  INI_GET_FIRST_VALUE,
                                  &vo);
    fail_unless(ret == 0);

    val = ini_get_int32_config_value(vo, 1, 100, NULL);
    fail_unless(val == 1, "Expected attribute value not found.\n");

    ini_free_attribute_list(attrs_list);
    ini_free_section_list(section_list);
    ini_config_destroy(result_cfg);
    ini_config_destroy(ini_cfg);
    ini_config_file_destroy(file_ctx);
    remove(empty_dir_path);
}
END_TEST

static Suite *ini_augment_suite(void)
{
    Suite *s = suite_create("ini_augment_suite");

    TCase *tc_augment = tcase_create("ini_augment");
    tcase_add_test(tc_augment, test_ini_augment_merge_sections);
    tcase_add_test(tc_augment, test_ini_augment_empty_dir);

    suite_add_tcase(s, tc_augment);

    return s;
}

int main(void)
{
    int number_failed;

    Suite *s = ini_augment_suite();
    SRunner *sr = srunner_create(s);
    srunner_run_all(sr, CK_ENV);
    number_failed = srunner_ntests_failed(sr);
    srunner_free(sr);
    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
