/*
    Copyright (C) 2008-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 decoder part of the API */

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

#ifndef CLOCK_MONOTONIC_RAW
# define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
#endif

struct test {
	uint8_t *in;
	char *out;
};

/* Here we have created one valid field per test so that we can check
 * that the feed decoder is decoding the fields correctly. This should
 * catch misinterpretations in the decoder aswell as catching any
 * little <-> big-endianness */

struct test test1[] = {
	{ (uint8_t*)"\x80\x00\x00", "28" },
	{ (uint8_t*)"\x80\x00\xFF", "283" },
	{ (uint8_t*)"\x80\x64", "0" },
	{ (uint8_t*)"\x80\x7F", "27" },
	{ (uint8_t*)"\x80\x63\x00", "0.99" },
	{ (uint8_t*)"\x80\x63\xFF", "255.99" },
	{ (uint8_t*)"\x80\x01\x0A", "10.01" },
	{ (uint8_t*)"\x80\x8F\xFF\xFF", "104.8576" },
	{ (uint8_t*)"\x80\x80\x00\x00", "0.0001" },
	{ (uint8_t*)"\x80\x9F\xFF\xFF", "10741.76" },
	{ (uint8_t*)"\x80\x90\x00\x00", "256.01" },
	{ (uint8_t*)"\x80\x90\xEF\x0A", "284" },
	{ (uint8_t*)"\x80\xA2\x81", "2000" },
	{ (uint8_t*)"\x80\xAF\x81", "20000000000000000" },
	{ (uint8_t*)"\x80\xA0\x18\x4B\x65\x32\x18\x4B\x65\x32\x98", "18446744073709551610" },
	{ (uint8_t*)"\x80\xBF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", "18446.744073709551615" },
	{ (uint8_t*)"\x80\xB7\x03\x61\x2C\x80", "1.0000001" },
	{ (uint8_t*)"\x80\xBF\x80", "0.000000000000001" },
	{ (uint8_t*)"\x80\xCF\x80", "-0.000000000000001" },
	{ (uint8_t*)"\x80\xC7\x03\x61\x2C\x80", "-1.0000001" },
	{ (uint8_t*)"\x80\xCF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", "-18446.744073709551615" },
	{ (uint8_t*)"\x80\xDD\xB3\xB7", "2006-09-19" },
	{ (uint8_t*)"\x80\xDD\x40\xBB", "2008-10" },
	{ (uint8_t*)"\x80\xDD\x00\xBA", "2008" },
	{ (uint8_t*)"\x80\xDD\x01\xBA", "2008-H1" },
	{ (uint8_t*)"\x80\xDD\x04\xBA", "2008-T2" },
	{ (uint8_t*)"\x80\xDD\x09\xBA", "2008-Q4" },
	{ (uint8_t*)"\x80\xDD\x1F\xBA", "2008-W22" },
	{ (uint8_t*)"\x80\xDD\xBF\xBB", "2008-W53" },
	{ (uint8_t*)"\x80\xF0", NULL },
	{ (uint8_t*)"\x80\xF6\x00\x00", "00:00:00" },
	{ (uint8_t*)"\x80\xF7\x7F\x51", "23:59:58" },
	{ (uint8_t*)"\x80\xF4\x01\x00\x00\x00", "00:00:00.001" },
	{ (uint8_t*)"\x80\xF4\xE7\x03\x00\x00", "00:00:00.999" },
	{ (uint8_t*)"\x80\xF4\x19\x58\x26\x05", "23:59:59.001" },
	{ (uint8_t*)"\x80\xF4\xFF\x5B\x26\x05", "23:59:59.999" },
	{ (uint8_t*)"\x80\xF4\x00\x00\x00\x80\x00\x01", "00:00:00.000000001" },
	{ (uint8_t*)"\x80\xF4\x94\x55\xB4\xCE\x36\x01", "23:59:59.000000001" },
	{ (uint8_t*)"\x80\xF4\x94\x91\x4E\xCE\xFF\xFF", "23:59:59.999999999" },
	{ (uint8_t*)"\x80\xF8\x81\xFF\x80", "+127 0" },
	{ (uint8_t*)"\x80\xF8\x81\x00\x80\x80", "+128 0" },
	{ (uint8_t*)"\x80\xF8\x81\x82\x80", "+2 0" },
	{ (uint8_t*)"\x80\xF9\x81\x82\x80", "-2 0" },
	{ (uint8_t*)"\x80\xF9\x81\x00\x80\x80", "-128 0" },
	{ (uint8_t*)"\x80\xFA\x81\x82\x80", "=2 0" },
	{ (uint8_t*)"\x80\xFA\x81\x00\x80\x80", "=128 0" },
	{ (uint8_t*)"\x80\xFB\x01\x00\x00", "28" },
	{ (uint8_t*)"\x80\xFC\x02\x01\x00\x00", "28" },
	{ (uint8_t*)"\x80\xFD\x04\x03\x02\x01\x00\x00", "28" },
	{ (uint8_t*)"\x80\xFF\x12\x34\x5D", "12345" },
	{ (uint8_t*)"\x80\xFF\xC2\x34\x5A\x98\xDD", "-2345.98" },
	{ (uint8_t*)"\x80\xF1\x61\x61\x61\x42\x42\x42\x63\x63\x63\x00", "aaaBBBccc" },
	{ (uint8_t*)"\x80\xF2\xE4\x78\xDA\x4B\x4C\xA4\x03\x00\x00\xA0\x8D\x26\x46", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
	{ (uint8_t*)"\x80\xFC\x0C\x02\xF1\x31\x32\x67\x50\x00", "12-------P----" },
	{ (uint8_t*)"\x80\xFC\x0C\x02\xF1\x34\x36\x62\x58\x61\x42\x62\x50\x61\x33\x00", "46--X-B--P-3--" },
};

/* This is an exact duplicate of the test above but we have added
 * the field twice. This is to ensure that the decoder properly
 * aligns all internal positions and pointers correctly after a
 * successfully decoded field. */

struct test test2[] = {
	{ (uint8_t*)"\xC0\x00\x00\x00\x00", "28" },
	{ (uint8_t*)"\xC0\x00\xFF\x00\xFF", "283" },
	{ (uint8_t*)"\xC0\x64\x64", "0" },
	{ (uint8_t*)"\xC0\x7F\x7F", "27" },
	{ (uint8_t*)"\xC0\x63\x00\x63\x00", "0.99" },
	{ (uint8_t*)"\xC0\x63\xFF\x63\xFF", "255.99" },
	{ (uint8_t*)"\xC0\x01\x0A\x01\x0A", "10.01" },
	{ (uint8_t*)"\xC0\x8F\xFF\xFF\x8F\xFF\xFF", "104.8576" },
	{ (uint8_t*)"\xC0\x80\x00\x00\x80\x00\x00", "0.0001" },
	{ (uint8_t*)"\xC0\x9F\xFF\xFF\x9F\xFF\xFF", "10741.76" },
	{ (uint8_t*)"\xC0\x90\x00\x00\x90\x00\x00", "256.01" },
	{ (uint8_t*)"\xC0\x90\xEF\x0A\x90\xEF\x0A", "284" },
	{ (uint8_t*)"\xC0\xA2\x81\xA2\x81", "2000" },
	{ (uint8_t*)"\xC0\xAF\x81\xAF\x81", "20000000000000000" },
	{ (uint8_t*)"\xC0\xA0\x18\x4B\x65\x32\x18\x4B\x65\x32\x98\xA0\x18\x4B\x65\x32\x18\x4B\x65\x32\x98", "18446744073709551610" },
	{ (uint8_t*)"\xC0\xBF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE\xBF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", "18446.744073709551615" },
	{ (uint8_t*)"\xC0\xB7\x03\x61\x2C\x80\xB7\x03\x61\x2C\x80", "1.0000001" },
	{ (uint8_t*)"\xC0\xBF\x80\xBF\x80", "0.000000000000001" },
	{ (uint8_t*)"\xC0\xCF\x80\xCF\x80", "-0.000000000000001" },
	{ (uint8_t*)"\xC0\xC7\x03\x61\x2C\x80\xC7\x03\x61\x2C\x80", "-1.0000001" },
	{ (uint8_t*)"\xC0\xCF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE\xCF\x00\x7E\x7E\x7E\x7E\x7E\x7E\x7E\x7E\xFE", "-18446.744073709551615" },
	{ (uint8_t*)"\xC0\xDD\xB3\xB7\xDD\xB3\xB7", "2006-09-19" },
	{ (uint8_t*)"\xC0\xDD\x40\xBB\xDD\x40\xBB", "2008-10" },
	{ (uint8_t*)"\xC0\xDD\x00\xBA\xDD\x00\xBA", "2008" },
	{ (uint8_t*)"\xC0\xDD\x01\xBA\xDD\x01\xBA", "2008-H1" },
	{ (uint8_t*)"\xC0\xDD\x04\xBA\xDD\x04\xBA", "2008-T2" },
	{ (uint8_t*)"\xC0\xDD\x09\xBA\xDD\x09\xBA", "2008-Q4" },
	{ (uint8_t*)"\xC0\xDD\x1F\xBA\xDD\x1F\xBA", "2008-W22" },
	{ (uint8_t*)"\xC0\xDD\xBF\xBB\xDD\xBF\xBB", "2008-W53" },
	{ (uint8_t*)"\xC0\xF0\xF0", NULL },
	{ (uint8_t*)"\xC0\xF6\x00\x00\xF6\x00\x00", "00:00:00" },
	{ (uint8_t*)"\xC0\xF7\x7F\x51\xF7\x7F\x51", "23:59:58" },
	{ (uint8_t*)"\xC0\xF4\x19\x58\x26\x05\xF4\x19\x58\x26\x05", "23:59:59.001" },
	{ (uint8_t*)"\xC0\xF4\xFF\x5B\x26\x05\xF4\xFF\x5B\x26\x05", "23:59:59.999" },
	{ (uint8_t*)"\xC0\xF4\x94\x55\xB4\xCE\x36\x01\xF4\x94\x55\xB4\xCE\x36\x01", "23:59:59.000000001" },
	{ (uint8_t*)"\xC0\xF4\x94\x91\x4E\xCE\xFF\xFF\xF4\x94\x91\x4E\xCE\xFF\xFF", "23:59:59.999999999" },
	{ (uint8_t*)"\xC0\xF8\x81\xFF\x80\xF8\x81\xFF\x80", "+127 0" },
	{ (uint8_t*)"\xC0\xF8\x81\x00\x80\x80\xF8\x81\x00\x80\x80", "+128 0" },
	{ (uint8_t*)"\xC0\xF8\x81\x82\x80\xF8\x81\x82\x80", "+2 0" },
	{ (uint8_t*)"\xC0\xF9\x81\x82\x80\xF9\x81\x82\x80", "-2 0" },
	{ (uint8_t*)"\xC0\xF9\x81\x00\x80\x80\xF9\x81\x00\x80\x80", "-128 0" },
	{ (uint8_t*)"\xC0\xFA\x81\x82\x80\xFA\x81\x82\x80", "=2 0" },
	{ (uint8_t*)"\xC0\xFA\x81\x00\x80\x80\xFA\x81\x00\x80\x80", "=128 0" },
	{ (uint8_t*)"\xC0\xFB\x01\x00\x00\xFB\x01\x00\x00", "28" },
	{ (uint8_t*)"\xC0\xFC\x02\x01\x00\x00\xFC\x02\x01\x00\x00", "28" },
	{ (uint8_t*)"\xC0\xFD\x04\x03\x02\x01\x00\x00\xFD\x04\x03\x02\x01\x00\x00", "28" },
	{ (uint8_t*)"\xC0\xFF\x12\x34\x5D\xFF\x12\x34\x5D", "12345" },
	{ (uint8_t*)"\xC0\xFF\xC2\x34\x5A\x98\xDD\xFF\xC2\x34\x5A\x98\xDD", "-2345.98" },
	{ (uint8_t*)"\xC0\xF1\x61\x61\x61\x42\x42\x42\x63\x63\x63\x00\xF1\x61\x61\x61\x42\x42\x42\x63\x63\x63\x00", "aaaBBBccc" },
	{ (uint8_t*)"\xC0\xF2\xE4\x78\xDA\x4B\x4C\xA4\x03\x00\x00\xA0\x8D\x26\x46\xF2\xE4\x78\xDA\x4B\x4C\xA4\x03\x00\x00\xA0\x8D\x26\x46", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
	{ (uint8_t*)"\xC0\xFC\x0C\x02\xF1\x31\x32\x67\x50\x00\xFC\x0C\x02\xF1\x31\x32\x67\x50\x00", "12-------P----" },
	{ (uint8_t*)"\xC0\xFC\x0C\x02\xF1\x34\x36\x62\x58\x61\x42\x62\x50\x61\x33\x00\xFC\x0C\x02\xF1\x34\x36\x62\x58\x61\x42\x62\x50\x61\x33\x00", "46--X-B--P-3--" },
};

static int perform_test (mdf_t handle, struct test *test, const int num, const int iterations, const int line)
{
	int fail = 0;
	int i, j;

	for (i = 0; i < num; i++) {
		handle->data = test[i].in;
		handle->message_end = 256;
		handle->pos = 0;
		handle->fpos = 1;
		handle->in_pmap_pos = 128;
		handle->field_pmap_pos = 0;

		for (j = 0; j < iterations; j++) {
			uint32_t tag;
			char *value;
			int ret;

			/* so we really will know that the value that we get later
			 * is generated and not shadow copied */
			if (handle->value != NULL)
				handle->value[0] = '\0';

			ret = mdf_get_next_field (handle, &tag, &value);

			if (ret == 1 && ((value != NULL && test[i].out != NULL && strcmp (test[i].out, value) == 0) || test[i].out == value))
				continue;

			fail = 1;

			printf ("test:%d iteration:%d (%d) Should Be:\"%s\" Was:\"%s\" (failed)\n",
				i + 1,
				j + 1,
				line,
				test[i].out == NULL ? "<null>" : test[i].out,
				value == NULL ? "<null>" : value
			);
		}
	}

	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 void performance_test (mdf_t handle, struct test *test, const int num, const int iterations)
{
	int i, j;
	const uint64_t nominator = iterations * (uint64_t)1000000000;

	for (i = 0; i < num; i++) {
		struct timespec ts1, ts2;
		char prefix = '\0';
		uint64_t result;

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts1);

		for (j = 0; j < iterations; j++) {
			handle->data = test[i].in;
			handle->message_end = 256;
			handle->pos = 0;
			handle->fpos = 1;
			handle->in_pmap_pos = 128;
			handle->field_pmap_pos = 0;

			uint32_t tag;
			char *value;

			mdf_get_next_field (handle, &tag, &value);
		}

		clock_gettime (CLOCK_MONOTONIC_RAW, &ts2);
		timespec_diff (&ts1, &ts2);

		if (ts2.tv_sec != 0 || ts2.tv_nsec != 0)
			result = nominator / (ts2.tv_sec * 1000000000 + ts2.tv_nsec);
		else
			result = nominator;

		if (result > 1000000000)
			prefix = 'G', result /= 1000000000;
		else if (result > 1000000)
			prefix = 'M', result /= 1000000;
		else if (result > 1000)
			prefix = 'k', result /= 1000;

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

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

	memset (&handle, 0, sizeof handle);

	handle.value = (uint8_t *) malloc (1024);
	handle.vlen = 1024;

	handle.templates = malloc (sizeof *handle.templates);
	handle.templates->num = 2;
	handle.templates->templates = (struct msg_template *) calloc (handle.templates->num, sizeof *handle.templates->templates);

	handle.templates->templates[0].mclass = MDF_MC_UNDEF;
	handle.templates->templates[0].tags_num = 1;
	handle.templates->templates[0].tags = (uint32_t *) malloc (sizeof (uint32_t) * handle.templates->templates[0].tags_num);
	handle.templates->templates[0].tags[0] = 0xffffffff;

	handle.templates->templates[1].mclass = MDF_MC_UNDEF;
	handle.templates->templates[1].tags_num = 2;
	handle.templates->templates[1].tags = (uint32_t *) malloc (sizeof (uint32_t) * handle.templates->templates[1].tags_num);
	handle.templates->templates[1].tags[0] = MDF_F_LASTPRICE;
	handle.templates->templates[1].tags[1] = MDF_F_BIDPRICE;

	handle.template = &handle.templates->templates[1];

	/* checks that the decoding is done as it should */
	if (perform_test (&handle, test1, NELEMS (test1), 1, __LINE__) != 0)
		fail = 1;

	/* checks syncronisation, i.e so that all pointers are correctly
	 * aligned to the next field etc after a decoded field */
	if (perform_test (&handle, test2, NELEMS (test2), 2, __LINE__) != 0)
		fail = 1;

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

	char *env = getenv ("LIBMDF_PERFTEST");

	if (env == NULL)
		return fail;

	int iterations = strtol (env, NULL, 10);

	if (iterations == 0)
		iterations = 10000000;

	printf ("\n*** Performance tests with %d iterations***\n", iterations);

	performance_test (&handle, test1, NELEMS (test1), iterations);

	return fail;
}

