/*
 * lws-api-test-gunzip
 *
 * Written in 2010-2022 by Andy Green <andy@warmcat.com>
 *
 * This file is made available under the Creative Commons CC0 1.0
 * Universal Public Domain Dedication.
 *
 * tests for LWS_WITH_GZINFLATE (inflator via upng)
 */

#include <libwebsockets.h>

enum {
	LWS_SW_STDIN,
	LWS_SW_STDOUT,
	LWS_SW_D,
	LWS_SW_HELP,
};

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

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

int fdin = 0, fdout = 1;

int
main(int argc, const char **argv)
{
	
	int result = 0, more = 1;
	const char *p;
	lws_stateful_ret_t r = LWS_SRET_WANT_INPUT;
	struct inflator_ctx *gunz;
	const uint8_t *outring;
	size_t l = 0, old_op = 0, outringlen, *opl, *cl, pw = 0;
	(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: gunzip\n");

	if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDIN].sw))) {
		fdin = open(p, LWS_O_RDONLY, 0);
		if (fdin < 0) {
			result = 1;
			lwsl_err("%s: unable to open stdin file\n", __func__);
			goto bail;
		}
	}

	if ((p = lws_cmdline_option(argc, argv, switches[LWS_SW_STDOUT].sw))) {
		fdout = open(p, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600);
		if (fdout < 0) {
			result = 1;
			lwsl_err("%s: unable to open stdout file\n", __func__);
			goto bail;
		}
	}

	if (!fdin) {
		struct timeval timeout;
		fd_set	fds;

		FD_ZERO(&fds);
		FD_SET(0, &fds);

		timeout.tv_sec  = 0;
		timeout.tv_usec = 1000;

		if (select(fdin + 1, &fds, NULL, NULL, &timeout) < 0 ||
		    !FD_ISSET(0, &fds)) {
			result = 1;
			lwsl_err("%s: pass PNG "
				 "on stdin or use --stdin\n", __func__);
			goto bail;
		}
	}

	gunz = lws_upng_inflator_create(&outring, &outringlen, &opl, &cl);
	if (!gunz)
		goto bail;

	do {
		uint8_t ib[9];
		const uint8_t *pib = NULL;
		ssize_t s, os;
		size_t ps = 0, part;

		pib = NULL;
		if ((r & LWS_SRET_WANT_INPUT) && more) {
			s = read(fdin, ib, sizeof(ib));

			if (s <= 0) {
				lwsl_err("%s: failed to read: %d (after %lu)\n", __func__, errno, (unsigned long)l);
				more = 0;
			} else {

				pib = ib;
				ps = (size_t)s;
				l += ps;

//				lwsl_hexdump_notice(pib, ps);

				lwsl_info("%s: fetched %u (%u)\n", __func__,
						(unsigned int)s, (unsigned int)l);
			}
		}

		do {
			r = lws_upng_inflate_data(gunz, pib, ps);
			pib = NULL;
			ps = 0;

			// lwsl_notice("r = %d\n", r);

			if (r & LWS_SRET_FATAL) {
				lwsl_err("%s: emit returned FATAL %d\n", __func__, r &0xff);
				result = 1;
				goto bail1;
			}

			if (!more && *opl == old_op) {
				lwsl_notice("%s: seem finished\n", __func__);
				/* no more input possible, and no output came */
				goto bail1;
			}

			os = (ssize_t)((*opl - (size_t)old_op) % outringlen);

			/* if we wrap around the ring, first do the part to the
			 * end of the ring */

			part = (size_t)os;
			if ((*opl % outringlen) < old_op)
				part = outringlen - old_op;

			// lwsl_notice("%s: out %d (%d -> %d)\n", __func__, (int)os, (int)old_op, (int)(old_op + part));

			if (write(fdout, outring + old_op,
#if defined(WIN32)
						(unsigned int)
#endif
						part) < (ssize_t)part) {
				lwsl_err("%s: write %d failed %d\n", __func__,
						(int)os, errno);
				goto bail1;
			}

			/* then do the remainder (if any) from the ring start */

			if ((*opl % outringlen) < old_op)
				if (write(fdout, outring,
	#if defined(WIN32)
							(unsigned int)
	#endif
							*opl % outringlen) < (ssize_t)(*opl % outringlen)) {
					lwsl_err("%s: write %d failed %d\n", __func__,
							(int)os, errno);
					goto bail1;
				}

			old_op = *opl % outringlen;
			*cl = *opl;
			pw = (size_t)(pw + (size_t)os);

			lwsl_debug("%s: wrote %d: r %u (left %u)\n", __func__,
					(int)os, r, (unsigned int)ps);

			if (r == LWS_SRET_OK) {
				lwsl_notice("%s: feels OK %lu\n", __func__, (unsigned long)pw);
				goto bail1;
			}

			if (r & LWS_SRET_WANT_INPUT)
				break;

		} while (ps); /* while any input left */
	} while (1);

bail1:

	lws_upng_inflator_destroy(&gunz);

bail:

	if (fdin >= 0)
		close(fdin);
	if (fdout >= 0 && fdout != 1)
		close(fdout);

	lwsl_user("Completed\n");

	return result;
}
