/*
 * config.c - hostmngr config handling.
 *
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/statfs.h>
#include <linux/magic.h>

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <uci.h>
#include <uci_blob.h>

#include <easy/easy.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "neigh.h"
#include "hostmngr.h"


static int hostmngr_config_get_base(struct hostmngr_config *cfg, struct uci_section *s)
{
	enum {
		HOSTMNGR_ENABLE,
		HOSTMNGR_SKIP_L2_DISCOVERY,
		HOSTMNGR_HISTORY,
		HOSTMNGR_HISTORY_PERSIST_REBOOT,
		HOSTMNGR_HISTORY_FILE,
		HOSTMNGR_HISTORY_TIMEOUT,
		HOSTMNGR_HISTORY_MAX_NUM,
		HOSTMNGR_HISTORY_MAX_SIZE,
		NUM_HOSTMNGR_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		{ .name = "enabled", .type = UCI_TYPE_STRING },
		{ .name = "skip_l2_discovery", .type = UCI_TYPE_STRING },
		{ .name = "history", .type = UCI_TYPE_STRING },
		{ .name = "history_reboot_persistent", .type = UCI_TYPE_STRING },
		{ .name = "history_file", .type = UCI_TYPE_STRING },
		{ .name = "history_timeout", .type = UCI_TYPE_STRING },
		{ .name = "history_max_entries", .type = UCI_TYPE_STRING },
		{ .name = "history_max_size", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_HOSTMNGR_ATTRS];


	uci_parse_section(s, opts, NUM_HOSTMNGR_ATTRS, tb);

	if (tb[HOSTMNGR_ENABLE]) {
		char *val = tb[HOSTMNGR_ENABLE]->v.string;

		cfg->enabled = strtol(val, NULL, 10) == 1 ? true : false;
	}

	if (tb[HOSTMNGR_SKIP_L2_DISCOVERY]) {
		char *val = tb[HOSTMNGR_SKIP_L2_DISCOVERY]->v.string;

		cfg->skip_l2_discovery = strtol(val, NULL, 10) == 1 ? true : false;
	}

	if (tb[HOSTMNGR_HISTORY]) {
		char *val = tb[HOSTMNGR_HISTORY]->v.string;

		cfg->history = strtol(val, NULL, 10) == 1 ? true : false;
	}

	if (tb[HOSTMNGR_HISTORY_PERSIST_REBOOT]) {
		char *val = tb[HOSTMNGR_HISTORY_PERSIST_REBOOT]->v.string;

		cfg->history_persist = strtol(val, NULL, 10) == 1 ? true : false;
	}

	if (tb[HOSTMNGR_HISTORY_TIMEOUT]) {
		char *val = tb[HOSTMNGR_HISTORY_TIMEOUT]->v.string;
		int tmo = strtol(val, NULL, 10);

		if (tmo >= 0)
			cfg->history_ageout = tmo;
		else
			dbg("Invalid config: history_timeout = %d\n", tmo);
	}

	if (tb[HOSTMNGR_HISTORY_FILE]) {
		char *val = tb[HOSTMNGR_HISTORY_FILE]->v.string;

		if (strlen(val) < FILE_PATH_MAX) {
			memset(cfg->history_file, 0, sizeof(cfg->history_file));
			strncpy(cfg->history_file, val, strlen(val));
		}
	}

	/* point history file to tmpfs if reboot persistence is not desired */
	if (!cfg->history_persist) {
		memset(cfg->history_file, 0, sizeof(cfg->history_file));
		strncpy(cfg->history_file, DEFAULT_HISTORY_TMPFS_FILE, FILE_PATH_MAX - 1);

		memset(cfg->hosts_file, 0, sizeof(cfg->hosts_file));
		strncpy(cfg->hosts_file, DEFAULT_HOSTS_TMPFS_FILE, FILE_PATH_MAX - 1);
	}

	if (tb[HOSTMNGR_HISTORY_MAX_NUM]) {
		const char *val = tb[HOSTMNGR_HISTORY_MAX_NUM]->v.string;
		int num = strtol(val, NULL, 10);

		if (num > 0)
			cfg->history_maxnum = num;
		else
			dbg("Invalid config: history_max_entries = %d\n", num);
	}

	return 0;
}

static void hostmngr_config_ifnamelist_append(struct hostmngr_config *cfg,
					      const char *name)
{
	size_t dlen = strlen(name);
	size_t sep = cfg->ifnamelistlen ? 1 : 0;	/* ',' before each but first */
	char *tmp;

	/* +1 for the terminating NUL */
	tmp = realloc(cfg->ifnamelist, cfg->ifnamelistlen + sep + dlen + 1);
	if (!tmp)
		return;

	cfg->ifnamelist = tmp;
	if (sep)
		cfg->ifnamelist[cfg->ifnamelistlen++] = ',';
	memcpy(cfg->ifnamelist + cfg->ifnamelistlen, name, dlen);
	cfg->ifnamelistlen += dlen;
	cfg->ifnamelist[cfg->ifnamelistlen] = '\0';
}

static int hostmngr_config_get_interfaces(struct hostmngr_config *cfg,
					  struct uci_section *s)
{
	enum {
		HOSTMNGR_ZONE_IFNAME,
		NUM_ZONE_IFNAME_ATTRS,
	};
	const struct blobmsg_policy zone_attrs[NUM_ZONE_IFNAME_ATTRS] = {
		[HOSTMNGR_ZONE_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_ARRAY },
	};
	const struct uci_blob_param_list ifnamelist = {
		.n_params = NUM_ZONE_IFNAME_ATTRS,
		.params = zone_attrs,
	};
	struct blob_attr *ctb, *attr;
	struct blob_buf b = {0};
	int rem;


	blob_buf_init(&b, 0);
	uci_to_blob(&b, s, &ifnamelist);
	ctb = blob_memdup(b.head);
	if (!ctb) {
		blob_buf_free(&b);
		return 0;
	}

	fprintf(stderr, "Config zone interfaces: ");
	blobmsg_for_each_attr(attr, ctb, rem) {
		if (blobmsg_type(attr) == BLOBMSG_TYPE_ARRAY) {
			struct blob_attr *x;
			int rem1;

			blobmsg_for_each_attr(x, attr, rem1) {
				if (blobmsg_type(x) != BLOBMSG_TYPE_STRING)
					continue;

				hostmngr_config_ifnamelist_append(cfg, blobmsg_data(x));
			}
		}
	}
	fprintf(stderr, "\n");

	free(ctb);
	blob_buf_free(&b);
	return 0;
}

void hostmngr_config_free(struct hostmngr_config *cfg)
{
	if (!cfg)
		return;

	if (cfg->ifnamelistlen > 0 && cfg->ifnamelist) {
		free(cfg->ifnamelist);
		cfg->ifnamelist = NULL;
		cfg->ifnamelistlen = 0;
	}
}

int hostmngr_reconfig(struct hostmngr_config *cfg, const char *path,
		      const char *file)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	int ret = 0;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (path) {
		uci_set_confdir(ctx, path);
		fprintf(stderr, "config path: %s  file: %s\n", path, file);
	}

	if (uci_load(ctx, file, &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);

		if (!strcmp(s->type, "global"))
			ret |= hostmngr_config_get_base(cfg, s);
		else if (!strcmp(s->type, "interface"))
			ret |= hostmngr_config_get_interfaces(cfg, s);
	}

	uci_free_context(ctx);
	return ret;
}

int hostmngr_config_defaults(struct hostmngr_config *cfg)
{
	cfg->enabled = true;
	cfg->skip_l2_discovery = false;
	cfg->history = true;
	cfg->history_persist = true;
	cfg->stats = true;
	strncpy(cfg->history_file, DEFAULT_HISTORY_FILE, FILE_PATH_MAX - 1);
	strncpy(cfg->hosts_file, DEFAULT_HOSTS_FILE, FILE_PATH_MAX - 1);
	cfg->history_ageout = DEFAULT_HISTORY_AGEOUT;
	cfg->history_maxnum = DEFAULT_HISTORY_MAX_NUM;
	cfg->history_maxsize = DEFAULT_HISTORY_MAX_SIZE;

	/* DEFAULT_LAN_IFNAME is always monitored (see hostmngr_include_interface);
	 * the list below is populated from the config 'interface' sections.
	 */
	cfg->ifnamelist = NULL;
	cfg->ifnamelistlen = 0;

	return 0;
}

int ap_get_mld_ifname(char *ifname, char *mld_ifname)
{
	enum {
		W_IFNAME,
		W_MLD,
		NUM_POLICIES
	};
	const struct uci_parse_option opts[] = {
		{ .name = "ifname", .type = UCI_TYPE_STRING },
		{ .name = "mld", .type = UCI_TYPE_STRING }
	};
	struct uci_option *tb[NUM_POLICIES];
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	int ret = -1;

	ctx = uci_alloc_context();
	if (!ctx)
		return ret;

	if (uci_load(ctx, "wireless", &pkg)) {
		uci_free_context(ctx);
		return ret;
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);
		struct uci_ptr ptr;

		if (strcmp(s->type, "wifi-iface"))
			continue;

		uci_parse_section(s, opts, NUM_POLICIES, tb);

		if (!tb[W_IFNAME])
			continue;

		if (strncmp(ifname, tb[W_IFNAME]->v.string, 16))
			continue;

		if (!tb[W_MLD])
			break;

		/* lookup mld ifname in wifi-mld section directly */
		ptr.package = "wireless";
		ptr.section = tb[W_MLD]->v.string;
		ptr.option = "ifname";
		ptr.target = UCI_TYPE_OPTION;

		ret = uci_lookup_ptr(ctx, &ptr, NULL, false);
		if (ret != UCI_OK ||!(ptr.flags & UCI_LOOKUP_DONE))
			break;

		if (!(ptr.flags & UCI_LOOKUP_COMPLETE))
			break;

		strncpy(mld_ifname, ptr.o->v.string, 16);
	}

	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return ret;
}

int hostmngr_dump_config(struct hostmngr_config *cfg)
{
	char *s, *ifnames = NULL;

	if (!cfg)
		return -1;

	fprintf(stderr, "Configuration\n");
	fprintf(stderr, "---\n");
	fprintf(stderr, " Enabled           : %d\n", cfg->enabled);
	fprintf(stderr, " Skip L2 discovery : %d\n", cfg->skip_l2_discovery);
	fprintf(stderr, " Collect stats     : %d\n", cfg->stats);
	fprintf(stderr, " History           : %d\n", cfg->history);
	fprintf(stderr, " History persists  : %d\n", cfg->history_persist);
	fprintf(stderr, " History file      : %s\n", cfg->history_file);
	fprintf(stderr, " History maxnum    : %u\n", cfg->history_maxnum);
	fprintf(stderr, " History maxsize   : %u\n", cfg->history_maxsize);
	fprintf(stderr, " Interfaces        : ");

	if (cfg->ifnamelistlen)
		ifnames = strdup(cfg->ifnamelist);

	foreach_token(s, ifnames, ", ") {
		fprintf(stderr, "%s ", s);
	}
	fprintf(stderr, "\n---\n");

	if (ifnames)
		free(ifnames);

	fprintf(stderr, "iflist = '%s'\n", cfg->ifnamelist);
	return 0;
}

