/**
 * \file test_io.c
 * \author Radek Krejci <rkrejci@cesnet.cz>
 * \brief libnetconf2 tests - input/output functions
 *
 * Copyright (c) 2015 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 <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <cmocka.h>
#include <libyang/libyang.h>

#include <messages_p.h>
#include <session_p.h>

#include "ln2_test.h"

struct wr {
    struct nc_session *session;
    struct nc_rpc *rpc;
};

static int
setup_write(void **state)
{
    (void) state; /* unused */
    int fd, pipes[2];
    struct wr *w;

    w = malloc(sizeof *w);
    w->session = calloc(1, sizeof *w->session);
    ly_ctx_new(ly_yang_module_dir(), 0, &w->session->ctx);
    ly_ctx_set_searchdir(w->session->ctx, TESTS_DIR "/data/modules");

    /* ietf-netconf */
    fd = open(TESTS_DIR "/data/modules/ietf-netconf.yin", O_RDONLY);
    if (fd == -1) {
        free(w->session);
        free(w);
        return -1;
    }

    lys_parse_fd(w->session->ctx, fd, LYS_IN_YIN, NULL);
    close(fd);

    assert_return_code(pipe(pipes), errno);

    w->session->status = NC_STATUS_RUNNING;
    w->session->version = NC_PROT_VERSION_10;
    w->session->opts.client.msgid = 999;
    w->session->ti_type = NC_TI_FD;
    w->session->io_lock = malloc(sizeof *w->session->io_lock);
    pthread_mutex_init(w->session->io_lock, NULL);
    w->session->ti.fd.in = pipes[0];
    w->session->ti.fd.out = pipes[1];

    /* get rpc to write */
    w->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING);
    assert_non_null(w->rpc);

    *state = w;

    return 0;
}

static int
teardown_write(void **state)
{
    struct wr *w = (struct wr *)*state;

    nc_rpc_free(w->rpc);
    close(w->session->ti.fd.in);
    w->session->ti.fd.in = -1;
    close(w->session->ti.fd.out);
    w->session->ti.fd.out = -1;
    nc_session_free(w->session, NULL);
    free(w);
    *state = NULL;

    return 0;
}

static void
test_write_rpc(void **state)
{
    struct wr *w = (struct wr *)*state;
    uint64_t msgid;
    NC_MSG_TYPE type;

    w->session->side = NC_CLIENT;

    do {
        type = nc_send_rpc(w->session, w->rpc, 1000, &msgid);
    } while (type == NC_MSG_WOULDBLOCK);

    assert_int_equal(type, NC_MSG_RPC);

    assert_int_equal(write(w->session->ti.fd.out, "\n", 1), 1);
}

static void
test_write_rpc_10(void **state)
{
    struct wr *w = (struct wr *)*state;

    w->session->version = NC_PROT_VERSION_10;

    return test_write_rpc(state);
}

static void
test_write_rpc_11(void **state)
{
    struct wr *w = (struct wr *)*state;

    w->session->version = NC_PROT_VERSION_11;

    return test_write_rpc(state);
}

static void
test_write_rpc_bad(void **state)
{
    struct wr *w = (struct wr *)*state;
    uint64_t msgid;
    NC_MSG_TYPE type;

    w->session->side = NC_SERVER;
    pthread_mutex_init(&w->session->opts.server.rpc_lock, NULL);
    pthread_cond_init(&w->session->opts.server.rpc_cond, NULL);
    w->session->opts.server.rpc_inuse = 0;

    do {
        type = nc_send_rpc(w->session, w->rpc, 1000, &msgid);
    } while (type == NC_MSG_WOULDBLOCK);

    assert_int_equal(type, NC_MSG_ERROR);
}

static void
test_write_rpc_10_bad(void **state)
{
    struct wr *w = (struct wr *)*state;

    w->session->version = NC_PROT_VERSION_10;

    return test_write_rpc_bad(state);
}

static void
test_write_rpc_11_bad(void **state)
{
    struct wr *w = (struct wr *)*state;

    w->session->version = NC_PROT_VERSION_11;

    return test_write_rpc_bad(state);
}

int
main(void)
{
    const struct CMUnitTest io[] = {
        cmocka_unit_test_setup_teardown(test_write_rpc_10, setup_write, teardown_write),
        cmocka_unit_test_setup_teardown(test_write_rpc_10_bad, setup_write, teardown_write),
        cmocka_unit_test_setup_teardown(test_write_rpc_11, setup_write, teardown_write),
        cmocka_unit_test_setup_teardown(test_write_rpc_11_bad, setup_write, teardown_write)
    };

    return cmocka_run_group_tests(io, NULL, NULL);
}
