/*
 * ESP32 Group protocol handler
 *
 * Copyright (C) 2017 Andy Green <andy@warmcat.com>
 *
 *  This library 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:
 *  version 2.1 of the License.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301  USA*
 *
 */
#include <string.h>
#include <nvs.h>
#include <esp_ota_ops.h>

typedef enum {
	GROUP_STATE_NONE,
	GROUP_STATE_INITIAL,
	GROUP_STATE_MEMBERS,
	GROUP_STATE_FINAL
} group_state;

struct per_session_data__lws_group {
	struct per_session_data__lws_group *next;
	group_state group_state;

	struct lws_group_member *member;

	unsigned char subsequent:1;
	unsigned char changed_partway:1;
};

struct per_vhost_data__lws_group {
	struct per_session_data__lws_group *live_pss_list;
	struct lws_context *context;
	struct lws_vhost *vhost;
	const struct lws_protocols *protocol;
	int count_live_pss;
};

static void render_ip4(char *dest, int len, uint8_t *ip)
{
	snprintf(dest, len, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
}



static int
callback_lws_group(struct lws *wsi, enum lws_callback_reasons reason,
		   void *user, void *in, size_t len)
{
	struct per_session_data__lws_group *pss =
			(struct per_session_data__lws_group *)user;
	struct per_vhost_data__lws_group *vhd =
			(struct per_vhost_data__lws_group *)
			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
					lws_get_protocol(wsi));
	char buffer[1024 + LWS_PRE], ipv4[20];
	char *start = buffer + LWS_PRE - 1, *p = start,
		      *end = buffer + sizeof(buffer) - 1;
	struct lws_group_member *mbr;
	int n, m;

	switch (reason) {

	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
				lws_get_protocol(wsi),
				sizeof(struct per_vhost_data__lws_group));
		vhd->context = lws_get_context(wsi);
		vhd->protocol = lws_get_protocol(wsi);
		vhd->vhost = lws_get_vhost(wsi);
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		if (!vhd)
			break;
		break;

	case LWS_CALLBACK_ESTABLISHED:
		lwsl_notice("%s: ESTABLISHED\n", __func__);
		vhd->count_live_pss++;
		pss->next = vhd->live_pss_list;
		vhd->live_pss_list = pss;
		pss->group_state = GROUP_STATE_INITIAL;
		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:

		switch (pss->group_state) {

		case GROUP_STATE_NONE:
			/* fallthru */

		case GROUP_STATE_INITIAL:

			p += snprintf((char *)p, end - p,
				      "{\n"
				      " \"group\":\"%s\","
				      " \"members\":[\n",
				      lws_esp32.group);

			n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;
			pss->group_state = GROUP_STATE_MEMBERS;
			pss->subsequent = 0;
			pss->changed_partway = 0;
			pss->member = lws_esp32.first;
			break;

		case GROUP_STATE_MEMBERS:

			/* confirm pss->member is still in the list... */

			mbr = lws_esp32.first;
			while (mbr && mbr != pss->member)
				mbr = mbr->next;

			if (!mbr) { /* no longer exists... */
				if (lws_esp32.first || pss->member)
					pss->changed_partway = 1;
				*p++ = ' ';
				pss->member = NULL;

				/*
				 * finish the list where we got to, then
				 * immediately reissue it
				 */
			}
			
			while (end - p > 100 && pss->member) {

				if (pss->subsequent)
					*p++ = ',';

				pss->subsequent = 1;
				render_ip4(ipv4, sizeof(ipv4), (uint8_t *)&pss->member->addr);

				p += snprintf((char *)p, end - p,
					      " {\n"
					      "  \"mac\":\"%s\",\n"
					      "  \"model\":\"%s\",\n"
					      "  \"role\":\"%s\",\n"
					      "  \"width\":\"%d\",\n"
					      "  \"height\":\"%d\",\n"
					      "  \"ipv4\":\"%s\"\n"
					      " }\n",
					        pss->member->mac,
					      	pss->member->model,
						pss->member->role,
						pss->member->width,
						pss->member->height,
						ipv4
					      );
				pss->member = pss->member->next;
			}

			lwsl_notice("%s\n", p);

			n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
			if (!pss->member)
				pss->group_state = GROUP_STATE_FINAL;
			break;

		case GROUP_STATE_FINAL:
			n = LWS_WRITE_CONTINUATION;
			p += sprintf((char *)p, "],\n \"discard\":\"%d\"}\n",
					pss->changed_partway);
			if (pss->changed_partway)
				pss->group_state = GROUP_STATE_INITIAL;
			else
				pss->group_state = GROUP_STATE_NONE;
			break;
		default:
			return 0;
		}
//		lwsl_notice("issue: %d (%d)\n", p - start, n);
		m = lws_write(wsi, (unsigned char *)start, p - start, n);
		if (m < 0) {
			lwsl_err("ERROR %d writing to di socket\n", m);
			return -1;
		}

		if (pss->group_state != GROUP_STATE_NONE)
			lws_callback_on_writable(wsi);

		break;

	case LWS_CALLBACK_RECEIVE:
		{
			break;
		}

	case LWS_CALLBACK_CLOSED:
		{
			struct per_session_data__lws_group **p = &vhd->live_pss_list;

			while (*p) {
				if ((*p) == pss) {
					*p = pss->next;
					continue;
				}

				p = &((*p)->next);
			}

			vhd->count_live_pss--;
		}
		break;

	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
		/* called when our wsi user_space is going to be destroyed */
		break;

	default:
		break;
	}

	return 0;
}

#define LWS_PLUGIN_PROTOCOL_LWS_GROUP \
	{ \
		"lws-group", \
		callback_lws_group, \
		sizeof(struct per_session_data__lws_group), \
		1024, 0, NULL, 900 \
	}