/*
 * lws-api-test-lws_dsh
 *
 * Written in 2010-2021 by Andy Green <andy@warmcat.com>
 *
 * This file is made available under the Creative Commons CC0 1.0
 * Universal Public Domain Dedication.
 */

#include <libwebsockets.h>


enum {
	LWS_SW_D,
	LWS_SW_HELP,
};

static const struct lws_switches switches[] = {
	[LWS_SW_D]	= { "-d",              "Debug logs (e.g. -d 15)" },
	[LWS_SW_HELP]	= { "--help",		"Show this help information" },
};

int
test1(void)
{
	struct lws_dsh *dsh;
	size_t size;
	void *a1;

	/*
	 * test 1: single dsh, alloc 2 kinds and free everything back to a
	 *         single free obj
	 */

	dsh = lws_dsh_create(NULL, 16384, 2);
	if (!dsh) {
		lwsl_err("%s: Failed to create dsh\n", __func__);

		return 1;
	}

	if (lws_dsh_alloc_tail(dsh, 0, "hello", 5, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 1\n", __func__);

		goto bail;
	}

	if (lws_dsh_alloc_tail(dsh, 1, "some other string", 17, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 2\n", __func__);

		goto bail;
	}

	if (lws_dsh_alloc_tail(dsh, 0, "hello again", 11, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 3\n", __func__);

		goto bail;
	}

	if (lws_dsh_get_head(dsh, 1, &a1, &size)) {
		lwsl_err("%s: no head 1\n", __func__);

		goto bail;
	}
	if (size != 17 || memcmp(a1, "some other string", 17)) {
		lwsl_err("%s: test 1 mismatch\n", __func__);

		goto bail;
	}
	lws_dsh_free(&a1);

	if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_err("%s: no head 2\n", __func__);

		goto bail;
	}
	if (size != 5 || memcmp(a1, "hello", 5)) {
		lwsl_err("%s: test 2 mismatch\n", __func__);

		goto bail;
	}
	lws_dsh_free(&a1);

	if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_err("%s: no head 3\n", __func__);

		goto bail;
	}
	if (size != 11 || memcmp(a1, "hello again", 11)) {
		lwsl_err("%s: test 3 mismatch\n", __func__);

		goto bail;
	}
	lws_dsh_free(&a1);

	lws_dsh_destroy(&dsh);

	return 0;
bail:
	lws_dsh_destroy(&dsh);

	return 1;
}

int
test3(void)
{
	struct lws_dsh *dsh, *dsh2;
	lws_dll2_owner_t owner;
	uint8_t blob[4096];

	memset(blob, 0, sizeof(blob));

	/*
	 * test 3: multiple dsh, umeetable allocation request
	 */

	lws_dll2_owner_clear(&owner);

	dsh = lws_dsh_create(&owner, 4096, 2);
	if (!dsh) {
		lwsl_err("%s: Failed to create dsh1\n", __func__);

		return 1;
	}

	dsh2 = lws_dsh_create(&owner, 4096, 2);
	if (!dsh2) {
		lwsl_err("%s: Failed to create dsh2\n", __func__);

		goto bail;
	}

	if (lws_dsh_alloc_tail(dsh, 0, blob, 4000, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 1\n", __func__);

		goto bail2;
	}

	if (lws_dsh_alloc_tail(dsh2, 0, "hello", 5, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 2\n", __func__);

		goto bail2;
	}

	/*
	 * There's just no room for this, we expect it to fail
	 */

	if (!lws_dsh_alloc_tail(dsh, 0, blob, 5000, NULL, 0)) {
		lwsl_err("%s: Didn't fail to alloc as expected\n", __func__);

		goto bail2;
	}

	if (lws_dsh_alloc_tail(dsh2, 0, "hello again", 11, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 4\n", __func__);

		goto bail2;
	}

	lws_dsh_destroy(&dsh2);
	lws_dsh_destroy(&dsh);

	return 0;

bail2:
	lws_dsh_destroy(&dsh2);

bail:
	lws_dsh_destroy(&dsh);

	return 1;
}

int
test4(void)
{
	uint8_t blob[4096];
	struct lws_dsh *dsh;
	size_t size;
	void *a1;

	memset(blob, 0, sizeof(blob));

	/*
	 * test 4: use up whole free list, then recover and alloc something
	 *	   else
	 */

	dsh = lws_dsh_create(NULL, 4096, 2);
	if (!dsh) {
		lwsl_err("%s: Failed to create dsh\n", __func__);

		return 1;
	}

	if (lws_dsh_alloc_tail(dsh, 0, blob, 4000, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 1\n", __func__);

		goto bail;
	}

	if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_err("%s: no head 1\n", __func__);

		goto bail;
	}
	if (size != 4000) {
		lwsl_err("%s: test 1 mismatch %d\n", __func__, (int)size);

		goto bail;
	}
	lws_dsh_free(&a1);

	if (lws_dsh_alloc_tail(dsh, 0, "some other string", 17, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 2\n", __func__);

		goto bail;
	}

	if (lws_dsh_alloc_tail(dsh, 0, "hello again", 11, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 3\n", __func__);

		goto bail;
	}

	if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_err("%s: no head 4\n", __func__);

		goto bail;
	}
	if (size != 17 || memcmp(a1, "some other string", 17)) {
		/* it may (should...) coalesce */
		if (size != 28 || memcmp(a1, "some other stringhello again", 28)) {
			lwsl_err("%s: test 4 mismatch %d\n", __func__, (int)size);

			goto bail;
		}
	}
	lws_dsh_free(&a1);

	lws_dsh_destroy(&dsh);

	return 0;
bail:
	lws_dsh_destroy(&dsh);

	return 1;
}

int
test5(void)
{
	struct lws_dsh *dsh;
	unsigned int budget;
	uint8_t blob[4096];
	lws_xos_t xos;
	size_t size;
	void *a1;

	memset(blob, 0, sizeof(blob));
	lws_xos_init(&xos, 0x123456789abcdef0ull);

	budget = (unsigned int)(lws_xos(&xos) % 4000) + 4000;

	lwsl_notice("%s: budget %u\n", __func__, budget);


	/*
	 * test 5: PRNG-based spamming and erratic bidi draining
	 */

	dsh = lws_dsh_create(NULL, 409600, 2);
	if (!dsh) {
		lwsl_err("%s: Failed to create dsh\n", __func__);

		return 1;
	}

	do {

		if (lws_xos_percent(&xos, 60)) {
			/* kind 0 is going to try to write */

			size = (size_t)((lws_xos(&xos) & 127) + 1);

			if (!lws_dsh_alloc_tail(dsh, 0, blob, size, NULL, 0))
				lwsl_notice("%s: kind 0 alloc %d\n", __func__, (int)size);
		}

		if (lws_xos_percent(&xos, 80)) {
			/* kind 1 is going to try to write */

			size = (size_t)((lws_xos(&xos) & 127) + 1);

			if (!lws_dsh_alloc_tail(dsh, 1, blob, size, NULL, 0))
				lwsl_notice("%s: kind 1 alloc %d\n", __func__, (int)size);
		}

		if (lws_xos_percent(&xos, 40)) {
			/* kind 0 is going to try to read */

			while (!lws_dsh_get_head(dsh, 0, &a1, &size)) {
				lwsl_notice("%s: kind 0 read %d\n", __func__, (int)size);
				lws_dsh_free(&a1);
			}
		}

		if (lws_xos_percent(&xos, 30)) {
			/* kind 1 is going to try to read */

			while (!lws_dsh_get_head(dsh, 1, &a1, &size)) {
				lwsl_notice("%s: kind 1 read %d\n", __func__, (int)size);
				lws_dsh_free(&a1);
			}
		}

	} while (budget--);

	while (!lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_notice("%s: kind 0 read %d\n", __func__, (int)size);
		lws_dsh_free(&a1);
	}

	while (!lws_dsh_get_head(dsh, 1, &a1, &size)) {
		lwsl_notice("%s: kind 1 read %d\n", __func__, (int)size);
		lws_dsh_free(&a1);
	}

#if defined(_DEBUG)
	lws_dsh_describe(dsh, "test dsh end state");
#endif

	lws_dsh_destroy(&dsh);

	return 0;
}

int
test6(void)
{
	struct lws_dsh *dsh;
	size_t size;
	void *a1;

	/*
	 * test 6: confirm sequential coalescing
	 */

	dsh = lws_dsh_create(NULL, 16384, 2);
	if (!dsh) {
		lwsl_err("%s: Failed to create dsh\n", __func__);

		return 1;
	}

	if (lws_dsh_alloc_tail(dsh, 0, "hello", 5, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 1\n", __func__);

		goto bail;
	}

	if (lws_dsh_alloc_tail(dsh, 1, "some other string", 17, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 2\n", __func__);

		goto bail;
	}

	if (lws_dsh_alloc_tail(dsh, 0, "hello again", 11, NULL, 0)) {
		lwsl_err("%s: Failed to alloc 3\n", __func__);

		goto bail;
	}

	if (lws_dsh_get_head(dsh, 1, &a1, &size)) {
		lwsl_err("%s: no head 1\n", __func__);

		goto bail;
	}
	if (size != 17 || memcmp(a1, "some other string", 17)) {
		lwsl_err("%s: test 1 mismatch\n", __func__);

		goto bail;
	}
	lws_dsh_free(&a1);

	if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_err("%s: no head 2\n", __func__);

		goto bail;
	}
	if (size != 5 || memcmp(a1, "hello", 5)) {
		lwsl_err("%s: test 2 mismatch\n", __func__);

		goto bail;
	}
	lws_dsh_free(&a1);

	if (lws_dsh_get_head(dsh, 0, &a1, &size)) {
		lwsl_err("%s: no head 3\n", __func__);

		goto bail;
	}
	if (size != 11 || memcmp(a1, "hello again", 11)) {
		lwsl_err("%s: test 3 mismatch\n", __func__);

		goto bail;
	}
	lws_dsh_free(&a1);

	lws_dsh_destroy(&dsh);

	return 0;
bail:
#if defined(_DEBUG)
	lws_dsh_describe(dsh, "test6 fail");
#endif
	lws_dsh_destroy(&dsh);

	return 1;
}

int main(int argc, const char **argv)
{
	
	int ret = 0, n;
	(void)switches;

	if ((argc == 1) || lws_cmdline_option(argc, argv, switches[LWS_SW_HELP].sw)) {
		lws_switches_print_help(argv[0], switches, LWS_ARRAY_SIZE(switches));
		return 0;
	}



	lwsl_user("LWS API selftest: lws_dsh\n");

	n = test1();
	lwsl_user("%s: test1: %d\n", __func__, n);
	ret |= n;
	if (ret)
		goto bail;

	n = test3();
	lwsl_user("%s: test3: %d\n", __func__, n);
	ret |= n;
	if (ret)
		goto bail;

	n = test4();
	lwsl_user("%s: test4: %d\n", __func__, n);
	ret |= n;
	if (ret)
		goto bail;

	n = test5();
	lwsl_user("%s: test5: %d\n", __func__, n);
	ret |= n;
	if (ret)
		goto bail;

	n = test6();
	lwsl_user("%s: test6: %d\n", __func__, n);
	ret |= n;

bail:
	lwsl_user("Completed: %s\n", ret ? "FAIL" : "PASS");

	return ret;
}
