/*
 * Copyright (c) 2014 by Farsight Security, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * test_writer_hello: simple "hello world" fstrm_io test.
 *
 * Instantiates a dummy writer implementation which captures all writes, then
 * verifies that the correct byte stream sequence was generated by the library.
 */

#include <arpa/inet.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fstrm.h>

#include "libmy/my_alloc.h"
#include "libmy/print_string.h"

static const char *test_content_type = "test:hello";
static const int num_iterations = 1000;

struct test_buf {
	struct test_buf		*next;
	size_t			len;
	void			*data;
};

static struct test_buf		t_head;
static struct test_buf		*t_cur = &t_head;

static struct test_buf		h_head;
static struct test_buf		*h_cur = &h_head;

static int			num_control_frames = 0;
static int			num_iovecs = 0;

static fstrm_res
test_rdwr_destroy(void *obj)
{
	fprintf(stderr, "%s: called\n", __func__);
	return fstrm_res_success;
}

static fstrm_res
test_rdwr_open(void *obj)
{
	fprintf(stderr, "%s: called\n", __func__);
	return fstrm_res_success;
}

static fstrm_res
test_rdwr_close(void *obj)
{
	fprintf(stderr, "%s: called\n", __func__);
	return fstrm_res_success;
}

static fstrm_res
test_rdwr_write(void *obj, const struct iovec *iov, int iovcnt)
{
	size_t nbytes = 0;

	fprintf(stderr, "%s: called, iov= %p, iovcnt= %d\n",
		__func__, iov, iovcnt);

	assert(iovcnt > 0);

	assert(iov[0].iov_len >= 4);
	if (memcmp(iov[0].iov_base, "\x00\x00\x00\x00", 4) == 0) {
		fprintf(stderr, "%s: got a control frame (%zd bytes): ",
			__func__, iov[0].iov_len);
		print_string(iov[0].iov_base, iov[0].iov_len, stderr);
		fputc('\n', stderr);
		num_control_frames++;
		return fstrm_res_success;
	}

	for (int i = 0; i < iovcnt; i++) {
		t_cur->next = my_calloc(1, sizeof(struct test_buf));
		t_cur = t_cur->next;
		t_cur->len = iov[i].iov_len;
		t_cur->data = my_calloc(1, t_cur->len);
		memmove(t_cur->data, iov[i].iov_base, iov[i].iov_len);
		nbytes += iov[i].iov_len;
	}
	num_iovecs += iovcnt;

	fprintf(stderr, "%s: got %zd bytes\n", __func__, nbytes);
	return fstrm_res_success;
}

static int
do_checks(void)
{
	struct test_buf *h, *h_next;
	struct test_buf *t, *t_next;

	h = h_head.next;
	t = t_head.next;
	if (!h || !t)
		return EXIT_FAILURE;

	if (num_iovecs != 2*num_iterations) {
		fprintf(stderr, "%s: didn't get the right number of iovec's\n", __func__);
		fprintf(stderr, "%s: num_iovecs= %d, num_iterations= %d\n",
			__func__, num_iovecs, num_iterations);
		return EXIT_FAILURE;
	}

	if (num_control_frames != 2) {
		fprintf(stderr, "%s: didn't get the right number of control frames\n", __func__);
		fprintf(stderr, "%s: num_control_frames = %d\n",
			__func__, num_control_frames);
		return EXIT_FAILURE;
	}

	for (;;) {
		if (!h)
			break;

		assert(h != NULL);
		assert(t != NULL);

		uint32_t len_wire, len;

		assert(t->len == sizeof(len_wire));
		memmove(&len_wire, t->data, sizeof(len_wire));
		len = ntohl(len_wire);

		assert(t->next != NULL);
		t = t->next;

		assert(len == t->len);
		assert(memcmp(h->data, t->data, len) == 0);

		t = t->next;
		h = h->next;
	}

	fprintf(stderr, "%s: all checks succeeded\n", __func__);

	/* cleanup */

	h = h_head.next;
	for (;;) {
		if (!h)
			break;
		h_next = h->next;
		free(h->data);
		free(h);
		h = h_next;
	}

	t = t_head.next;
	for (;;) {
		if (!t)
			break;
		t_next = t->next;
		free(t->data);
		free(t);
		t = t_next;
	}

	return EXIT_SUCCESS;
}

int
main(void)
{
	fstrm_res res;
	struct fstrm_iothr *iothr = NULL;
	struct fstrm_iothr_options *iothr_opt = NULL;
	struct fstrm_iothr_queue *ioq = NULL;
	struct fstrm_rdwr *rdwr = NULL;
	struct fstrm_writer *w = NULL;
	struct fstrm_writer_options *wopt = NULL;

	rdwr = fstrm_rdwr_init(NULL);
	fstrm_rdwr_set_destroy(rdwr, test_rdwr_destroy);
	fstrm_rdwr_set_open(rdwr, test_rdwr_open);
	fstrm_rdwr_set_close(rdwr, test_rdwr_close);
	fstrm_rdwr_set_write(rdwr, test_rdwr_write);

	wopt = fstrm_writer_options_init();
	res = fstrm_writer_options_add_content_type(wopt,
						    test_content_type,
						    strlen(test_content_type));
	assert(res == fstrm_res_success);

	w = fstrm_writer_init(wopt, &rdwr);
	// 'rdwr' is now owned by 'w'.

	fstrm_writer_options_destroy(&wopt);

	iothr = fstrm_iothr_init(iothr_opt, &w);
	if (iothr == NULL) {
		fprintf(stderr, "fstrm_io_init() failed.\n");
		fstrm_writer_destroy(&w);
		return EXIT_FAILURE;
	}
	// 'w' is now owned by 'iothr'.

	fstrm_iothr_options_destroy(&iothr_opt);

	ioq = fstrm_iothr_get_input_queue(iothr);
	if (ioq == NULL) {
		fprintf(stderr, "fstrm_iothr_get_input_queue() failed\n");
		fstrm_iothr_destroy(&iothr);
		return EXIT_FAILURE;
	}

	for (int i = 0; i < num_iterations; i++) {
		char buf[100];
		char *bytes;

		buf[0] = '\0';
		sprintf(buf, "hello world #%d", i);

		h_cur->next = my_calloc(1, sizeof(*h_cur->next));
		h_cur = h_cur->next;
		h_cur->len = strlen(buf);
		h_cur->data = my_strdup(buf);

		bytes = my_strdup(buf);

		for (;;) {
			res = fstrm_iothr_submit(iothr, ioq, bytes, strlen(bytes),
						 fstrm_free_wrapper, NULL);
			if (res == fstrm_res_success) {
				break;
			} else if (res == fstrm_res_again) {
				poll(NULL, 0, 1); /* sleep for a millisecond */
				continue;
			} else {
				free(bytes);
				fprintf(stderr, "fstrm_iothr_submit() failed\n");
				fstrm_iothr_destroy(&iothr);
				return EXIT_FAILURE;
			}
		}
	}

	fstrm_iothr_destroy(&iothr);

	fprintf(stderr, "num_control_frames = %d\n", num_control_frames);

	return do_checks();
}
