#include "private-libwebsockets.h"

#include "extension-permessage-deflate.h"

LWS_VISIBLE void
lws_context_init_extensions(struct lws_context_creation_info *info,
			    struct lws_context *context)
{
	lwsl_info(" LWS_MAX_EXTENSIONS_ACTIVE: %u\n", LWS_MAX_EXTENSIONS_ACTIVE);
}

enum lws_ext_option_parser_states {
	LEAPS_SEEK_NAME,
	LEAPS_EAT_NAME,
	LEAPS_SEEK_VAL,
	LEAPS_EAT_DEC,
	LEAPS_SEEK_ARG_TERM
};

LWS_VISIBLE int
lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi,
		      void *ext_user, const struct lws_ext_options *opts,
		      const char *in, int len)
{
	enum lws_ext_option_parser_states leap = LEAPS_SEEK_NAME;
	unsigned int match_map = 0, n, m, w = 0, count_options = 0,
		     pending_close_quote = 0;
	struct lws_ext_option_arg oa;

	oa.option_name = NULL;

	while (opts[count_options].name)
		count_options++;
	while (len) {
		lwsl_ext("'%c' %d", *in, leap);
		switch (leap) {
		case LEAPS_SEEK_NAME:
			if (*in == ' ')
				break;
			if (*in == ',') {
				len = 1;
				break;
			}
			match_map = (1 << count_options) - 1;
			leap = LEAPS_EAT_NAME;
			w = 0;

		/* fallthru */

		case LEAPS_EAT_NAME:
			oa.start = NULL;
			oa.len = 0;
			m = match_map;
			n = 0;
			pending_close_quote = 0;
			while (m) {
				if (m & 1) {
					lwsl_ext("    m=%d, n=%d, w=%d\n", m, n, w);

					if (*in == opts[n].name[w]) {
						if (!opts[n].name[w + 1]) {
							oa.option_index = n;
							lwsl_ext("hit %d\n", oa.option_index);
							leap = LEAPS_SEEK_VAL;
							if (len == 1)
								goto set_arg;
							break;
						}
					} else {
						match_map &= ~(1 << n);
						if (!match_map) {
							lwsl_ext("empty match map\n");
							return -1;
						}
					}
				}
				m >>= 1;
				n++;
			}
			w++;
			break;
		case LEAPS_SEEK_VAL:
			if (*in == ' ')
				break;
			if (*in == ',') {
				len = 1;
				break;
			}
			if (*in == ';' || len == 1) { /* ie,nonoptional */
				if (opts[oa.option_index].type == EXTARG_DEC)
					return -1;
				leap = LEAPS_SEEK_NAME;
				goto set_arg;
			}
			if (*in == '=') {
				w = 0;
				pending_close_quote = 0;
				if (opts[oa.option_index].type == EXTARG_NONE)
					return -1;

				leap = LEAPS_EAT_DEC;
				break;
			}
			return -1;

		case LEAPS_EAT_DEC:
			if (*in >= '0' && *in <= '9') {
				if (!w)
					oa.start = in;
				w++;
				if (len != 1)
					break;
			}
			if (!w && *in =='"') {
				pending_close_quote = 1;
				break;
			}
			if (!w)
				return -1;
			if (pending_close_quote && *in != '"' && len != 1)
				return -1;
			leap = LEAPS_SEEK_ARG_TERM;
			if (oa.start)
				oa.len = in - oa.start;
			if (len == 1)
				oa.len++;

set_arg:
			ext->callback(lws_get_context(wsi),
				ext, wsi, LWS_EXT_CB_OPTION_SET,
				ext_user, (char *)&oa, 0);
			if (len == 1)
				break;
			if (pending_close_quote && *in == '"')
				break;

			/* fallthru */

		case LEAPS_SEEK_ARG_TERM:
			if (*in == ' ')
				break;
			if (*in == ';') {
				leap = LEAPS_SEEK_NAME;
				break;
			}
			if (*in == ',') {
				len = 1;
				break;
			}
			return -1;
		}
		len--;
		in++;
	}

	return 0;
}


/* 0 = nobody had nonzero return, 1 = somebody had positive return, -1 = fail */

int lws_ext_cb_active(struct lws *wsi, int reason, void *arg, int len)
{
	int n, m, handled = 0;

	for (n = 0; n < wsi->count_act_ext; n++) {
		m = wsi->active_extensions[n]->callback(lws_get_context(wsi),
			wsi->active_extensions[n], wsi, reason,
			wsi->act_ext_user[n], arg, len);
		if (m < 0) {
			lwsl_ext("Ext '%s' failed to handle callback %d!\n",
				 wsi->active_extensions[n]->name, reason);
			return -1;
		}
		/* valgrind... */
		if (reason == LWS_EXT_CB_DESTROY)
			wsi->act_ext_user[n] = NULL;
		if (m > handled)
			handled = m;
	}

	return handled;
}

int lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi,
			int reason, void *arg, int len)
{
	int n = 0, m, handled = 0;
	const struct lws_extension *ext;

	if (!wsi || !wsi->vhost)
		return 0;

	ext = wsi->vhost->extensions;

	while (ext && ext->callback && !handled) {
		m = ext->callback(context, ext, wsi, reason,
				  (void *)(lws_intptr_t)n, arg, len);
		if (m < 0) {
			lwsl_ext("Ext '%s' failed to handle callback %d!\n",
				 wsi->active_extensions[n]->name, reason);
			return -1;
		}
		if (m)
			handled = 1;

		ext++;
		n++;
	}

	return 0;
}

int
lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len)
{
	struct lws_tokens eff_buf;
	int ret, m, n = 0;

	eff_buf.token = (char *)buf;
	eff_buf.token_len = len;

	/*
	 * while we have original buf to spill ourselves, or extensions report
	 * more in their pipeline
	 */

	ret = 1;
	while (ret == 1) {

		/* default to nobody has more to spill */

		ret = 0;

		/* show every extension the new incoming data */
		m = lws_ext_cb_active(wsi,
			       LWS_EXT_CB_PACKET_TX_PRESEND, &eff_buf, 0);
		if (m < 0)
			return -1;
		if (m) /* handled */
			ret = 1;

		if ((char *)buf != eff_buf.token)
			/*
			 * extension recreated it:
			 * need to buffer this if not all sent
			 */
			wsi->u.ws.clean_buffer = 0;

		/* assuming they left us something to send, send it */

		if (eff_buf.token_len) {
			n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token,
							    eff_buf.token_len);
			if (n < 0) {
				lwsl_info("closing from ext access\n");
				return -1;
			}

			/* always either sent it all or privately buffered */
			if (wsi->u.ws.clean_buffer)
				len = n;
		}

		lwsl_parser("written %d bytes to client\n", n);

		/* no extension has more to spill?  Then we can go */

		if (!ret)
			break;

		/* we used up what we had */

		eff_buf.token = NULL;
		eff_buf.token_len = 0;

		/*
		 * Did that leave the pipe choked?
		 * Or we had to hold on to some of it?
		 */

		if (!lws_send_pipe_choked(wsi) && !wsi->trunc_len)
			/* no we could add more, lets's do that */
			continue;

		lwsl_debug("choked\n");

		/*
		 * Yes, he's choked.  Don't spill the rest now get a callback
		 * when he is ready to send and take care of it there
		 */
		lws_callback_on_writable(wsi);
		wsi->extension_data_pending = 1;
		ret = 0;
	}

	return len;
}

int
lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r,
			  void *v, size_t len)
{
	struct lws_context *context = wsi->context;
	int n, handled = 0;

	/* maybe an extension will take care of it for us */

	for (n = 0; n < wsi->count_act_ext && !handled; n++) {
		if (!wsi->active_extensions[n]->callback)
			continue;

		handled |= wsi->active_extensions[n]->callback(context,
			wsi->active_extensions[n], wsi,
			r, wsi->act_ext_user[n], v, len);
	}

	return handled;
}

int
lws_set_extension_option(struct lws *wsi, const char *ext_name,
			 const char *opt_name, const char *opt_val)
{
	struct lws_ext_option_arg oa;
	int idx = 0;

	/* first identify if the ext is active on this wsi */
	while (idx < wsi->count_act_ext &&
	       strcmp(wsi->active_extensions[idx]->name, ext_name))
		idx++;

	if (idx == wsi->count_act_ext)
		return -1; /* request ext not active on this wsi */

	oa.option_name = opt_name;
	oa.option_index = 0;
	oa.start = opt_val;
	oa.len = 0;

	return wsi->active_extensions[idx]->callback(
			wsi->context, wsi->active_extensions[idx], wsi,
			LWS_EXT_CB_NAMED_OPTION_SET, wsi->act_ext_user[idx], &oa, 0);
}