/*
 * sai-builder conf.c
 *
 * 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
 */

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

#include "private.h"

static const char * const paths[] = {
	"systemd-nspawn.path",
	"systemd-nspawn.instances",
	"systemd-nspawn.timeout",

	"target[]",
	"target[].name",
	"target[].timeout",
	"target[].consoles[]",
	"target[].consoles[].name",
	"target[].consoles[].path",
	"target[].consoles[].baudrate",
	"target[].consoles[].mode",

	"masters[]",
	"masters[].url",
	"masters[].interface",
	"masters[].selfsigned",
	"masters[].priority", /* array of builder names */
};

enum enum_paths {
	LEJPM_NSPAWN_PATH,
	LEJPM_NSPAWN_INSTANCES,
	LEJPM_NSPAWN_TIMEOUT,

	LEJPM_TARGET,
	LEJPM_TARGET_NAME,
	LEJPM_TARGET_TIMEOUT,
	LEJPM_TARGET_CONSOLES,
	LEJPM_TARGET_CONSOLES_NAME,
	LEJPM_TARGET_CONSOLES_PATH,
	LEJPM_TARGET_CONSOLES_BAUDRATE,
	LEJPM_TARGET_CONSOLES_MODE,

	LEJPM_MASTERS,
	LEJPM_MASTERS_URL,
	LEJPM_MASTERS_INTERFACE,
	LEJPM_MASTERS_SELFSIGNED,
	LEJPM_MASTERS_PRIORITY,
};

struct jpargs {
	struct sai_builder *builder;

	struct sai_nspawn *nspawn;
	struct sai_master *master;
	struct sai_target *target;
	struct sai_console *console;
};

#define SAI_BUILDER_CONF_CHUNK 512
#define SAI_BUILDER_PATH_MAX 128

static int
sai_builder_scan_overlays_cb(const char *dirpath, void *user,
			     struct lws_dir_entry *lde)
{
	struct sai_nspawn *nspawn = (struct sai_nspawn *)user;
	size_t s = strlen(lde->name);
	struct sai_overlay *overlay;

	if (lde->type != LDOT_DIR)
		return 0;

	if (strncmp(lde->name, "env-", 4))
		return 0;

	overlay = lwsac_use(&nspawn->builder->conf_head, sizeof(*overlay),
			 SAI_BUILDER_CONF_CHUNK);
	if (!overlay)
		return 1;

	memset(overlay, 0, sizeof(*overlay));
	overlay->name = lwsac_use(&nspawn->builder->conf_head, s + 1,
				 SAI_BUILDER_CONF_CHUNK);
	if (!overlay->name)
		return 1;
	memcpy(overlay->name, lde->name, s + 1);

	lws_dll_add_front(&overlay->overlay_list, &nspawn->overlay_head);

	return 0;
}

static int
sai_builder_scan_nspawn_cb(const char *dirpath, void *user,
			   struct lws_dir_entry *lde)
{
	struct sai_builder *builder = (struct sai_builder *)user;
	size_t s = strlen(lde->name);
	struct sai_nspawn *nspawn;
	char path[256];

	if (lde->type != LDOT_DIR)
		return 0;

	nspawn = lwsac_use(&builder->conf_head, sizeof(*nspawn),
			 SAI_BUILDER_CONF_CHUNK);
	if (!nspawn)
		return 1;

	memset(nspawn, 0, sizeof(*nspawn));
	nspawn->builder = builder;
	nspawn->name = lwsac_use(&builder->conf_head, s + 1,
				 SAI_BUILDER_CONF_CHUNK);
	if (!nspawn->name) {
		free(nspawn);
		return 1;
	}
	memcpy((void *)nspawn->name, lde->name, s + 1);

	lws_dll_add_front(&nspawn->nspawn_list, &builder->nspawn_head);

	lws_snprintf(path, sizeof(path) - 1, "%s/%s", dirpath, lde->name);

	if (lws_dir(path, nspawn, sai_builder_scan_overlays_cb)) {
		lwsl_err("%s: unable to scan nspawn dir %s\n", __func__,
			 builder->nspawn_path);
		return 1;
	}

	return 0;
}

static signed char
sai_builder_conf_cb(struct lejp_ctx *ctx, char reason)
{
	struct jpargs *a = (struct jpargs *)ctx->user;
	const char **pp;
	int n;

#if 0
	lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match);
	for (n = 0; n < ctx->wildcount; n++)
		lwsl_notice("    %d\n", ctx->wild[n]);
#endif

	if (reason == LEJPCB_OBJECT_START) {
		switch (ctx->path_match - 1) {
		case LEJPM_TARGET:
			a->target = lwsac_use_zeroed(&a->builder->conf_head,
						     sizeof(*a->target),
						     SAI_BUILDER_CONF_CHUNK);
			if (!a->target)
				return 1;
			lws_dll_add_front(&a->target->target_list,
					  &a->builder->target_head);
			return 0;

		case LEJPM_TARGET_CONSOLES:
			a->console = lwsac_use_zeroed(&a->builder->conf_head,
						      sizeof(*a->console),
						      SAI_BUILDER_CONF_CHUNK);
			if (!a->console)
				return 1;
			lws_dll_add_front(&a->console->console_list,
					  &a->target->console_head);
			return 0;

		case LEJPM_MASTERS:
			a->master = lwsac_use_zeroed(&a->builder->conf_head,
						     sizeof(*a->master),
						     SAI_BUILDER_CONF_CHUNK);
			if (!a->master)
				return 1;
			lws_dll_add_front(&a->master->master_list,
					  &a->builder->master_head);
			return 0;

		default:
			return 0;
		}
	}

	/* we only match on the prepared path strings */
	if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
		return 0;

	if (reason == LEJPCB_VAL_NUM_INT) {
		n =  atoi(ctx->buf);
		switch (ctx->path_match - 1) {
		case LEJPM_NSPAWN_INSTANCES:
			a->builder->nspawn_instances = n;
			break;
		case LEJPM_NSPAWN_TIMEOUT:
			a->builder->nspawn_timeout = n;
			break;
		case LEJPM_TARGET_TIMEOUT:
			a->target->timeout  = n;
			break;
		case LEJPM_TARGET_CONSOLES_BAUDRATE:
			a->console->baudrate = n;
			break;
		case LEJPM_MASTERS_SELFSIGNED:
			a->master->accept_selfsigned = n;
			break;
		case LEJPM_MASTERS_PRIORITY:
			a->master->priority = n;
			break;
		default:
			break;
		}

		return 0;
	}

	if (reason != LEJPCB_VAL_STR_END)
		return 0;

	/* only the end part of the string, where we know the length */

	switch (ctx->path_match - 1) {
	case LEJPM_NSPAWN_PATH:
		pp = &a->builder->nspawn_path;
		break;

	case LEJPM_TARGET_NAME:
		pp = &a->target->name;
		break;

	case LEJPM_TARGET_CONSOLES_NAME:
		pp = &a->console->name;
		break;

	case LEJPM_TARGET_CONSOLES_PATH:
		pp = &a->console->path;
		break;

	case LEJPM_TARGET_CONSOLES_MODE:
		pp = &a->console->mode;
		break;

	case LEJPM_MASTERS_URL:
		pp = &a->master->url;
		break;

	case LEJPM_MASTERS_INTERFACE:
		pp = &a->master->interface;
		break;

	default:
		return 0;
	}

	*pp = lwsac_use(&a->builder->conf_head, ctx->npos + 1,
			SAI_BUILDER_CONF_CHUNK);
	if (!*pp)
		return 1;
	memcpy((char *)(*pp), ctx->buf, ctx->npos);
	((char *)(*pp))[ctx->npos] = '\0';

	return 0;
}

int
sai_builder_config(struct sai_builder *builder, const char *d)
{
	unsigned char buf[128];
	struct lejp_ctx ctx;
	struct jpargs a;
	int n, m, fd;

	memset(&a, 0, sizeof(a));
	a.builder = builder;

	lws_snprintf((char *)buf, sizeof(buf) - 1, "%s/conf", d);

	fd = lws_open((char *)buf, O_RDONLY);
	if (fd < 0) {
		lwsl_err("Cannot open %s\n", (char *)buf);
		return 2;
	}
	lwsl_info("%s: %s\n", __func__, (char *)buf);
	lejp_construct(&ctx, sai_builder_conf_cb, &a, paths, LWS_ARRAY_SIZE(paths));

	do {
		n = read(fd, buf, sizeof(buf));
		if (!n)
			break;

		m = (int)(signed char)lejp_parse(&ctx, buf, n);
	} while (m == LEJP_CONTINUE);

	close(fd);
	n = ctx.line;
	lejp_destruct(&ctx);

	if (m < 0) {
		lwsl_err("%s/conf(%u): parsing error %d: %s\n", d, n, m,
			 lejp_error_to_string(m));
		return 2;
	}

	/* scan the configured nspawn directory for what we support */

	if (builder->nspawn_path &&
	    lws_dir(builder->nspawn_path, builder, sai_builder_scan_nspawn_cb)) {
		lwsl_err("%s: unable to scan nspawn dir %s\n", __func__,
			 builder->nspawn_path);

		return 1;
	}

	return 0;
}

void
sai_builder_config_destroy(struct sai_builder *builder)
{
	lwsac_free(&builder->conf_head);
}