/**
 * @file test_xpath.c
 * @author Michal Vasko <mvasko@cesnet.cz>
 * @brief Cmocka tests for XPath expression evaluation.
 *
 * Copyright (c) 2016 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
 */

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

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

struct state {
    struct ly_ctx *ctx;
    struct lyd_node *dt;
    struct ly_set *set;
};

static const char *data =
"<interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\" xmlns:ianaift=\"urn:ietf:params:xml:ns:yang:iana-if-type\""
    " xmlns:ip=\"urn:ietf:params:xml:ns:yang:ietf-ip\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\">"
  "<interface>"
    "<name>iface1</name>"
    "<description>iface1 dsc</description>"
    "<type>ianaift:ethernetCsmacd</type>"
    "<enabled>true</enabled>"
    "<link-up-down-trap-enable>disabled</link-up-down-trap-enable>"
    "<ip:ipv4>"
      "<ip:enabled>true</ip:enabled>"
      "<ip:forwarding>true</ip:forwarding>"
      "<ip:mtu>68</ip:mtu>"
      "<ip:address>"
        "<ip:ip>10.0.0.1</ip:ip>"
        "<ip:netmask>255.0.0.0</ip:netmask>"
      "</ip:address>"
      "<ip:address>"
        "<ip:ip>172.0.0.1</ip:ip>"
        "<ip:prefix-length>16</ip:prefix-length>"
      "</ip:address>"
      "<ip:neighbor>"
        "<ip:ip>10.0.0.2</ip:ip>"
        "<ip:link-layer-address>01:34:56:78:9a:bc:de:f0</ip:link-layer-address>"
      "</ip:neighbor>"
    "</ip:ipv4>"
    "<ip:ipv6>"
      "<ip:enabled>true</ip:enabled>"
      "<ip:forwarding>false</ip:forwarding>"
      "<ip:mtu>1280</ip:mtu>"
      "<ip:address>"
        "<ip:ip>2001:abcd:ef01:2345:6789:0:1:1</ip:ip>"
        "<ip:prefix-length>64</ip:prefix-length>"
      "</ip:address>"
      "<ip:neighbor>"
        "<ip:ip>2001:abcd:ef01:2345:6789:0:1:2</ip:ip>"
        "<ip:link-layer-address>01:34:56:78:9a:bc:de:f0</ip:link-layer-address>"
      "</ip:neighbor>"
      "<ip:dup-addr-detect-transmits>52</ip:dup-addr-detect-transmits>"
      "<ip:autoconf>"
        "<ip:create-global-addresses>true</ip:create-global-addresses>"
        "<ip:create-temporary-addresses>false</ip:create-temporary-addresses>"
        "<ip:temporary-valid-lifetime>600</ip:temporary-valid-lifetime>"
        "<ip:temporary-preferred-lifetime>300</ip:temporary-preferred-lifetime>"
      "</ip:autoconf>"
    "</ip:ipv6>"
  "</interface>"
  "<interface>"
    "<name>iface2</name>"
    "<description>iface2 dsc</description>"
    "<type>ianaift:softwareLoopback</type>"
    "<enabled>false</enabled>"
    "<link-up-down-trap-enable>disabled</link-up-down-trap-enable>"
    "<ip:ipv4>"
      "<ip:address>"
        "<ip:ip>10.0.0.5</ip:ip>"
        "<ip:netmask>255.0.0.0</ip:netmask>"
      "</ip:address>"
      "<ip:address>"
        "<ip:ip>172.0.0.5</ip:ip>"
        "<ip:prefix-length>16</ip:prefix-length>"
      "</ip:address>"
      "<ip:neighbor>"
        "<ip:ip>10.0.0.1</ip:ip>"
        "<ip:link-layer-address>01:34:56:78:9a:bc:de:fa</ip:link-layer-address>"
      "</ip:neighbor>"
    "</ip:ipv4>"
    "<ip:ipv6>"
      "<ip:address>"
        "<ip:ip>2001:abcd:ef01:2345:6789:0:1:5</ip:ip>"
        "<ip:prefix-length>64</ip:prefix-length>"
      "</ip:address>"
      "<ip:neighbor>"
        "<ip:ip>2001:abcd:ef01:2345:6789:0:1:1</ip:ip>"
        "<ip:link-layer-address>01:34:56:78:9a:bc:de:fa</ip:link-layer-address>"
      "</ip:neighbor>"
      "<ip:dup-addr-detect-transmits>100</ip:dup-addr-detect-transmits>"
      "<ip:autoconf>"
        "<ip:create-global-addresses>true</ip:create-global-addresses>"
        "<ip:create-temporary-addresses>false</ip:create-temporary-addresses>"
        "<ip:temporary-valid-lifetime>600</ip:temporary-valid-lifetime>"
        "<ip:temporary-preferred-lifetime>300</ip:temporary-preferred-lifetime>"
      "</ip:autoconf>"
    "</ip:ipv6>"
  "</interface>"
"</interfaces>"
;

static int
setup_f(void **state)
{
    struct state *st;
    const char *augschema = "ietf-ip";
    const char *typeschema = "iana-if-type";
    const char *ietfdir = TESTS_DIR"/schema/yin/ietf/";
    const struct lys_module *mod;

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

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

    /* schema */
    mod = ly_ctx_load_module(st->ctx, augschema, NULL);
    if (!mod) {
        fprintf(stderr, "Failed to load data module \"%s\".\n", augschema);
        goto error;
    }
    lys_features_enable(mod, "*");

    mod = ly_ctx_get_module(st->ctx, "ietf-interfaces", NULL, 0);
    if (!mod) {
        fprintf(stderr, "Failed to get data module \"ietf-interfaces\".\n");
        goto error;
    }
    lys_features_enable(mod, "*");

    mod = ly_ctx_load_module(st->ctx, typeschema, NULL);
    if (!mod) {
        fprintf(stderr, "Failed to load data module \"%s\".\n", typeschema);
        goto error;
    }

    /* data */
    st->dt = lyd_parse_mem(st->ctx, data, LYD_XML, LYD_OPT_CONFIG);
    if (!st->dt) {
        fprintf(stderr, "Failed to build the data tree.\n");
        goto error;
    }

    return 0;

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

    return -1;
}

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

    lyd_free_withsiblings(st->dt);
    ly_set_free(st->set);
    ly_ctx_destroy(st->ctx, NULL);
    free(st);
    (*state) = NULL;

    return 0;
}

static void
test_invalid(void **state)
{
    struct state *st = (*state);

    st->set = lyd_find_path(st->dt, "/:interface/name");
    assert_ptr_equal(st->set, NULL);
    assert_int_not_equal(ly_errno, 0);

    st->set = lyd_find_path(st->dt, "/interface/name[./]");
    assert_ptr_equal(st->set, NULL);
    assert_int_not_equal(ly_errno, 0);

    st->set = lyd_find_path(st->dt, "/interface/name[./.]()");
    assert_ptr_equal(st->set, NULL);
    assert_int_not_equal(ly_errno, 0);
}

static void
test_simple(void **state)
{
    struct state *st = (*state);

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 2);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface[name='iface1']");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface[name='iface1']/ietf-ip:ipv4/ietf-ip:address");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 2);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface[name='iface1']/ietf-ip:ipv4/ietf-ip:address[ietf-ip:ip='10.0.0.1']");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    ly_set_free(st->set);
    st->set = NULL;
}

static void
test_advanced(void **state)
{
    struct state *st = (*state);

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface[name='iface1']/ietf-ip:ipv4/*[ietf-ip:ip]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 3);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces//*[ietf-ip:ip]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 10);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces//*[ietf-ip:ip[.='10.0.0.1']]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 2);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//ietf-ip:ip[.='10.0.0.1']");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 2);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//*[../description]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 14);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//interface[name='iface1']/ietf-ip:ipv4//*");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 12);
    ly_set_free(st->set);
    st->set = NULL;
}

static void
test_functions_operators(void **state)
{
    struct state *st = (*state);

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface/name[true() and not(false()) and not(boolean(. != 'iface1'))]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[0])->value_str, "iface1");
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "(/ietf-interfaces:interfaces/interface/name)[round(ceiling(1.8)+0.4)+floor(0.28)]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[0])->value_str, "iface2");
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "/ietf-interfaces:interfaces/interface/type[string-length(substring-after(\"hello12hi\", '12')) != 2 or starts-with(.,'iana') and contains(.,'back') and .=substring-before(concat(string(.),'aab', \"abb\"),'aa')]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[0])->value_str, "iana-if-type:softwareLoopback");
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//*[ietf-ip:neighbor/ietf-ip:link-layer-address = translate(normalize-space('\t\n01   .34    .56\t.78.9a\n\r.bc.de.f0  \t'), '. ', ':')]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 2);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "(//ietf-ip:ip)[position() = last()]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[0])->value_str, "2001:abcd:ef01:2345:6789:0:1:1");
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "(//ietf-ip:ip)[count(//*[.='52'])]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 1);
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[0])->value_str, "10.0.0.1");
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//*[local-name()='autoconf' and namespace-uri()='urn:ietf:params:xml:ns:yang:ietf-ip']");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 2);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//interface[name='iface2']//. | //ip | //interface[number((1 mod (20 - 15)) div 1)]//.");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 68);
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "//ietf-ip:ip[position() mod 2 = 1] | //ietf-ip:ip[position() mod 2 = 0]");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 10);
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[0])->value_str, "10.0.0.1");
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[1])->value_str, "172.0.0.1");
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[2])->value_str, "10.0.0.2");
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[7])->value_str, "10.0.0.1");
    assert_string_equal(((struct lyd_node_leaf_list *)st->set->set.d[9])->value_str, "2001:abcd:ef01:2345:6789:0:1:1");
    ly_set_free(st->set);
    st->set = NULL;

    st->set = lyd_find_path(st->dt, "(//*)[1] | (//*)[last()] | (//*)[10] | (//*)[8]//.");
    assert_ptr_not_equal(st->set, NULL);
    assert_int_equal(st->set->number, 15);
    ly_set_free(st->set);
    st->set = NULL;
}

int main(void)
{
    const struct CMUnitTest tests[] = {
                    cmocka_unit_test_setup_teardown(test_invalid, setup_f, teardown_f),
                    cmocka_unit_test_setup_teardown(test_simple, setup_f, teardown_f),
                    cmocka_unit_test_setup_teardown(test_advanced, setup_f, teardown_f),
                    cmocka_unit_test_setup_teardown(test_functions_operators, setup_f, teardown_f),
                    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}
