/*
 * Copyright (C) 2013 Tobias Brunner
 * Hochschule fuer Technik Rapperswil
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program 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 General Public License
 * for more details.
 */

#include "test_suite.h"

#include <bio/bio_writer.h>

/*******************************************************************************
 * different integer writes
 */

static inline void verify_int_buffer(chunk_t data, int bits, int val)
{
	size_t i;
	int len = bits / 8;

	ck_assert_int_eq(data.len, (val + 1) * len);
	for (i = 0; i < data.len; i++)
	{
		if ((i + 1) % len)
		{
			ck_assert_int_eq(data.ptr[i], 0);
		}
		else
		{
			ck_assert_int_eq(data.ptr[i], i / len);
		}
	}
}

#define assert_integer_write(init, bits) ({ \
	int i; \
	bio_writer_t *writer = bio_writer_create(init); \
	for (i = 0; i < 16; i++) \
	{ \
		writer->write_uint##bits(writer, i); \
		verify_int_buffer(writer->get_buf(writer), bits, i); \
	} \
	writer->destroy(writer); \
})

START_TEST(test_write_uint8)
{
	/* use default buffer (and increase) size */
	assert_integer_write(0, 8);
	/* force a resize by the given size */
	assert_integer_write(1, 8);
}
END_TEST

START_TEST(test_write_uint16)
{
	assert_integer_write(0, 16);
	assert_integer_write(1, 16);
}
END_TEST

START_TEST(test_write_uint24)
{
	assert_integer_write(0, 24);
	assert_integer_write(1, 24);
}
END_TEST

START_TEST(test_write_uint32)
{
	assert_integer_write(0, 32);
	assert_integer_write(1, 32);
}
END_TEST

START_TEST(test_write_uint64)
{
	assert_integer_write(0, 64);
	assert_integer_write(1, 64);
}
END_TEST

/*******************************************************************************
 * write data / skip
 */

static inline void assert_writer_after_write(bio_writer_t *writer, int count)
{
	chunk_t buf;
	size_t i;

	buf = writer->get_buf(writer);
	ck_assert_int_eq(buf.len, count * 3);
	for (i = 0; i < buf.len; i++)
	{
		ck_assert(buf.ptr[i] == i % 3);
	}
}

START_TEST(test_write_data)
{
	chunk_t buf, data = chunk_from_chars(0x00, 0x01, 0x02);
	bio_writer_t *writer;

	/* no allocation, but default buffer size */
	writer = bio_writer_create(0);
	buf = writer->get_buf(writer);
	ck_assert_int_eq(buf.len, 0);
	ck_assert(buf.ptr == NULL);

	writer->write_data(writer, chunk_empty);
	buf = writer->get_buf(writer);
	ck_assert_int_eq(buf.len, 0);
	ck_assert(buf.ptr == NULL);
	writer->destroy(writer);

	/* custom buffer size, initial buffer allocated */
	writer = bio_writer_create(1);
	buf = writer->get_buf(writer);
	ck_assert_int_eq(buf.len, 0);
	ck_assert(buf.ptr != NULL);

	writer->write_data(writer, chunk_empty);
	buf = writer->get_buf(writer);
	ck_assert_int_eq(buf.len, 0);
	ck_assert(buf.ptr != NULL);
	writer->destroy(writer);

	writer = bio_writer_create(0);

	writer->write_data(writer, data);
	assert_writer_after_write(writer, 1);

	writer->write_data(writer, data);
	assert_writer_after_write(writer, 2);

	writer->write_data(writer, data);
	assert_writer_after_write(writer, 3);

	writer->destroy(writer);
}
END_TEST

START_TEST(test_skip)
{
	chunk_t skipped, buf, data = chunk_from_chars(0x00, 0x01, 0x02);
	bio_writer_t *writer;

	writer = bio_writer_create(4);
	skipped = writer->skip(writer, 3);
	ck_assert_int_eq(skipped.len, 3);
	buf = writer->get_buf(writer);
	ck_assert(skipped.ptr == buf.ptr);
	memset(skipped.ptr, 0, skipped.len);

	writer->write_data(writer, data);
	buf = writer->get_buf(writer);
	ck_assert(chunk_equals(buf, chunk_from_chars(0x00, 0x00, 0x00, 0x00, 0x01, 0x02)));
	writer->destroy(writer);

	writer = bio_writer_create(1);
	skipped = writer->skip(writer, 3);
	memcpy(skipped.ptr, data.ptr, data.len);

	writer->write_data(writer, data);
	assert_writer_after_write(writer, 2);
	writer->destroy(writer);
}
END_TEST

/*******************************************************************************
 * write length followed by data
 */

#define assert_write_data_len(init, bits) ({ \
	bio_writer_t *writer; \
	chunk_t buf, data; \
	int i, len = bits / 8; \
	writer = bio_writer_create(init); \
	writer->write_data##bits(writer, chunk_empty); \
	buf = writer->get_buf(writer); \
	ck_assert_int_eq(buf.len, len); \
	ck_assert_int_eq(buf.ptr[len - 1], 0); \
	writer->destroy(writer); \
	data = chunk_alloca(32); \
	memset(data.ptr, 0, data.len); \
	for (i = 0; i < 32; i++) \
	{ \
		data.ptr[i] = i; \
		data.len = i; \
		writer = bio_writer_create(init); \
		writer->write_data##bits(writer, data); \
		buf = writer->get_buf(writer); \
		ck_assert_int_eq(buf.len, len + i); \
		ck_assert_int_eq(buf.ptr[len - 1], i); \
		ck_assert(chunk_equals(chunk_create(buf.ptr + len, buf.len - len), data)); \
		writer->destroy(writer); \
	} \
})

START_TEST(test_write_data8)
{
	assert_write_data_len(0, 8);
	assert_write_data_len(1, 8);
}
END_TEST

START_TEST(test_write_data16)
{
	assert_write_data_len(0, 16);
	assert_write_data_len(1, 16);
}
END_TEST

START_TEST(test_write_data24)
{
	assert_write_data_len(0, 24);
	assert_write_data_len(1, 24);
}
END_TEST

START_TEST(test_write_data32)
{
	assert_write_data_len(0, 32);
	assert_write_data_len(1, 32);
}
END_TEST


/*******************************************************************************
 * add length header before current data
 */

#define assert_wrap_data(init, bits) ({ \
	bio_writer_t *writer; \
	chunk_t buf, data; \
	int i, len = bits / 8; \
	writer = bio_writer_create(init); \
	writer->wrap##bits(writer); \
	buf = writer->get_buf(writer); \
	ck_assert_int_eq(buf.len, len); \
	ck_assert_int_eq(buf.ptr[len - 1], 0); \
	writer->destroy(writer); \
	data = chunk_alloca(32); \
	memset(data.ptr, 0, data.len); \
	for (i = 0; i < 32; i++) \
	{ \
		data.ptr[i] = i; \
		data.len = i; \
		writer = bio_writer_create(init); \
		writer->write_data(writer, data); \
		writer->wrap##bits(writer); \
		buf = writer->get_buf(writer); \
		ck_assert_int_eq(buf.len, len + i); \
		ck_assert_int_eq(buf.ptr[len - 1], i); \
		ck_assert(chunk_equals(chunk_create(buf.ptr + len, buf.len - len), data)); \
		writer->wrap##bits(writer); \
		buf = writer->get_buf(writer); \
		ck_assert_int_eq(buf.len, 2 * len + i); \
		ck_assert_int_eq(buf.ptr[len - 1], len + i); \
		ck_assert(chunk_equals(chunk_create(buf.ptr + 2 * len, buf.len - 2 * len), data)); \
		writer->destroy(writer); \
	} \
})

START_TEST(test_wrap8)
{
	assert_wrap_data(0, 8);
	assert_wrap_data(1, 8);
}
END_TEST

START_TEST(test_wrap16)
{
	assert_wrap_data(0, 16);
	assert_wrap_data(1, 16);
}
END_TEST

START_TEST(test_wrap24)
{
	assert_wrap_data(0, 24);
	assert_wrap_data(1, 24);
}
END_TEST

START_TEST(test_wrap32)
{
	assert_wrap_data(0, 32);
	assert_wrap_data(1, 32);
}
END_TEST

/*******************************************************************************
 * test data extraction
 */

START_TEST(test_get_buf)
{
	bio_writer_t *writer;
	chunk_t data1, data2;

	writer = bio_writer_create(0);
	writer->write_uint8(writer, 1);
	data1 = writer->get_buf(writer);
	ck_assert_int_eq(data1.len, 1);
	ck_assert(data1.ptr[0] == 1);

	data2 = writer->get_buf(writer);
	ck_assert(chunk_equals(data1, data2));
	ck_assert(data1.ptr == data2.ptr);
	writer->destroy(writer);
}
END_TEST

START_TEST(test_extract_buf)
{
	bio_writer_t *writer;
	chunk_t data1, data2;

	writer = bio_writer_create(0);
	writer->write_uint8(writer, 1);
	data1 = writer->extract_buf(writer);
	ck_assert_int_eq(data1.len, 1);
	ck_assert(data1.ptr[0] == 1);

	data2 = writer->get_buf(writer);
	ck_assert_int_eq(data2.len, 0);
	ck_assert(data2.ptr == NULL);
	data2 = writer->extract_buf(writer);
	ck_assert_int_eq(data2.len, 0);
	ck_assert(data2.ptr == NULL);

	writer->write_uint8(writer, 1);
	data2 = writer->get_buf(writer);
	ck_assert(chunk_equals(data1, data2));
	ck_assert(data1.ptr != data2.ptr);

	writer->destroy(writer);
	chunk_free(&data1);
}
END_TEST

Suite *bio_writer_suite_create()
{
	Suite *s;
	TCase *tc;

	s = suite_create("bio_writer");

	tc = tcase_create("integer writes");
	tcase_add_test(tc, test_write_uint8);
	tcase_add_test(tc, test_write_uint16);
	tcase_add_test(tc, test_write_uint24);
	tcase_add_test(tc, test_write_uint32);
	tcase_add_test(tc, test_write_uint64);
	suite_add_tcase(s, tc);

	tc = tcase_create("data writes/skip");
	tcase_add_test(tc, test_write_data);
	tcase_add_test(tc, test_skip);
	suite_add_tcase(s, tc);

	tc = tcase_create("data length writes");
	tcase_add_test(tc, test_write_data8);
	tcase_add_test(tc, test_write_data16);
	tcase_add_test(tc, test_write_data24);
	tcase_add_test(tc, test_write_data32);
	suite_add_tcase(s, tc);

	tc = tcase_create("wrap writes");
	tcase_add_test(tc, test_wrap8);
	tcase_add_test(tc, test_wrap16);
	tcase_add_test(tc, test_wrap24);
	tcase_add_test(tc, test_wrap32);
	suite_add_tcase(s, tc);

	tc = tcase_create("get/extract");
	tcase_add_test(tc, test_get_buf);
	tcase_add_test(tc, test_extract_buf);
	suite_add_tcase(s, tc);

	return s;
}
