/**
 * @file test_tree_data_merge.c
 * @author Radek Krejci <rkrejci@cesnet.cz>
 * @brief Cmocka tests for complex data merges.
 *
 * Copyright (c) 2017 CESNET, z.s.p.o.
 *
 * This source code is licensed under BSD 3-Clause License (the "License").
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://opensource.org/licenses/BSD-3-Clause
 */

#define _GNU_SOURCE

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <cmocka.h>

#include "tests/config.h"
#include "libyang.h"

struct state {
    struct ly_ctx *ctx1;
    struct ly_ctx *ctx2;
    struct ly_ctx *ctx3;
    struct lyd_node *source;
    struct lyd_node *target;
    struct lyd_node *result;
    char *path, *output;
};

static int
setup_dflt(void **state)
{
    struct state *st;

    (*state) = st = calloc(1, sizeof *st);
    if (!st) {
        fprintf(stderr, "Memory allocation error.\n");
        return -1;
    }

    /* libyang context */
    st->ctx1 = ly_ctx_new(TESTS_DIR "/api/files", 0);
    if (!st->ctx1) {
        fprintf(stderr, "Failed to create context.\n");
        goto error;
    }

    return 0;

error:
    ly_ctx_destroy(st->ctx1, NULL);
    free(st);
    (*state) = NULL;

    return -1;
}

static int
teardown_dflt(void **state)
{
    struct state *st = (*state);

    free(st->path);
    free(st->output);
    lyd_free_withsiblings(st->target);
    lyd_free_withsiblings(st->source);
    lyd_free_withsiblings(st->result);
    ly_ctx_destroy(st->ctx1, NULL);
    free(st);
    (*state) = NULL;

    return 0;
}

static int
setup_mctx(void **state)
{
    struct state *st;

    (*state) = st = calloc(1, sizeof *st);
    if (!st) {
        fprintf(stderr, "Memory allocation error");
        return -1;
    }

    /* libyang context */
    st->ctx1 = ly_ctx_new(NULL, 0);
    st->ctx2 = ly_ctx_new(NULL, 0);
    st->ctx3 = ly_ctx_new(NULL, 0);
    if (!st->ctx1 || !st->ctx2 || !st->ctx3) {
        fprintf(stderr, "Failed to create context.\n");
        return -1;
    }

    return 0;
}

static int
teardown_mctx(void **state)
{
    struct state *st = (*state);

    lyd_free_withsiblings(st->source);
    lyd_free_withsiblings(st->target);
    lyd_free_withsiblings(st->result);
    ly_ctx_destroy(st->ctx1, NULL);
    ly_ctx_destroy(st->ctx2, NULL);
    ly_ctx_destroy(st->ctx3, NULL);

    free(st);
    (*state) = NULL;

    return 0;
}

static void
test_merge(void **state)
{
    struct state *st = (*state);
    uint32_t i;
    const char *output_template =
"<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">"
  "<module>"
    "<name>yang</name>"
    "<revision>2016-02-11</revision>"
    "<conformance-type>implement</conformance-type>"
    "<namespace>urn:ietf:params:xml:ns:yang:1</namespace>"
  "</module>"
  "<module>"
    "<name>ietf-yang-library</name>"
    "<revision>2016-02-01</revision>"
    "<conformance-type>implement</conformance-type>"
    "<namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>"
  "</module>"
  "<module>"
    "<name>ietf-netconf-acm</name>"
    "<revision>2012-02-22</revision>"
    "<conformance-type>implement</conformance-type>"
    "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-acm</namespace>"
  "</module>"
  "<module>"
    "<name>ietf-netconf</name>"
    "<revision>2011-06-01</revision>"
    "<conformance-type>implement</conformance-type>"
    "<namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>"
    "<feature>writable-running</feature>"
    "<feature>candidate</feature>"
    "<feature>rollback-on-error</feature>"
    "<feature>validate</feature>"
    "<feature>startup</feature>"
    "<feature>xpath</feature>"
  "</module>"
  "<module>"
    "<name>ietf-netconf-monitoring</name>"
    "<revision>2010-10-04</revision>"
    "<conformance-type>implement</conformance-type>"
    "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring</namespace>"
  "</module>"
  "<module>"
    "<name>ietf-netconf-with-defaults</name>"
    "<revision>2011-06-01</revision>"
    "<conformance-type>implement</conformance-type>"
    "<namespace>urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults</namespace>"
  "</module>"
"</modules-state>";

    st->target = lyd_parse_path(st->ctx1, TESTS_DIR "/api/files/merge_start.xml", LYD_XML, LYD_OPT_GET);
    assert_ptr_not_equal(st->target, NULL);

    asprintf(&st->path, TESTS_DIR "/api/files/mergeXX.xml");
    for (i = 1; i < 12; ++i) {
        sprintf(st->path + (strlen(st->path) - 6), "%02u.xml", i);
        st->source = lyd_parse_path(st->ctx1, st->path, LYD_XML, LYD_OPT_GET);
        assert_ptr_not_equal(st->source, NULL);

        assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
        st->source = NULL;
    }

    lyd_print_mem(&st->output, st->target, LYD_XML, 0);
    assert_string_equal(st->output, output_template);
}

static void
test_merge2(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  container c1 {"
                    "    container c2 {"
                    "      leaf y { type string; }"
                    "}}}";
    const char *trg = "<x xmlns=\"urn:x\">x</x>";
    const char *result = "<x xmlns=\"urn:x\">x</x><c1 xmlns=\"urn:x\"><c2><y>y</y></c2></c1>";
    char *printed = NULL;

    /* merging leaf x and leaf y - without the parents, lyd_merge is supposed also to merge subtrees */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    st->result = lyd_new_path(NULL, st->ctx1, "/x:c1/c2/y", "y", 0, 0);
    assert_ptr_not_equal(st->result, NULL);
    assert_int_equal(lyd_validate(&st->result, LYD_OPT_CONFIG, NULL), 0);

    /* lyd_new_path returns the first created node, so the top level c1, but we need the
     * subtree with the y node */
    st->source = st->result->child->child;
    lyd_unlink(st->source);
    lyd_free(st->result);
    st->result = NULL;

    /* the target tree contains only the x node */
    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge3(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "    container A {"
                    "      leaf f1 {type string;}"
                    "      container B {"
                    "        leaf f2 {type string;}"
                    "      }"
                    "    }"
                    "  }";
    const char *trg = "<A xmlns=\"urn:x\"> <f1>block</f1> </A>";
    const char *src = "<A xmlns=\"urn:x\"> <f1>aa</f1> <B> <f2>bb</f2> </B> </A>";
    const char *result = "<A xmlns=\"urn:x\"><f1>aa</f1><B><f2>bb</f2></B></A>";
    char *printed = NULL;

    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);
    assert_int_equal(lyd_validate(&st->target, LYD_OPT_CONFIG, NULL), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge4(void **state)
{
    struct state *st = (*state);
    const char *sch =
        "module A {"
            "namespace \"aa:A\";"
            "prefix A;"
            "container A {"
                "leaf f1 {type string;}"
                "container B {"
                    "leaf f2 {type string;}"
                "}"
                "container C {"
                    "leaf f3 {type string;}"
                "}"
            "}"
        "}";

    const char *trg = "<A xmlns=\"aa:A\"> <B> <f2>aaa</f2> </B> </A>";
    const char *src = "<A xmlns=\"aa:A\"> <C> <f3>bbb</f3> </C> </A>";
    const char *result = "<A xmlns=\"aa:A\"><B><f2>aaa</f2></B><C><f3>bbb</f3></C></A>";
    char *printed = NULL;

    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx1, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    /* merge them */
    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);
    assert_int_equal(lyd_validate(&st->target, LYD_OPT_CONFIG, NULL), 0);

    /* check the result */
    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_dflt1(void **state)
{
    struct state *st = (*state);
    struct lyd_node *tmp;

    assert_ptr_not_equal(ly_ctx_load_module(st->ctx1, "merge-dflt", NULL), NULL);

    st->target = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/c", "c_dflt", 0, 0);
    assert_ptr_not_equal(st->target, NULL);
    assert_int_equal(lyd_validate(&(st->target), LYD_OPT_CONFIG, NULL), 0);

    st->source = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/a", "a_val", 0, 0);
    assert_ptr_not_equal(st->source, NULL);
    tmp = lyd_new_path(st->source, st->ctx1, "/merge-dflt:top/b", "b_val", 0, 0);
    assert_ptr_not_equal(tmp, NULL);
    assert_int_equal(lyd_validate(&(st->source), LYD_OPT_CONFIG, NULL), 0);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
    st->source = NULL;

    /* c should be replaced and now be default */
    assert_int_equal(st->target->child->dflt, 1);
}

static void
test_merge_dflt2(void **state)
{
    struct state *st = (*state);
    struct lyd_node *tmp;

    assert_ptr_not_equal(ly_ctx_load_module(st->ctx1, "merge-dflt", NULL), NULL);

    st->target = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/c", "c_dflt", 0, 0);
    assert_ptr_not_equal(st->target, NULL);
    assert_int_equal(lyd_validate(&(st->target), LYD_OPT_CONFIG, NULL), 0);

    st->source = lyd_new_path(NULL, st->ctx1, "/merge-dflt:top/a", "a_val", 0, 0);
    assert_ptr_not_equal(st->source, NULL);
    tmp = lyd_new_path(st->source, st->ctx1, "/merge-dflt:top/b", "b_val", 0, 0);
    assert_ptr_not_equal(tmp, NULL);
    assert_int_equal(lyd_validate(&(st->source), LYD_OPT_CONFIG, NULL), 0);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_EXPLICIT), 0);

    /* c should not be replaced, so c remains not default */
    assert_int_equal(st->target->child->dflt, 0);
}

static void
test_merge_to_trgctx1(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  leaf y { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *trg = "<y xmlns=\"urn:x\">y</y>";
    const char *result = "<y xmlns=\"urn:x\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 1: src is in ctx1, trg is in ctx2, result is expected in ctx2, src is being destroyed */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    assert_int_equal(lyd_merge(st->target, st->source, LYD_OPT_DESTRUCT), 0);
    /* forget pointer to source, it should be destroyed, if not it will be seen in valgrind as memory leak */
    st->source = NULL;

    assert_ptr_equal(st->target->schema->module->ctx, st->ctx2);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx2);

    /* print the result after freeing the ctx1 */
    ly_ctx_destroy(st->ctx1, NULL);
    st->ctx1 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_to_trgctx2(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  leaf y { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *trg = "<y xmlns=\"urn:x\">y</y>";
    const char *result = "<y xmlns=\"urn:x\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 2: src is in ctx1, trg is in ctx2, result is expected in ctx2, src is not destroyed */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    assert_int_equal(lyd_merge(st->target, st->source, 0), 0);

    assert_ptr_equal(st->target->schema->module->ctx, st->ctx2);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx2);

    assert_ptr_not_equal(st->source, st->target->next);

    /* print the result after freeing the ctx1 */
    lyd_free(st->source);
    ly_ctx_destroy(st->ctx1, NULL);
    st->source = NULL;
    st->ctx1 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}

static void
test_merge_to_ctx(void **state)
{
    struct state *st = (*state);
    const char *sch = "module x {"
                    "  namespace urn:x;"
                    "  prefix x;"
                    "  leaf x { type string; }"
                    "  leaf y { type string; }}";
    const char *src = "<x xmlns=\"urn:x\">x</x>";
    const char *trg = "<y xmlns=\"urn:x\">y</y>";
    const char *result = "<y xmlns=\"urn:x\">y</y><x xmlns=\"urn:x\">x</x>";
    char *printed = NULL;

    /* case 3: src is in ctx1, trg is in ctx2, result is requested in ctx3, src is destroyed */
    assert_ptr_not_equal(lys_parse_mem(st->ctx1, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx2, sch, LYS_IN_YANG), NULL);
    assert_ptr_not_equal(lys_parse_mem(st->ctx3, sch, LYS_IN_YANG), NULL);

    st->source = lyd_parse_mem(st->ctx1, src, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->source, NULL);

    st->target = lyd_parse_mem(st->ctx2, trg, LYD_XML, LYD_OPT_CONFIG);
    assert_ptr_not_equal(st->target, NULL);

    assert_int_equal(lyd_merge_to_ctx(&st->target, st->source, LYD_OPT_DESTRUCT, st->ctx3), 0);
    st->source = NULL; /* source is expected to be destroyed */

    /* check the merged data - leaf y */
    assert_ptr_equal(st->target->schema->module->ctx, st->ctx3);
    /* check the merged data - leaf x */
    assert_ptr_not_equal(st->target->next, NULL);
    assert_ptr_equal(st->target->next->schema->module->ctx, st->ctx3);

    /* print the result after freeing the ctx1 and ctx2 */
    ly_ctx_destroy(st->ctx1, NULL);
    ly_ctx_destroy(st->ctx2, NULL);
    st->ctx1 = NULL;
    st->ctx2 = NULL;

    lyd_print_mem(&printed, st->target, LYD_XML, LYP_WITHSIBLINGS);
    assert_string_equal(printed, result);
    free(printed);
}


int main(void)
{
    const struct CMUnitTest tests[] = {
                    cmocka_unit_test_setup_teardown(test_merge, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge2, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge3, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge4, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge_dflt1, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge_dflt2, setup_dflt, teardown_dflt),
                    cmocka_unit_test_setup_teardown(test_merge_to_trgctx1, setup_mctx, teardown_mctx),
                    cmocka_unit_test_setup_teardown(test_merge_to_trgctx2, setup_mctx, teardown_mctx),
                    cmocka_unit_test_setup_teardown(test_merge_to_ctx, setup_mctx, teardown_mctx),};

    return cmocka_run_group_tests(tests, NULL, NULL);
}
