/*
 * Sai master
 *
 * Copyright (C) 2019 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
 *
 * The same ws interface is connected-to by builders (on path /builder), and
 * provides the query transport for browsers (on path /browse).
 */

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <time.h>

#include <git2.h>

#include "private.h"

typedef enum {
	SWT_BUILDER,
	SWT_BROWSE
} ws_type;

struct pss {
	ws_type type;
};

struct vhd {
	struct lws_context *context;
	struct lws_vhost *vhost;

	struct sai_master master;
};

/*
 * {"schema":"com-warmcat-sai-builder-attach",
 "hostname":"learn", "targets": [{"name":"optee-hikey-arm64"}
],
{"nspawns": []}
 */


static const char * const paths[] = {
	"schema",
	"hostname",
	"nspawn-timeout",

	"targets[]",
	"targets[].name",

	"nspawns[]",
	"nspawns[].name",
	"nspawns[].overlays[]",
	"nspawns[].overlays[].name",
};

enum enum_paths {
	SBAJP_SCHEMA,
	SBAJP_HOSTNAME,
	SBAJP_NSPAWN_TIMEOUT,

	SBAJP_TARGETS,
	SBAJP_TARGETS_NAME,

	SBAJP_NSPAWNS,
	SBAJP_NSPAWNS_NAME,
	SBAJP_NSPAWNS_OVERLAYS,
	SBAJP_NSPAWNS_OVERLAYS_NAME,
};

static int
sai_master_nspawn_destroy(struct lws_dll *d)
{
	sai_nspawn_t *n = lws_container_of(d, sai_nspawn_t, nspawn_list);

	lws_dll_remove(&n->nspawn_list);
	free(n);

	return 0;
}

static int
sai_master_console_destroy(struct lws_dll *d)
{
	sai_console_t *c = lws_container_of(d, sai_console_t, console_list);

	lws_dll_remove(&c->console_list);
	free(c);

	return 0;
}

static int
sai_master_target_destroy(struct lws_dll *d)
{
	sai_target_t *t = lws_container_of(d, sai_target_t, target_list);

	lws_dll_foreach_safe(&t->console_head, sai_master_console_destroy);

	free(t);

	return 0;
}

static int
sai_master_builder_destroy(struct lws_dll *d)
{
	sai_builder_t *b = lws_container_of(d, sai_builder_t, builder_list);

	lws_dll_foreach_safe(&b->nspawn_head, sai_master_nspawn_destroy);
	lws_dll_foreach_safe(&b->target_head, sai_master_target_destroy);

	if (b->hostname)
		free(b->hostname);

	free(b);

	return 0;
}

static int
sai_destroy_job(struct lws_dll *d)
{
	sai_job_t *job = lws_container_of(d, sai_job_t, jobs_list);

	lws_dll_remove(d);
	free(job);

	return 0;
}

static void
sai_master_master_destroy(struct sai_master *master)
{
	lws_dll_foreach_safe(&master->builder_head, sai_master_builder_destroy);

	lws_dll_foreach_safe(&master->pending_jobs_head, sai_destroy_job);
	lws_dll_foreach_safe(&master->ongoing_jobs_head, sai_destroy_job);
}

static int
callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
	    void *in, size_t len)
{
	struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get(
				lws_get_vhost(wsi), lws_get_protocol(wsi));
	uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
		*end = &buf[sizeof(buf) - LWS_PRE - 1];
	struct pss *pss = (struct pss *)user;
	struct sai_job *job;
	const char *ccp;
	int n;

	(void)end;
	(void)p;
	(void)paths;

	switch (reason) {
	case LWS_CALLBACK_PROTOCOL_INIT:
		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
						  lws_get_protocol(wsi),
						  sizeof(struct vhd));
		if (!vhd)
			return -1;

		vhd->context = lws_get_context(wsi);
		vhd->vhost = lws_get_vhost(wsi);
		break;

	case LWS_CALLBACK_PROTOCOL_DESTROY:
		sai_master_master_destroy(&vhd->master);
		break;

	case LWS_CALLBACK_HTTP:

		n = HTTP_STATUS_FORBIDDEN;

		if (strcmp((const char *)in, "/sai/update-hook")) {
			lwsl_notice("%s: unknown path %s\n", __func__,
				    (const char *)in);
			goto http_resp;
		}

		job = malloc(sizeof(*job));
		if (!job) {
			lwsl_err("%s: OOM\n", __func__);

			return 1;
		}

		ccp = lws_get_urlarg_by_name(wsi, "rn=", (char *)start,
					     sizeof(job->reponame));
		if (!ccp || !*ccp) {
			lwsl_notice("%s: missing rn=\n", __func__);
			free(job);
			goto http_resp;
		}
		lws_strncpy(job->reponame, ccp, sizeof(job->reponame));
		ccp = lws_get_urlarg_by_name(wsi, "ref=", (char *)start + 128,
					     sizeof(job->ref));
		if (!ccp || !*ccp) {
			lwsl_notice("%s: missing ref=\n", __func__);
			free(job);
			goto http_resp;
		}
		lws_strncpy(job->ref, ccp, sizeof(job->ref));
		ccp = lws_get_urlarg_by_name(wsi, "head=", (char *)start + 256,
					     sizeof(job->head));
		if (!ccp || !*ccp) {
			lwsl_notice("%s: missing ref=\n", __func__);
			free(job);
			goto http_resp;
		}
		lws_strncpy(job->head, ccp, sizeof(job->head));

		job->requested = time(NULL);

		ccp = strrchr(job->ref + strlen(job->ref) - 1, '/');
		if (ccp)
			ccp++;

		lwsl_user("%s: added job: %s %s %s\n", __func__, job->reponame,
			  ccp, job->head);

		{
			git_clone_options opts;
			git_repository *repo;
			const char *url = "https://libwebsockets.org/repo/libwebsockets";

			git_clone_init_options(&opts, GIT_CLONE_OPTIONS_VERSION);
			opts.checkout_branch = ccp;

			if (git_clone(&repo, url, "/tmp", &opts)) {
				lwsl_err("%s: unable to clone\n", __func__);
				free(job);
				goto http_resp;
			}

			lwsl_user("%s: clone OK\n", __func__);
		}

		lws_dll_add_front(&job->jobs_list, &vhd->master.pending_jobs_head);
		n = HTTP_STATUS_OK;

		/* faillthru */

http_resp:
		if (lws_add_http_header_status(wsi, n, &p, end))
			goto bail;
		if (lws_add_http_header_content_length(wsi, 0, &p, end))
			goto bail;
		if (lws_finalize_write_http_header(wsi, start, &p, end))
			goto bail;
		goto try_to_reuse;

	case LWS_CALLBACK_ESTABLISHED:

		if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) {
			if (lws_hdr_copy(wsi, (char *)start, 64, WSI_TOKEN_GET_URI) < 0)
				return -1;
		} else
			if (lws_hdr_copy(wsi, (char *)start, 64, WSI_TOKEN_HTTP_COLON_PATH) < 0)
				return -1;

		if (!strcmp((char *)start, "/browse")) {
			n = SWT_BUILDER;
		} else {
			if (!strcmp((char *)start, "builder")) {
				n = SWT_BROWSE;
			} else {
				lwsl_err("%s: unknown URL\n", __func__);

				return -1;
			}
		}

		pss->type = n;
		break;

	case LWS_CALLBACK_RECEIVE:
		lwsl_user("RECEIVE: %d\n", (int)len);
		lwsl_user("%.*s\n", (int)len, (const char *)in);
		break;

	default:
		break;
	}

	return lws_callback_http_dummy(wsi, reason, user, in, len);

bail:
	return 1;

try_to_reuse:
	if (lws_http_transaction_completed(wsi))
		return -1;

	return 0;
}

const struct lws_protocols protocol_ws =
	{ "com-warmcat-sai", callback_ws, sizeof(struct pss), 0 };