/*
    Copyright (C) 2011-2017, Millistream Market Data <support@millistream.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

/* This will test the encoder part of the API */

#include <sys/time.h>
#include "../src/common.h"
#include "../src/encode.h"

#ifndef CLOCK_MONOTONIC_RAW
# define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
#endif

struct test {
	uint32_t tag;
	uint8_t *out;
	int len;
	char *in;
	uint64_t uin;
	int decimals;
};

struct test2 {
	uint8_t *out;
	int len;
	int hour;
	int min;
	int sec;
	int msec;
};

struct test3 {
	uint8_t *out;
	int len;
	int year;
	int mon;
	int day;
};

struct test4 {
	uint8_t *out;
	int len;
	int hour;
	int min;
	int sec;
	int nsec;
};

struct itest {
	uint8_t *out;
	int len;
	char *in;
	int64_t iin;
	int decimals;
};

struct test numeric_test[] = {
	{ 0, (uint8_t*)"\x00\x00", 2, "28" },
	{ 0, (uint8_t*)"\x00\xFF", 2, "283" },
	{ 0, (uint8_t*)"\x64", 1, "0" },
	{ 0, (uint8_t*)"\x7F", 1, "27" },
	{ 0, (uint8_t*)"\x63\x00", 2, "0.99" },
	{ 0, (uint8_t*)"\x63\xFF", 2, "255.99" },
	{ 0, (uint8_t*)"\x01\x0A", 2, "10.01" },
	{ 0, (uint8_t*)"\x8F\x70\xB7", 3, "103.0001" },
	{ 0, (uint8_t*)"\x8F\xF9\xFF", 3, "104.857" },
	{ 0, (uint8_t*)"\x8F\xFF\xFF", 3, "104.8576" },
	{ 0, (uint8_t*)"\x80\x00\x00", 3, "0.0001" },
	{ 0, (uint8_t*)"\x9F\xFF\xFF", 3, "10741.76" },
	{ 0, (uint8_t*)"\x90\x00\x00", 3, "256.01" },
	{ 0, (uint8_t*)"\x90\xEF\x0A", 3, "284" },
	{ 0, (uint8_t*)"\xA2\x81", 2, "2000" },
	{ 0, (uint8_t*)"\xAF\x81", 2, "20000000000000000" },
	{ 0, (uint8_t*)"\xA0\x18\x4B\x65\x32\x18\x4B\x65\x32\x98", 10, "18446744073709551610" },
	{ 0, (uint8_t*)"\xBF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", 11, "18446.744073709551615" },
	{ 0, (uint8_t*)"\xB7\x03\x61\x2C\x80", 5, "1.0000001" },
	{ 0, (uint8_t*)"\xBF\x80", 2, "0.000000000000001" },
	{ 0, (uint8_t*)"\xCF\x80", 2, "-0.000000000000001" },
	{ 0, (uint8_t*)"\xC7\x03\x61\x2C\x80", 5, "-1.0000001" },
	{ 0, (uint8_t*)"\xCF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", 11, "-18446.744073709551615" },
	{ 0, (uint8_t*)"\xF0", 1, NULL },
};

struct test uint_test[] = {
	{ 0, (uint8_t*)"\x00\x00", 2, NULL, 28, 0 },
	{ 0, (uint8_t*)"\x00\xFF", 2, NULL, 283, 0 },
	{ 0, (uint8_t*)"\x64", 1, NULL, 0, 0 },
	{ 0, (uint8_t*)"\x7F", 1, NULL, 27, 0 },
	{ 0, (uint8_t*)"\x63\x00", 2, NULL, 99, 2 },
	{ 0, (uint8_t*)"\x63\xFF", 2, NULL, 25599, 2 },
	{ 0, (uint8_t*)"\x01\x0A", 2, NULL, 1001, 2 },
	{ 0, (uint8_t*)"\x8F\x70\xB7", 3, NULL, 1030001, 4 },
	{ 0, (uint8_t*)"\x8F\xF9\xFF", 3, NULL, 104857, 3 },
	{ 0, (uint8_t*)"\x8F\xFF\xFF", 3, NULL, 1048576, 4 },
	{ 0, (uint8_t*)"\x80\x00\x00", 3, NULL, 1, 4 },
	{ 0, (uint8_t*)"\x9F\xFF\xFF", 3, NULL, 1074176, 2 },
	{ 0, (uint8_t*)"\x90\x00\x00", 3, NULL, 25601, 2 },
	{ 0, (uint8_t*)"\x90\xEF\x0A", 3, NULL, 284, 0 },
	{ 0, (uint8_t*)"\xA2\x81", 2, NULL, 2000, 0 },
	{ 0, (uint8_t*)"\xAF\x81", 2, NULL, 20000000000000000, 0 },
	{ 0, (uint8_t*)"\xA0\x18\x4B\x65\x32\x18\x4B\x65\x32\x98", 10, NULL, __UINT64_C(18446744073709551610), 0 },
	{ 0, (uint8_t*)"\xBF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", 11, NULL, __UINT64_C(18446744073709551615),  15},
	{ 0, (uint8_t*)"\xB7\x03\x61\x2C\x80", 5, NULL, 10000001,  7},
	{ 0, (uint8_t*)"\xBF\x80", 2, NULL, 1, 15 },
};

struct itest int_test[] = {
	{ (uint8_t*)"\x00\x00", 2, NULL, 28, 0 },
	{ (uint8_t*)"\x00\xFF", 2, NULL, 283, 0 },
	{ (uint8_t*)"\x64", 1, NULL, 0, 0 },
	{ (uint8_t*)"\x7F", 1, NULL, 27, 0 },
	{ (uint8_t*)"\x63\x00", 2, NULL, 99, 2 },
	{ (uint8_t*)"\x63\xFF", 2, NULL, 25599, 2 },
	{ (uint8_t*)"\x01\x0A", 2, NULL, 1001, 2 },
	{ (uint8_t*)"\x8F\x70\xB7", 3, NULL, 1030001, 4 },
	{ (uint8_t*)"\x8F\xF9\xFF", 3, NULL, 104857, 3 },
	{ (uint8_t*)"\x8F\xFF\xFF", 3, NULL, 1048576, 4 },
	{ (uint8_t*)"\x80\x00\x00", 3, NULL, 1, 4 },
	{ (uint8_t*)"\x9F\xFF\xFF", 3, NULL, 1074176, 2 },
	{ (uint8_t*)"\x90\x00\x00", 3, NULL, 25601, 2 },
	{ (uint8_t*)"\x90\xEF\x0A", 3, NULL, 284, 0 },
	{ (uint8_t*)"\xA2\x81", 2, NULL, 2000, 0 },
	{ (uint8_t*)"\xAF\x81", 2, NULL, 20000000000000000, 0 },
	{ (uint8_t*)"\xC7\x03\x61\x2C\x80", 5, NULL, -10000001, 7 },
	{ (uint8_t*)"\xB7\x03\x61\x2C\x80", 5, NULL, 10000001,  7},
	{ (uint8_t*)"\xBF\x80", 2, NULL, 1, 15 },
};

struct test date_test[] = {
	{ 0, (uint8_t*)"\xDD\xB3\xB7", 3, "2006-09-19" },
	{ 0, (uint8_t*)"\xDD\x40\xBB", 3, "2008-10" },
	{ 0, (uint8_t*)"\xDD\x00\xBA", 3, "2008" },
	{ 0, (uint8_t*)"\xDD\x01\xBA", 3, "2008-H1" },
	{ 0, (uint8_t*)"\xDD\x04\xBA", 3, "2008-T2" },
	{ 0, (uint8_t*)"\xDD\x09\xBA", 3, "2008-Q4" },
	{ 0, (uint8_t*)"\xDD\x1F\xBA", 3, "2008-W22" },
	{ 0, (uint8_t*)"\xDD\xBF\xBB", 3, "2008-W53" },
};

struct test3 date_test2[] = {
	{ (uint8_t*)"\xDD\xB3\xB7", 3, 2006,  9, 19    }, /* 2006-09-19 */
	{ (uint8_t*)"\xDD\x40\xBB", 3, 2008, 10, 0     }, /* 2008-10 */
	{ (uint8_t*)"\xDD\x00\xBA", 3, 2008,  0, 0     }, /* 2008 */
	{ (uint8_t*)"\xDD\x01\xBA", 3, 2008,  0, 1     }, /* 2008-H1 */
	{ (uint8_t*)"\xDD\x04\xBA", 3, 2008,  0, 2+2   }, /* 2008-T2 */
	{ (uint8_t*)"\xDD\x09\xBA", 3, 2008,  0, 4+5   }, /* 2008-Q4 */
	{ (uint8_t*)"\xDD\x1F\xBA", 3, 2008,  0, 22+9  }, /* 2008-W22 */
	{ (uint8_t*)"\xDD\xBF\xBB", 3, 2008, 13, 53-22 }, /* 2008-W53 */
};

struct test time_test[] = {
	{ 0, (uint8_t*)"\xF6\x00\x00", 3, "00:00:00" },
	{ 0, (uint8_t*)"\xF7\x7F\x51", 3, "23:59:58" },
	{ 0, (uint8_t*)"\xF4\x19\x58\x26\x05", 5, "23:59:59.001" },
	{ 0, (uint8_t*)"\xF4\xFF\x5B\x26\x05", 5, "23:59:59.999" },
	{ 0, (uint8_t*)"\xF4\x94\x55\xB4\xCE\x36\x01", 7, "23:59:59.000000001" },
	{ 0, (uint8_t*)"\xF4\x94\x91\x4E\xCE\xFF\xFF", 7, "23:59:59.999999999" },
};

struct test2 time_test2[] = {
	{ (uint8_t*)"\xF6\x00\x00", 3,  0,  0,  0, 0 },
	{ (uint8_t*)"\xF7\x7F\x51", 3, 23, 59, 58, 0 },
	{ (uint8_t*)"\xF4\x19\x58\x26\x05", 5, 23, 59, 59,   1 },
	{ (uint8_t*)"\xF4\xFF\x5B\x26\x05", 5, 23, 59, 59, 999 },
};

struct test4 time_test3[] = {
	{ (uint8_t*)"\xF6\x00\x00", 3,  0,  0,  0, 0 },
	{ (uint8_t*)"\xF7\x7F\x51", 3, 23, 59, 58, 0 },
	{ (uint8_t*)"\xF4\x00\x00\x00\x80\x00\x01", 7,  0,  0,  0,         1 },
	{ (uint8_t*)"\xF4\x94\x55\xB4\xCE\x36\x01", 7, 23, 59, 59,         1 },
	{ (uint8_t*)"\xF4\x94\x91\x4E\xCE\xFF\xFF", 7, 23, 59, 59, 999999999 },
};

struct test string_test[] = {
	{ 0,         (uint8_t*)"\xF1\x61\x61\x61\x42\x42\x42\x63\x63\x63\x00", 11, "aaaBBBccc", 9 },
	{ 0,         (uint8_t*)"\xF1\x53\x45\x30\x30\x30\x30\x31\x30\x38\x36\x35\x36\x00", 14, "SE0000108656", 12 },
	{ 0,         (uint8_t*)"\xF1\xC3\xA5\xC3\xA4\xC3\xB6\xC3\x85\xC3\x84\xC3\x96\xC3\xA6\xC3\x9F\xE2\x82\xAC\xC2\xB5\x00", 23, "åäöÅÄÖæß€µ", 21 },
	{ MDF_F_MMT, (uint8_t*)"\xF1\x31\x32\x67\x50\x00", 6, "12-------P----", 14 },
	{ MDF_F_MMT, (uint8_t*)"\xF1\x34\x36\x62\x58\x61\x42\x62\x50\x61\x33\x00", 12, "46--X-B--P-3--", 14 },
};

struct test list_test[] = {
	{ 0, (uint8_t*)"\xFA\x80\x9C", 3, "28" },
	{ 0, (uint8_t*)"\xFA\x81\x9C\x9C", 4, "28 28" },
	{ 0, (uint8_t*)"\xFA\x82\x9C\x9C\x01\xD7", 6, "28 28 343" },
	{ 0, (uint8_t*)"\xFA\x80\x9C", 3, "=28" },
	{ 0, (uint8_t*)"\xFA\x81\x9C\x9C", 4, "=28 28" },
	{ 0, (uint8_t*)"\xFA\x82\x9C\x9C\x01\xD7", 6, "=28 28 343" },
	{ 0, (uint8_t*)"\xF8\x80\x9C", 3, "+28" },
	{ 0, (uint8_t*)"\xF8\x81\x9C\x9C", 4, "+28 28" },
	{ 0, (uint8_t*)"\xF8\x82\x9C\x9C\x01\xD7", 6, "+28 28 343" },
	{ 0, (uint8_t*)"\xF9\x80\x9C", 3, "-28" },
	{ 0, (uint8_t*)"\xF9\x81\x9C\x9C", 4, "-28 28" },
	{ 0, (uint8_t*)"\xF9\x82\x9C\x9C\x01\xD7", 6, "-28 28 343" },
};

int iterations;

static int perform_test (struct test *test, const int num, int (*test_function)(mdf_message_t message, const uint32_t tag, const char *value), const char * const testf)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (test_function (message, test[i].tag, test[i].in) == 0) {
			printf ("test:%d (%s). Encoding of \"%s\". Function returned 0. (failed)\n", i + 1, testf, test[i].in);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (%s). Encoding of \"%s\". Should Be:\"0x", i + 1, testf, test[i].in == NULL ? "<null>" : test[i].in);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static int perform_stringtest2 (struct test *test, const int num)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (mdf_message_add_string2 (message, test[i].tag, test[i].in, test[i].uin) == 0) {
			printf ("test:%d (mdf_message_add_string2). Encoding of \"%s\". Function returned 0. (failed)\n", i + 1, test[i].in);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (mdf_message_add_string2). Encoding of \"%s\". Should Be:\"0x", i + 1, test[i].in);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static int perform_uinttest (struct test *test, const int num)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (mdf_message_add_uint (message, 0, test[i].uin, test[i].decimals) == 0) {
			printf ("test:%d (mdf_message_add_uint). Encoding of \"%s\". Function returned 0. (failed)\n", i + 1, test[i].in);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (mdf_message_add_uint). Encoding of \"%s\". Should Be:\"0x", i + 1, test[i].in == NULL ? "<null>" : test[i].in);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static int perform_inttest (struct itest *test, const int num)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (mdf_message_add_int (message, 0, test[i].iin, test[i].decimals) == 0) {
			printf ("test:%d (mdf_message_add_int). Encoding of \"%s\". Function returned 0. (failed)\n", i + 1, test[i].in);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (mdf_message_add_int). Encoding of \"%s\". Should Be:\"0x", i + 1, test[i].in == NULL ? "<null>" : test[i].in);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static int perform_timetest2 (struct test2 *test, const int num)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (mdf_message_add_time2 (message, 0, test[i].hour, test[i].min, test[i].sec, test[i].msec) == 0) {
			printf ("test:%d (mdf_message_add_time2). Encoding of \"%02d:%02d:%02d.%03d\". Function returned 0. (failed)\n", i + 1, test[i].hour, test[i].min, test[i].sec, test[i].msec);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (mdf_message_add_time2). Encoding of \"%02d:%02d:%02d.%03d\". Should Be:\"0x", i + 1, test[i].hour, test[i].min, test[i].sec, test[i].msec);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static int perform_timetest3 (struct test4 *test, const int num)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (mdf_message_add_time3 (message, 0, test[i].hour, test[i].min, test[i].sec, test[i].nsec) == 0) {
			printf ("test:%d (mdf_message_add_time3). Encoding of \"%02d:%02d:%02d.%09d\". Function returned 0. (failed)\n", i + 1, test[i].hour, test[i].min, test[i].sec, test[i].nsec);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (mdf_message_add_time3). Encoding of \"%02d:%02d:%02d.%09d\". Should Be:\"0x", i + 1, test[i].hour, test[i].min, test[i].sec, test[i].nsec);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static int perform_datetest2 (struct test3 *test, const int num)
{
	int fail = 0;
	int i;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return 1;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return 1;

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		int j;

		message->messages[0].fields = 0;
		message->data_used = 0;
		message->fields_num = 0;

		if (mdf_message_add_date2 (message, 0, test[i].year, test[i].mon, test[i].day) == 0) {
			printf ("test:%d (mdf_message_add_date2). Encoding of \"%d-%02d-%02d\". Function returned 0. (failed)\n", i + 1, test[i].year, test[i].mon, test[i].day);
			fail = 1;
			continue;
		}

		if (message->data_used == test[i].len && memcmp (message->data, test[i].out, test[i].len) == 0)
			continue;

		fail = 1;

		printf ("test:%d (mdf_message_add_date2). Encoding of \"%d-%02d-%02d\". Should Be:\"0x", i + 1, test[i].year, test[i].mon, test[i].day);

		for (j = 0; j < test[i].len; j++)
			printf ("%02X", test[i].out[j]);

		printf ("\" Was:\"0x");

		for (j = 0; j < message->data_used; j++)
			printf ("%02X", message->data[j]);

		printf ("\" (failed)\n");
	}

	return fail;
}

static void timespec_diff (struct timespec *ts1, struct timespec *ts2)
{
	ts2->tv_sec -= ts1->tv_sec;

	if (ts2->tv_nsec > ts1->tv_nsec) {
		ts2->tv_nsec -= ts1->tv_nsec;
	} else {
		ts2->tv_sec--;
		ts2->tv_nsec = 1000000000 - (ts1->tv_nsec - ts2->tv_nsec);
	}
}

static uint64_t calc_performance (struct timespec *ts1, struct timespec *ts2, char *prefix, uint64_t nominator)
{
	timespec_diff (ts1, ts2);

	if (ts2->tv_sec != 0 || ts2->tv_nsec != 0)
		nominator /= ts2->tv_sec * 1000000000 + ts2->tv_nsec;

	if (nominator > 1000000000)
		*prefix = 'G', nominator /= 1000000000;
	else if (nominator > 1000000)
		*prefix = 'M', nominator /= 1000000;
	else if (nominator > 1000)
		*prefix = 'k', nominator /= 1000;
	else
		*prefix = '\0';

	return nominator;
}

static void performance_test (struct test *test, const int num, int (*test_function)(mdf_message_t message, const uint32_t tag, const char *value), const char * const testf)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("%s:\n", testf);

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			test_function (message, test[i].tag, test[i].in);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of \"%s\" = %" PRIu64 "%c/sec\n", i + 1, test[i].in == NULL ? "<null>" : test[i].in, result, prefix);
	}

	printf ("\n");
}

static void performance_uint (struct test *test, const int num)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("mdf_message_add_uint:\n");

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			mdf_message_add_uint (message, 0, test[i].uin, test[i].decimals);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of %" PRIu64 " = %" PRIu64 "%c/sec\n", i + 1, test[i].uin, result, prefix);
	}

	printf ("\n");
}

static void performance_int (struct itest *test, const int num)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("mdf_message_add_int:\n");

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			mdf_message_add_int (message, 0, test[i].iin, test[i].decimals);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of %" PRId64 " = %" PRIu64 "%c/sec\n", i + 1, test[i].iin, result, prefix);
	}

	printf ("\n");
}

static void performance_time2 (struct test2 *test, const int num)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("mdf_message_add_time2:\n");

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			mdf_message_add_time2 (message, 0, test[i].hour, test[i].min, test[i].sec, test[i].msec);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of \"%02d:%02d:%02d.%03d\" = %" PRIu64 "%c/sec\n", i + 1, test[i].hour, test[i].min, test[i].sec, test[i].msec, result, prefix);
	}

	printf ("\n");
}

static void performance_time3 (struct test4 *test, const int num)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("mdf_message_add_time3:\n");

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			mdf_message_add_time3 (message, 0, test[i].hour, test[i].min, test[i].sec, test[i].nsec);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of \"%02d:%02d:%02d.%09d\" = %" PRIu64 "%c/sec\n", i + 1, test[i].hour, test[i].min, test[i].sec, test[i].nsec, result, prefix);
	}

	printf ("\n");
}

static void performance_date2 (struct test3 *test, const int num)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("mdf_message_add_date2:\n");

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			mdf_message_add_date2 (message, 0, test[i].year, test[i].mon, test[i].day);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of \"%d-%02d-%02d\" = %" PRIu64 "%c/sec\n", i + 1, test[i].year, test[i].mon, test[i].day, result, prefix);
	}

	printf ("\n");
}

static void performance_string2 (struct test *test, const int num, const int utf8_validation)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;
	struct mdf_message_s *message = mdf_message_create ();

	if (message == NULL)
		return;

	mdf_message_set_property (message, MDF_MSG_OPT_UTF8, utf8_validation);

	if (mdf_message_add (message, 0, MDF_M_QUOTE) == 0)
		return;

	printf ("mdf_message_add_string2 (utf8 validation: %d):\n", utf8_validation);

	/* prepare a large enough buffer so we don't have to resize on the fly */
	message->messages_max = 1;
	message->messages = calloc (1, sizeof (struct message) * message->messages_max);
	message->fields_max = 1;
	message->fields = calloc (1, sizeof (struct field) * message->fields_max);
	message->data_size = 1024;
	message->data = calloc (1, message->data_size);

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			message->messages[0].fields = 0;
			message->data_used = 0;
			message->fields_num = 0;
			mdf_message_add_string2 (message, test[i].tag, test[i].in, test[i].uin);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

		char prefix;
		uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

		printf ("Test #%d Encoding of \"%s\" = %" PRIu64 "%c/sec\n", i + 1, test[i].in, result, prefix);
	}

	printf ("\n");
}

static void performance_msg ()
{
	const uint64_t nominator = iterations * (uint64_t)1000000000;

	mdf_message_t message = mdf_message_create ();

	if (message == NULL)
		return;

	printf ("mdf_message_add (cold): ");

	struct timespec ts1, ts2;

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

	int j;
	for (j = 0; j < iterations; j++) {
		mdf_message_add (message, j, MDF_M_INSTRUMENTRESET);
	}

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

	char prefix;
	uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

	printf ("%" PRIu64 "%c/sec\n", result, prefix);

	mdf_message_reset (message);

	printf ("mdf_message_add (hot): ");

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

	for (j = 0; j < iterations; j++) {
		mdf_message_add (message, j, MDF_M_INSTRUMENTRESET);
	}

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

	result = calc_performance (&ts1, &ts2, &prefix, nominator);
	printf ("%" PRIu64 "%c/sec\n", result, prefix);

	mdf_message_destroy (message);
}

static void performance_fields ()
{
	const uint64_t nominator = iterations * (uint64_t)1000000000;

	mdf_message_t message = mdf_message_create ();

	if (message == NULL)
		return;

	printf ("mdf_message_add_numeric(null) (cold): ");
	mdf_message_add (message, 0, MDF_M_QUOTE);

	struct timespec ts1, ts2;

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

	int j;
	for (j = 0; j < iterations; j++) {
		mdf_message_add_numeric (message, j, NULL);
	}

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

	char prefix;
	uint64_t result = calc_performance (&ts1, &ts2, &prefix, nominator);

	printf ("%" PRIu64 "%c/sec\n", result, prefix);

	mdf_message_reset (message);
	mdf_message_add (message, 0, MDF_M_QUOTE);

	printf ("mdf_message_add_numeric(null) (hot): ");

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

	for (j = 0; j < iterations; j++) {
		mdf_message_add_numeric (message, j, NULL);
	}

	clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);

	result = calc_performance (&ts1, &ts2, &prefix, nominator);
	printf ("%" PRIu64 "%c/sec\n", result, prefix);

	mdf_message_destroy (message);
}

int main (int argc, char **argv)
{
	int fail = 0;

	if (perform_test (numeric_test, NELEMS (numeric_test), mdf_message_add_numeric, "mdf_message_add_numeric") != 0)
		fail = 1;

	if (perform_uinttest (uint_test, NELEMS (uint_test)) != 0)
		fail = 1;

	if (perform_inttest (int_test, NELEMS (int_test)) != 0)
		fail = 1;

	if (perform_test (date_test, NELEMS (date_test), mdf_message_add_date, "mdf_message_add_date") != 0)
		fail = 1;

	if (perform_datetest2 (date_test2, NELEMS (date_test2)) != 0)
		fail = 1;

	if (perform_test (time_test, NELEMS (time_test), mdf_message_add_time, "mdf_message_add_time") != 0)
		fail = 1;

	if (perform_timetest2 (time_test2, NELEMS (time_test2)) != 0)
		fail = 1;

	if (perform_timetest3 (time_test3, NELEMS (time_test3)) != 0)
		fail = 1;

	if (perform_test (string_test, NELEMS (string_test), mdf_message_add_string, "mdf_message_add_string") != 0)
		fail = 1;

	if (perform_stringtest2 (string_test, NELEMS (string_test)) != 0)
		fail = 1;

	if (perform_test (list_test, NELEMS (list_test), mdf_message_add_list, "mdf_message_add_list") != 0)
		fail = 1;

	/* no need to do the performance test if we fail with the encoding! */
	if (fail == 1)
		return fail;

	char *env = getenv ("LIBMDF_PERFTEST");

	if (env == NULL)
		return fail;

	iterations = strtol (env, NULL, 10);

	if (iterations == 0)
		iterations = 10000000;

	printf ("\n*** Performance tests with %d iterations***\n", iterations);
	performance_test (numeric_test, NELEMS (numeric_test), mdf_message_add_numeric, "mdf_message_add_numeric");
	performance_uint (uint_test, NELEMS (uint_test));
	performance_int (int_test, NELEMS (int_test));
	performance_test (date_test, NELEMS (date_test), mdf_message_add_date, "mdf_message_add_date");
	performance_date2 (date_test2, NELEMS (date_test2));
	performance_test (time_test, NELEMS (time_test), mdf_message_add_time, "mdf_message_add_time");
	performance_time2 (time_test2, NELEMS (time_test2));
	performance_time3 (time_test3, NELEMS (time_test3));
	performance_test (string_test, NELEMS (string_test), mdf_message_add_string, "mdf_message_add_string");
	performance_string2 (string_test, NELEMS (string_test), 1);
	performance_string2 (string_test, NELEMS (string_test), 0);
	performance_test (list_test, NELEMS (list_test), mdf_message_add_list, "mdf_message_add_list");
	performance_msg ();
	performance_fields ();

	return fail;
}

