/*
 * lws-api-test-ssjpeg
 *
 * 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.
 */

#include <libwebsockets.h>

enum {
	LWS_SW_STDOUT,
	LWS_SW_HELP,
};

static const struct lws_switches switches[] = {
	[LWS_SW_STDOUT]	= { "--stdout",        "Enable --stdout feature" },
	[LWS_SW_HELP]	= { "--help",		"Show this help information" },
};

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

/*
 * The dlo and the flow are inside the context of the SS
 */

LWS_SS_USER_TYPEDEF
	lws_flow_t			flow;
	lws_jpeg_t			*j;
} myss_t;

static lws_dlo_rasterize_t dlo_rasterize;
struct lws_context *cx;
static int fdout = 1, result = 1;

/* sul to produce some lines of output bitmap */

static void
rasterize(lws_sorted_usec_list_t *sul)
{
	lws_dlo_rasterize_t *rast = lws_container_of(sul, lws_dlo_rasterize_t, sul);
	lws_flow_t *flow = lws_container_of(rast->owner.head, lws_flow_t, list);
	myss_t *m = lws_container_of(flow, myss_t, flow);
	const uint8_t *pix = NULL;
	lws_stateful_ret_t r;
	ssize_t os;

	do {
		if (!flow->len) {
			if (flow->blseglen)
				lws_buflist_use_segment(&flow->bl, flow->blseglen);
			flow->len = lws_buflist_next_segment_len(
					&flow->bl, (uint8_t **)&flow->data);
			flow->blseglen = (uint32_t)flow->len;
			if (!flow->len)
				return;
		}

		r = lws_jpeg_emit_next_line(m->j, &pix,
				(const uint8_t **)&flow->data, &flow->len, 0);
		if (!flow->len && flow->blseglen) {
			lws_buflist_use_segment(&flow->bl, flow->blseglen);
			flow->blseglen = 0;
		}
		if (r == LWS_SRET_WANT_INPUT) {
			if (lws_buflist_next_segment_len(&flow->bl, NULL))
				continue;

			if (r == LWS_SRET_WANT_INPUT && flow->h) {
				int32_t est = lws_ss_get_est_peer_tx_credit(flow->h) +
					(int)lws_buflist_total_len(&flow->bl) +
					(int)flow->len;

				lwsl_debug("%s: est %d\n", __func__, est);
				if (est < flow->window)
					lws_ss_add_peer_tx_credit(flow->h, flow->window);
			}

			return;
		}

		if (r >= LWS_SRET_FATAL) {
			lwsl_notice("%s: emit returned FATAL\n", __func__);
			flow->state = LWSDLOFLOW_STATE_READ_FAILED;
			lws_default_loop_exit(cx);
			return;
		}

		if (!pix)
			return;

		os = (ssize_t)(lws_jpeg_get_width(m->j) *
				(lws_jpeg_get_pixelsize(m->j) / 8));

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

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

		if (r == LWS_SRET_OK) {
			lwsl_notice("%s: feels complete\n", __func__);
			flow->state = LWSDLOFLOW_STATE_READ_COMPLETED;
			result = 0;
			lws_default_loop_exit(cx);
			return;
		}

	} while (1);

	return;

bail1:
	return;
}

/* secure streams payload interface */

static lws_ss_state_return_t
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
	myss_t *m = (myss_t *)userobj;
	lws_dlo_rasterize_t *rast1 = lws_container_of(m->flow.list.owner,
						lws_dlo_rasterize_t, owner);

	if (len && lws_buflist_append_segment(&m->flow.bl, buf, len) < 0)
		return LWSSSSRET_DISCONNECT_ME;

	if (flags & LWSSS_FLAG_EOM) {
		m->flow.state = LWSDLOFLOW_STATE_READ_COMPLETED;
		return LWSSSSRET_DISCONNECT_ME;
	}

	lws_sul_schedule(lws_ss_get_context(m->ss), 0, &rast1->sul, rasterize, 1);

	return LWSSSSRET_OK;
}

static lws_ss_state_return_t
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
	   lws_ss_tx_ordinal_t ack)
{
	myss_t *m = (myss_t *)userobj;
	const char *url = (const char*)m->opaque_data;
	lws_ss_state_return_t r;

	switch (state) {
	case LWSSSCS_CREATING:
		m->flow.h = m->ss;
		m->flow.window = 4096;
		m->j = lws_jpeg_new();
		if (!m->j) {
			lwsl_err("%s: failed to allocate\n", __func__);
			return LWSSSSRET_DESTROY_ME;
		}

		if (lws_ss_set_metadata(m->ss, "endpoint", url, strlen(url))) {
			lwsl_err("%s: failed to use metadata %s\n", __func__,
					url);
			return LWSSSSRET_DESTROY_ME;
		}

		r = lws_ss_client_connect(m->ss);
		if (r)
			return r;

		lws_dll2_add_tail(&m->flow.list, &dlo_rasterize.owner);
		break;

	case LWSSSCS_DESTROYING:
		m->flow.h = NULL;
		lws_buflist_destroy_all_segments(&m->flow.bl);
		lws_jpeg_free(&m->j);
		lws_dll2_remove(&m->flow.list);
		break;

	default:
		break;
	}

	return LWSSSSRET_OK;
}

static LWS_SS_INFO("default", myss_t)
	.rx				= myss_rx,
	.state				= myss_state,
	.manual_initial_tx_credit	= 1400
};

static void
sigint_handler(int sig)
{
	lws_default_loop_exit(cx);
}

int
main(int argc, const char **argv)
{
	struct lws_context_creation_info info;
	const char *p;
	size_t l = 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 SS JPEG test client   <https://server/my.jpg>\n");

	signal(SIGINT, sigint_handler);

	lws_context_info_defaults(&info, NULL);lws_cmdline_option_handle_builtin(argc, argv, &info);

	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) {
			lwsl_err("%s: unable to open stdout file\n", __func__);
			goto bail;
		}
	}

	info.fd_limit_per_thread = 1 + 6 + 1;
	info.port = CONTEXT_PORT_NO_LISTEN;
	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
		       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
		       LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW;

	/* create the cx */

	cx = lws_create_context(&info);
	if (!cx) {
		lwsl_err("lws init failed\n");
		return 1;
	}

	/* create the SS to the jpg using the URL on argv[1] */

	if (lws_ss_create(cx, 0, &ssi_myss_t, (void *)argv[1], NULL, NULL, NULL)) {
		lws_context_destroy(cx);
		goto bail2;
	}

	lws_context_default_loop_run_destroy(cx);

bail2:
	if (fdout != 1)
		close(fdout);

bail:
	lwsl_user("Completed: %s (read %u)\n", result ? "FAIL" : "PASS",
							(unsigned int)l);

	return result;
}
