/*
 * swmod_uci.c: SWMOD deamon
 *
 * Copyright (C) 2020-2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: Amin Ben Ramdhane <amin.benramdhane@pivasoftware.com>
 *
 * See LICENSE file for license related information.
 */

#include <string.h>
#include <ctype.h>

#include "swmod_uci.h"
#include "tools.h"

static struct uci_context *uci_ctx_swmod = NULL;

static int swmod_commit_package(const char *package)
{
	struct uci_ptr ptr = {0};

	if (uci_lookup_ptr(uci_ctx_swmod, &ptr, (char *)package, true) != UCI_OK) {
		PRINT_ERR("Package (%s) not found", package);
		return -1;
	}

	if (uci_commit(uci_ctx_swmod, &ptr.p, false) != UCI_OK) {
		PRINT_ERR("Unable to commit %s", package);
		return -1;
	}

	return 0;
}

int swmod_uci_init(const char *conf_path)
{
	uci_ctx_swmod = uci_alloc_context();
	if (!uci_ctx_swmod)
		return -1;

	uci_set_confdir(uci_ctx_swmod, conf_path);

	return 0;
}

int swmod_uci_fini(const char *package_name)
{
	/* Commit package */
	swmod_commit_package(package_name);

	/* Freed uci context  */
	if (uci_ctx_swmod)
		uci_free_context(uci_ctx_swmod);

	return 0;
}

static bool swmod_uci_validate_section(const char *str)
{
	if (!*str)
		return false;

	for (; *str; str++) {
		unsigned char c = *str;

		if (isalnum(c) || c == '_')
			continue;

		return false;
	}

	return true;
}

static int swmod_uci_init_ptr(struct uci_ptr *ptr, const char *package, const char *section, const char *option, const char *value)
{
	memset(ptr, 0, sizeof(struct uci_ptr));

	/* value */
	if (value)
		ptr->value = value;

	ptr->package = package;
	if (!ptr->package)
		goto error;

	ptr->section = section;
	if (!ptr->section) {
		ptr->target = UCI_TYPE_PACKAGE;
		goto lastval;
	}

	ptr->option = option;
	if (!ptr->option) {
		ptr->target = UCI_TYPE_SECTION;
		goto lastval;
	} else
		ptr->target = UCI_TYPE_OPTION;

lastval:
	if (ptr->section && !swmod_uci_validate_section(ptr->section))
		ptr->flags |= UCI_LOOKUP_EXTENDED;

	return 0;

error:
	return -1;
}

struct uci_section *swmod_uci_walk_section(const char *package, const char *section_type, struct uci_section *prev_section)
{
	struct uci_ptr ptr;
	struct uci_element *e;
	struct uci_section *next_section;

	if (section_type == NULL) {
		if (prev_section) {
			e = &prev_section->e;
			if (e->list.next == &prev_section->package->sections)
				return NULL;
			e = container_of(e->list.next, struct uci_element, list);
			next_section = uci_to_section(e);
			return next_section;
		} else {
			if (swmod_uci_init_ptr(&ptr, package, NULL, NULL, NULL))
				return NULL;

			if (uci_lookup_ptr(uci_ctx_swmod, &ptr, NULL, true) != UCI_OK)
				return NULL;

			if (ptr.p->sections.next == &ptr.p->sections)
				return NULL;
			e = container_of(ptr.p->sections.next, struct uci_element, list);
			next_section = uci_to_section(e);

			return next_section;
		}
	} else {
		struct uci_list *ul = NULL, *shead = NULL;

		if (prev_section) {
			ul = &prev_section->e.list;
			shead = &prev_section->package->sections;
		} else {
			if (swmod_uci_init_ptr(&ptr, package, NULL, NULL, NULL))
				return NULL;

			if (uci_lookup_ptr(uci_ctx_swmod, &ptr, NULL, true) != UCI_OK)
				return NULL;

			ul = &ptr.p->sections;
			shead = &ptr.p->sections;
		}
		while (ul->next != shead) {
			e = container_of(ul->next, struct uci_element, list);
			next_section = uci_to_section(e);
			if (strcmp(next_section->type, section_type) == 0)
				return next_section;
			ul = ul->next;
		}
		return NULL;
	}
	return NULL;
}

static struct uci_element *swmod_uci_lookup_list(struct uci_list *list, const char *name)
{
	struct uci_element *e;

	uci_foreach_element(list, e) {
		if (!strcmp(e->name, name))
			return e;
	}

	return NULL;
}

static int swmod_uci_lookup_ptr_by_section(struct uci_ptr *ptr, struct uci_section *section, const char *option, const char *value)
{
	struct uci_element *e;
	memset(ptr, 0, sizeof(struct uci_ptr));

	ptr->package = section->package->e.name;
	ptr->section = section->e.name;
	ptr->option = option;
	ptr->value = value;
	ptr->flags |= UCI_LOOKUP_DONE;

	ptr->p = section->package;
	ptr->s = section;

	if (ptr->option) {
		e = swmod_uci_lookup_list(&ptr->s->options, ptr->option);
		if (!e)
			return UCI_OK;
		ptr->o = uci_to_option(e);
		ptr->last = e;
		ptr->target = UCI_TYPE_OPTION;
	} else {
		ptr->last = &ptr->s->e;
		ptr->target = UCI_TYPE_SECTION;
	}

	ptr->flags |= UCI_LOOKUP_COMPLETE;

	return UCI_OK;
}

char *swmod_uci_get_value_by_section(struct uci_section *section, const char *option)
{
	struct uci_ptr ptr;

	if (swmod_uci_lookup_ptr_by_section(&ptr, section, option, NULL) != UCI_OK)
		return "";

	if (!ptr.o)
		return "";

	if (ptr.o->v.string)
		return ptr.o->v.string;
	else
		return "";
}

char *swmod_uci_set_value_by_section(struct uci_section *section, const char *option, const char *value)
{
	struct uci_ptr ptr;

	if (section == NULL)
		return "";

	if (swmod_uci_lookup_ptr_by_section(&ptr, section, option, value) != UCI_OK)
		return "";

	if (uci_set(uci_ctx_swmod, &ptr) != UCI_OK)
		return "";

	if (uci_save(uci_ctx_swmod, ptr.p) != UCI_OK)
		return "";

	if (ptr.o && ptr.o->v.string)
		return ptr.o->v.string;

	return "";
}

char *swmod_uci_set_value_by_section_list(struct uci_section *section, const char *option, const char *value)
{
	struct uci_ptr ptr;

	if (section == NULL)
		return "";

	if (swmod_uci_lookup_ptr_by_section(&ptr, section, option, value) != UCI_OK)
		return "";

	if (uci_add_list(uci_ctx_swmod, &ptr) != UCI_OK)
		return "";

	if (uci_save(uci_ctx_swmod, ptr.p) != UCI_OK)
		return "";

	if (ptr.o && ptr.o->v.string)
		return ptr.o->v.string;

	return "";
}

struct uci_section *swmod_uci_add_section(const char *package, const char *section_type, bool rename)
{
	struct uci_ptr ptr;
	struct uci_section *s = NULL;

	if (swmod_uci_init_ptr(&ptr, package, NULL, NULL, NULL)) {
		PRINT_ERR("uci init failed %s::%s", package, section_type);
		return NULL;
	}

	if (uci_lookup_ptr(uci_ctx_swmod, &ptr, NULL, true) != UCI_OK) {
		PRINT_ERR("uci lookup failed %s::%s", package, section_type);
		return NULL;
	}

	if (uci_add_section(uci_ctx_swmod, ptr.p, section_type, &s) != UCI_OK) {
		PRINT_ERR("uci add failed %s::%s", package, section_type);
		return NULL;
	}

	if (rename == true) {
		ptr.package = s->package->e.name;
		ptr.p = s->package;

		ptr.section = s->e.name;
		ptr.s = s;

		ptr.value = section_type;
		ptr.target = UCI_TYPE_SECTION;

		if (uci_lookup_ptr(uci_ctx_swmod, &ptr, NULL, true) == UCI_OK) {
			uci_rename(uci_ctx_swmod, &ptr);
		}
	}

	if (uci_save(uci_ctx_swmod, ptr.p) != UCI_OK)
		return NULL;

	return s;
}

int swmod_uci_delete_by_section(struct uci_section *section, const char *option, const char *value)
{
	struct uci_ptr ptr = {0};

	if (section == NULL)
		return -1;

	if (swmod_uci_lookup_ptr_by_section(&ptr, section, option, value) != UCI_OK)
		return -1;

	if (uci_delete(uci_ctx_swmod, &ptr) != UCI_OK)
		return -1;

	if (uci_save(uci_ctx_swmod, ptr.p) != UCI_OK)
		return -1;

	return 0;
}

bool check_section_exist_by_option(const char *section, const char *section_type, const char *option, const char *val_check, struct uci_section **s)
{
	swmod_uci_foreach_section(section, section_type, *s) {
		const char *val = swmod_uci_get_value_by_section(*s, option);

		if (strcmp(val, val_check) == 0)
			return true;
	}
	return false;
}

bool uci_str_to_bool(const char *value)
{
	if (!value)
		return false;

	if (strncasecmp(value, "true", 4) == 0 ||
	    value[0] == '1' ||
	    strncasecmp(value, "on", 2) == 0 ||
	    strncasecmp(value, "yes", 3) == 0 ||
	    strncasecmp(value, "enable", 6) == 0)
		return true;

	return false;
}

int swmod_uci_commit(char *package)
{
	struct blob_buf b;
	uint32_t id;
	int rc = -1;

	if (package == NULL)
		return rc;

	memset(&b, 0, sizeof(struct blob_buf));
	blob_buf_init(&b, 0);
	blobmsg_add_string(&b, "config", package);

	if (!ubus_lookup_id(ubus_ctx, "uci", &id))
		rc = ubus_invoke(ubus_ctx, id, "commit", b.head, NULL, NULL, UBUS_TIMEOUT);

	blob_buf_free(&b);

	return rc;
}
