/*
 * Copyright (C) 2025 iopsys Software Solutions AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 *	Author: Mohd Husaam Mehdi <husaam.mehdi@iopsys.eu>
 */

#include "ipv6rd.h"

#define DEFAULT_SEC_NAME "wan6rd"

/*************************************************************
* COMMON FUNCTIONS
**************************************************************/
static void dmmap_synchronizeIPv6rdInterfaceSetting(struct dmctx *dmctx)
{
	struct uci_section *interface_s = NULL;
	bool is_6rd_obj_exists = false;
	char *instance = NULL;

	bbfdm_create_empty_file("/etc/bbfdm/dmmap/IPv6rd");

	uci_foreach_sections("network", "interface", interface_s) {

		char *proto = NULL;
		dmuci_get_value_by_section_string(interface_s, "proto", &proto);
		if (DM_LSTRCMP(proto, "6rd") != 0)
			continue;

		is_6rd_obj_exists = true;

		// Device.IPv6rd.InterfaceSetting.{i}.
		create_dmmap_obj(dmctx, 0, "IPv6rd", "InterfaceSetting", interface_s, &instance);
	}

	if (is_6rd_obj_exists == false) {
		// no 6rd section was found
		// we still need to present one 6rd object with empty values since this object is readable
		// and if the any parameter is set then section will be created

		// Device.IPv6rd.InterfaceSetting.{i}.
		create_dmmap_obj(dmctx, 0, "IPv6rd", "InterfaceSetting", interface_s, &instance);
	}

	dmuci_commit_package_bbfdm("IPv6rd");
}

static void check_and_add_6rd_section(void **data)
{
	struct dm_data *curr_data = *((struct dm_data **)data);

	if (curr_data->config_section == NULL) {
		// Create config section
		dmuci_add_section("network", "interface", &curr_data->config_section);
		dmuci_rename_section_by_section(curr_data->config_section, DEFAULT_SEC_NAME);
		dmuci_set_value_by_section(curr_data->config_section, "proto", "6rd");
		dmuci_set_value_by_section(curr_data->config_section, "disabled", "1");

		// Update dmmap section
		char sec_name[128] = {0};

		snprintf(sec_name, sizeof(sec_name), "network.%s", DEFAULT_SEC_NAME);
		dmuci_set_value_by_section(curr_data->dmmap_section, "__section_name__", sec_name);
	}
}

/*************************************************************
* GET & SET PARAM
**************************************************************/
static int get_IPv6rd_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	// no way to disable ipv6rd for now, always enabled
	*value = dmstrdup("1");
	return 0;
}

static int set_IPv6rd_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			// no way to disable ipv6rd for now, always enabled
			break;
	}
	return 0;
}

static int get_IPv6rd_InterfaceSettingNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, NULL);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_IPv6rdInterfaceSetting_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (((struct dm_data *)data)->config_section == NULL)
		return 0;

	char *disabled = NULL;
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "disabled", &disabled);

	if (DM_STRCMP(disabled, "1") != 0)
		*value = dmstrdup("1");

	return 0;
}

static int set_IPv6rdInterfaceSetting_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			check_and_add_6rd_section(&data);

			string_to_bool(value, &b);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "disabled", b ? "0" : "1");
			break;
	}
	return 0;
}

static int get_IPv6rdInterfaceSetting_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	get_IPv6rdInterfaceSetting_Enable(refparam, ctx, data, instance, value);
	*value = ((*value)[0] == '1') ? "Enabled" : "Disabled";
	return 0;
}

static int get_IPv6rdInterfaceSetting_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int set_IPv6rdInterfaceSetting_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int get_IPv6rdInterfaceSetting_BorderRelayIPv4Addresses(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (((struct dm_data *)data)->config_section == NULL)
		return 0;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "peeraddr", value);
	return 0;
}

static int set_IPv6rdInterfaceSetting_BorderRelayIPv4Addresses(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			// only single IPv4 address is allowed in openwrt
			if (bbfdm_validate_string(ctx, value, -1, 15, NULL, IPv4Address))
				return FAULT_9007;
			break;
		case VALUESET:
			check_and_add_6rd_section(&data);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "peeraddr", value);
			break;
	}
	return 0;
}

static int get_IPv6rdInterfaceSetting_AllTrafficToBorderRelay(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (((struct dm_data *)data)->config_section == NULL)
		return 0;

	// this is always true in openwrt
	*value = dmstrdup("1");
	return 0;
}

static int set_IPv6rdInterfaceSetting_AllTrafficToBorderRelay(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			// this is always true in openwrt
			// if logic is added in future, remember to use check_and_add_6rd_section
			break;
	}
	return 0;
}

static int get_IPv6rdInterfaceSetting_SPIPv6Prefix(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (((struct dm_data *)data)->config_section == NULL)
		return 0;

	char *ip6prefix = NULL, *ip6prefixlen = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "ip6prefix", &ip6prefix);
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "ip6prefixlen", &ip6prefixlen);

	if (DM_STRLEN(ip6prefix) && DM_STRLEN(ip6prefixlen))
		dmasprintf(value, "%s/%s", ip6prefix, ip6prefixlen);

	return 0;
}

static int set_IPv6rdInterfaceSetting_SPIPv6Prefix(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, NULL, IPv6Prefix))
				return FAULT_9007;
			break;
		case VALUESET:
			check_and_add_6rd_section(&data);
			// separate the prefix and prefix len
			char *slash = DM_STRCHR(value, '/');
			if (!slash)
				return FAULT_9002;

			// terminate prefix where slash was present
			*slash = '\0';

			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "ip6prefix", value);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "ip6prefixlen", slash + 1);
			break;
	}
	return 0;
}

static int get_IPv6rdInterfaceSetting_IPv4MaskLength(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (((struct dm_data *)data)->config_section == NULL)
		return 0;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "ip4prefixlen", value);
	return 0;
}

static int set_IPv6rdInterfaceSetting_IPv4MaskLength(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"0","32"}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			check_and_add_6rd_section(&data);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "ip4prefixlen", value);
			break;
	}
	return 0;
}

static int get_IPv6rdInterfaceSetting_AddressSource(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (((struct dm_data *)data)->config_section == NULL)
		return 0;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->dmmap_section, "AddressSource", value);

	if (!value || (*value)[0] == '\0') {
		char *ipaddr = NULL;

		dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "ipaddr", &ipaddr);

		_bbfdm_get_references(ctx, "Device.IP.Interface.*.IPv4Address.", "IPAddress", ipaddr, value);

		// save it for later
		dmuci_set_value_by_section(((struct dm_data *)data)->dmmap_section, "AddressSource", *value);
	}

	return 0;
}

static int set_IPv6rdInterfaceSetting_AddressSource(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_objects[] = {"Device.IP.Interface.*.IPv4Address.", NULL};
	struct dm_reference reference = {0};

	bbfdm_get_reference_linker(ctx, value, &reference);

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, reference.path, -1, 256, NULL, NULL))
				return FAULT_9007;

			if (dm_validate_allowed_objects(ctx, &reference, allowed_objects))
				return FAULT_9007;
			break;
		case VALUESET:
			if (DM_STRLEN(reference.value)) {
				check_and_add_6rd_section(&data);
				dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "ipaddr", reference.value);
				dmuci_set_value_by_section(((struct dm_data *)data)->dmmap_section, "AddressSource", reference.path);
			}
			break;
	}
	return 0;
}

static int get_IPv6rdInterfaceSetting_TunnelInterface(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	// Tunnel, Tunneled interfaces are not yet supported
	// when their support is added, we can update this function
	// TODO
	return 0;
}

static int get_IPv6rdInterfaceSetting_TunneledInterface(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	// Tunnel, Tunneled interfaces are not yet supported
	// when their support is added, we can update this function
	// TODO
	return 0;
}

/*************************************************************
* Init & Clean Module
**************************************************************/
int init_ipv6rd_module(void *data)
{
	struct dmctx bbf_ctx = {0};

	bbf_ctx_init(&bbf_ctx, NULL);
	dmmap_synchronizeIPv6rdInterfaceSetting(&bbf_ctx);
	bbf_ctx_clean(&bbf_ctx);

	return 0;
}

/**********************************************************************************************************************************
*                                            OBJ & PARAM DEFINITION
***********************************************************************************************************************************/
/* *** Device. *** */
DMOBJ tDeviceIPv6rdObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"IPv6rd", &DMREAD, NULL, NULL, "file:/lib/netifd/proto/6rd.sh,/etc/config/network", NULL, NULL, NULL, tIPv6rdObj, tIPv6rdParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/* *** Device.IPv6rd. *** */
DMOBJ tIPv6rdObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys */
{"InterfaceSetting", &DMREAD, NULL, NULL, NULL, generic_browse, NULL, NULL, NULL, tIPv6rdInterfaceSettingParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMLEAF tIPv6rdParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Enable", &DMWRITE, DMT_BOOL, get_IPv6rd_Enable, set_IPv6rd_Enable, BBFDM_BOTH},
{"InterfaceSettingNumberOfEntries", &DMREAD, DMT_UNINT, get_IPv6rd_InterfaceSettingNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.IPv6rd.InterfaceSetting.{i}. *** */
DMLEAF tIPv6rdInterfaceSettingParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Enable", &DMWRITE, DMT_BOOL, get_IPv6rdInterfaceSetting_Enable, set_IPv6rdInterfaceSetting_Enable, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_IPv6rdInterfaceSetting_Status, NULL, BBFDM_BOTH},
{"Alias", &DMWRITE, DMT_STRING, get_IPv6rdInterfaceSetting_Alias, set_IPv6rdInterfaceSetting_Alias, BBFDM_BOTH},
{"BorderRelayIPv4Addresses", &DMWRITE, DMT_STRING, get_IPv6rdInterfaceSetting_BorderRelayIPv4Addresses, set_IPv6rdInterfaceSetting_BorderRelayIPv4Addresses, BBFDM_BOTH},
{"AllTrafficToBorderRelay", &DMWRITE, DMT_BOOL, get_IPv6rdInterfaceSetting_AllTrafficToBorderRelay, set_IPv6rdInterfaceSetting_AllTrafficToBorderRelay, BBFDM_BOTH},
{"SPIPv6Prefix", &DMWRITE, DMT_STRING, get_IPv6rdInterfaceSetting_SPIPv6Prefix, set_IPv6rdInterfaceSetting_SPIPv6Prefix, BBFDM_BOTH},
{"IPv4MaskLength", &DMWRITE, DMT_UNINT, get_IPv6rdInterfaceSetting_IPv4MaskLength, set_IPv6rdInterfaceSetting_IPv4MaskLength, BBFDM_BOTH},
{"AddressSource", &DMWRITE, DMT_STRING, get_IPv6rdInterfaceSetting_AddressSource, set_IPv6rdInterfaceSetting_AddressSource, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"TunnelInterface", &DMREAD, DMT_STRING, get_IPv6rdInterfaceSetting_TunnelInterface, NULL, BBFDM_BOTH},
{"TunneledInterface", &DMREAD, DMT_STRING, get_IPv6rdInterfaceSetting_TunneledInterface, NULL, BBFDM_BOTH},
{0}
};
