/*
 * Copyright (C) 2023-2024 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: Amin Ben Romdhane <amin.benromdhane@iopsys.eu>
 */

#include "interfacestack.h"

#define BBFDM_DMMAP_BRIDGING_DIR_PATH "/etc/bbfdm/dmmap/Bridging"

struct interfacestack_data
{
	char *HigherLayer;
	char *LowerLayer;
	char *HigherAlias;
	char *LowerAlias;
};

/*************************************************************
* COMMON FUNCTIONS
**************************************************************/
static struct uci_section *get_bridge_management_port_section(const char *instance)
{
	struct uci_section *s = NULL;
	char *management = NULL;

	uci_path_foreach_option_eq(bbfdm, "dmmap_bridge_port", "bridge_port", "br_inst", instance, s) {

		dmuci_get_value_by_section_string(s, "management", &management);
		if (DM_STRCMP(management, "1") == 0)
			return s;
	}

	return NULL;
}

static char *get_lower_alias_value(const char *path)
{
	struct uci_section *s = NULL;
	char *alias_value = NULL;

	if (DM_STRLEN(path) == 0)
		return "";

	char *instance = DM_STRRCHR(path, '.');
	if (!instance)
		return "";

	if (DM_STRNCMP(path, "Device.IP.Interface.", strlen("Device.IP.Interface.")) == 0) {
		get_dmmap_section_of_config_section_eq("IP", "Interface", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.Ethernet.Link.", strlen("Device.Ethernet.Link.")) == 0) {
		get_dmmap_section_of_config_section_eq("Ethernet", "Link", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.Ethernet.Interface.", strlen("Device.Ethernet.Interface.")) == 0) {
		get_dmmap_section_of_config_section_eq("Ethernet", "Interface", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.Bridging.Bridge.", strlen("Device.Bridging.Bridge.")) == 0 ||
			DM_STRNCMP(path, "Device.GRE.Tunnel.", strlen("Device.GRE.Tunnel.")) == 0) {
		regmatch_t pmatch[1] = {0};

		bool res = match(path, "([0-9]+)", 1, pmatch);
		if (res) {
			char first_inst[8] = {0};
			char *second_inst = NULL;

			DM_STRNCPY(first_inst, &path[pmatch[0].rm_so], pmatch[0].rm_eo - pmatch[0].rm_so + 1);
			if (DM_STRLEN(first_inst) == 0)
				return "";

			if (DM_STRNCMP(path, "Device.Bridging.Bridge.", strlen("Device.Bridging.Bridge.")) == 0) {
				if (file_exists(BBFDM_DMMAP_BRIDGING_DIR_PATH)) {
					/* New dmmap method: use Bridging/Port sections */
					regmatch_t pm[3] = {0};
					bool res2 = match(path, "Device\\.Bridging\\.Bridge\\.([0-9]+)\\.Port\\.([0-9]+)", 3, pm);
					if (res2) {
						char br_inst[8] = {0};
						char port_inst[8] = {0};

						DM_STRNCPY(br_inst, &path[pm[1].rm_so], pm[1].rm_eo - pm[1].rm_so + 1);
						DM_STRNCPY(port_inst, &path[pm[2].rm_so], pm[2].rm_eo - pm[2].rm_so + 1);

						uci_path_foreach_sections(bbfdm, "Bridging", "Port", s) {
							char *sec_name = s->e.name;
							char *inst = NULL;

							dmuci_get_value_by_section_string(s, "__instance__", &inst);

							if (DM_STRLEN(sec_name) == 0 || DM_STRLEN(inst) == 0)
								continue;

							char prefix[32] = {0};
							snprintf(prefix, sizeof(prefix), "Bridge_%sPort_", br_inst);
							if (DM_STRNCMP(sec_name, prefix, DM_STRLEN(prefix)) != 0)
								continue;

							if (DM_STRCMP(inst, port_inst) == 0) {
								dmuci_get_value_by_section_string(s, "Alias", &alias_value);
								break;
							}
						}
					}
				} else {
					/* Old dmmap method: use dmmap_bridge_port */
					uci_path_foreach_option_eq(bbfdm, "dmmap_bridge_port", "bridge_port", "br_inst", first_inst, s) {
						dmuci_get_value_by_section_string(s, "bridge_port_instance", &second_inst);
						if (DM_STRCMP(second_inst, instance + 1) == 0) {
							dmuci_get_value_by_section_string(s, "bridge_port_alias", &alias_value);
							break;
						}
					}
				}
			} else if (DM_STRNCMP(path, "Device.GRE.Tunnel.", strlen("Device.GRE.Tunnel.")) == 0) {
				uci_path_foreach_option_eq(bbfdm, "GRE", "Interface", "Tunnel", first_inst, s) {
					dmuci_get_value_by_section_string(s, "__instance__", &second_inst);
					if (DM_STRCMP(second_inst, instance + 1) == 0) {
						dmuci_get_value_by_section_string(s, "Alias", &alias_value);
						break;
					}
				}
			}
		}
	} else if (DM_STRNCMP(path, "Device.WiFi.SSID.", strlen("Device.WiFi.SSID.")) == 0) {
		get_dmmap_section_of_config_section_eq("WiFi", "SSID", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.WiFi.Radio.", strlen("Device.WiFi.Radio.")) == 0) {
		get_dmmap_section_of_config_section_eq("WiFi", "Radio", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.Ethernet.VLANTermination.", strlen("Device.Ethernet.VLANTermination.")) == 0) {
		get_dmmap_section_of_config_section_eq("Ethernet", "VLANTermination", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.PPP.Interface.", strlen("Device.PPP.Interface.")) == 0) {
		get_dmmap_section_of_config_section_eq("PPP", "Interface", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	} else if (DM_STRNCMP(path, "Device.Ethernet."BBF_VENDOR_PREFIX"MACVLAN.", strlen("Device.Ethernet."BBF_VENDOR_PREFIX"MACVLAN.")) == 0) {
		get_dmmap_section_of_config_section_eq("Ethernet", BBF_VENDOR_PREFIX"MACVLAN", "__instance__", instance + 1, &s);
		dmuci_get_value_by_section_string(s, "Alias", &alias_value);
	}

	return alias_value ? alias_value : "";
}

static int create_interface_stack_instance(struct dmctx *dmctx, DMNODE *parent_node,
		struct interfacestack_data *data, struct uci_section *s,
		const char *path, const char *inst_number, const char *inst_alias, int *curr_inst)
{
	struct dm_data curr_data = {0};
	char *instance = NULL, *inst = NULL;
	char *LowerLayer = NULL;

	if (!s || !data || !path || !inst_number || !inst_alias)
		goto end;

	dmuci_get_value_by_section_string(s, inst_number, &instance);
	if (DM_STRLEN(instance) == 0)
		goto end;

	dmasprintf(&data->HigherLayer, "%s%s", path, instance);
	dmuci_get_value_by_section_string(s, inst_alias, &data->HigherAlias);
	dmuci_get_value_by_section_string(s, "LowerLayers", &LowerLayer);

	data->LowerLayer = LowerLayer;
	data->LowerAlias = get_lower_alias_value(data->LowerLayer);

	inst = handle_instance_without_section(dmctx, parent_node, ++(*curr_inst));

	curr_data.additional_data = (void *)data;

	if (DM_LINK_INST_OBJ(dmctx, parent_node, &curr_data, inst) == DM_STOP)
		return -1;

end:
	return 0;
}

/*************************************************************
* ENTRY METHOD
**************************************************************/
static int browseInterfaceStackInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct interfacestack_data curr_interfacestack_data = {0};
	struct uci_section *s = NULL;
	int idx = 0;

	/* Higher Layer is Device.IP.Interface.{i}. */
	uci_path_foreach_sections(bbfdm, "IP", "Interface", s) {

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				"Device.IP.Interface.", "__instance__", "Alias", &idx))
			goto end;
	}

	/* Higher Layer is Device.GRE.Tunnel.{i}.Interface.{i}. */
	uci_path_foreach_sections(bbfdm, "GRE", "Interface", s) {
		char *tunnel_inst = NULL;
		char path[128] = {0};

		dmuci_get_value_by_section_string(s, "Tunnel", &tunnel_inst);

		snprintf(path, sizeof(path), "Device.GRE.Tunnel.%s.Interface.", tunnel_inst);

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				path, "__instance__", "Alias", &idx))
			goto end;
	}

	/* Higher Layer is Device.PPP.Interface.{i}. */
	uci_path_foreach_sections(bbfdm, "PPP", "Interface", s) {

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				"Device.PPP.Interface.", "__instance__", "Alias", &idx))
			goto end;
	}

	/* Higher Layer is Device.Ethernet.X_IOWRT_EU_MACVLAN.{i}. */
	uci_path_foreach_sections(bbfdm, "Ethernet", BBF_VENDOR_PREFIX"MACVLAN", s) {

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				"Device.Ethernet."BBF_VENDOR_PREFIX"MACVLAN.", "__instance__", "Alias", &idx))
			goto end;
	}

	/* Higher Layer is Device.Ethernet.VLANTermination.{i}. */
	uci_path_foreach_sections(bbfdm, "Ethernet", "VLANTermination", s) {

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				"Device.Ethernet.VLANTermination.", "__instance__", "Alias", &idx))
			goto end;
	}

	/* Higher Layer is Device.Ethernet.Link.{i}. */
	uci_path_foreach_sections(bbfdm, "Ethernet", "Link", s) {

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				"Device.Ethernet.Link.", "__instance__", "Alias", &idx))
			goto end;
	}

	/* Higher Layer is Device.Bridging.Bridge.{i}.Port.{i}. */
	if (file_exists(BBFDM_DMMAP_BRIDGING_DIR_PATH)) {
		/* New dmmap method: use Bridging/Bridge and Bridging/Port sections */
		/* Emit stack entries for each Bridge's management port toward other non-management ports,
		 * then emit standard HigherLayer->LowerLayer entries per Port using LowerLayers. */
		struct uci_section *bridge_s = NULL;
		uci_path_foreach_sections(bbfdm, "Bridging", "Bridge", bridge_s) {
			char *br_inst = NULL;
			char *mg_port_inst = NULL;
			char *mg_port_alias = NULL;
			struct uci_section *port_s = NULL;

			dmuci_get_value_by_section_string(bridge_s, "__instance__", &br_inst);
			if (DM_STRLEN(br_inst) == 0)
				continue;

			/* Find management port for this bridge: Name == br name? In new dmmap, the
			 * management/aggregation port is the one whose Name equals bridge Name, i.e.,
			 * typically Upper with LowerLayers listing other bridge ports. */
			{
				char *bridge_name = NULL;
				dmuci_get_value_by_section_string(bridge_s, "Name", &bridge_name);
				uci_path_foreach_sections(bbfdm, "Bridging", "Port", port_s) {
					char *sec_name = port_s->e.name;
					char *port_inst = NULL, *alias = NULL, *name = NULL;
					char prefix[32] = {0};

					dmuci_get_value_by_section_string(port_s, "__instance__", &port_inst);
					dmuci_get_value_by_section_string(port_s, "Alias", &alias);
					dmuci_get_value_by_section_string(port_s, "Name", &name);

					snprintf(prefix, sizeof(prefix), "Bridge_%sPort_", br_inst);
					if (DM_STRLEN(sec_name) == 0 || DM_STRLEN(port_inst) == 0 || DM_STRNCMP(sec_name, prefix, DM_STRLEN(prefix)) != 0)
						continue;

					if (DM_STRCMP(name, bridge_name) == 0) {
						mg_port_inst = port_inst;
						mg_port_alias = alias;
						break;
					}
				}
			}

			/* If a management/aggregation port is found, create pair entries to each other port of same bridge */
			if (DM_STRLEN(mg_port_inst)) {
				struct uci_section *ps = NULL;
				uci_path_foreach_sections(bbfdm, "Bridging", "Port", ps) {
					char *sec_name = ps->e.name;
					char *port_inst = NULL, *alias = NULL;
					char prefix[32] = {0};
					struct dm_data curr_data = {0};

					dmuci_get_value_by_section_string(ps, "__instance__", &port_inst);
					dmuci_get_value_by_section_string(ps, "Alias", &alias);

					snprintf(prefix, sizeof(prefix), "Bridge_%sPort_", br_inst);
					if (DM_STRLEN(sec_name) == 0 || DM_STRLEN(port_inst) == 0 || DM_STRNCMP(sec_name, prefix, DM_STRLEN(prefix)) != 0)
						continue;

					if (DM_STRCMP(port_inst, mg_port_inst) == 0)
						continue;

					dmasprintf(&curr_interfacestack_data.HigherLayer, "Device.Bridging.Bridge.%s.Port.%s", br_inst, mg_port_inst);
					curr_interfacestack_data.HigherAlias = mg_port_alias;
					dmasprintf(&curr_interfacestack_data.LowerLayer, "Device.Bridging.Bridge.%s.Port.%s", br_inst, port_inst);
					curr_interfacestack_data.LowerAlias = alias;

					char *inst = handle_instance_without_section(dmctx, parent_node, ++idx);
					curr_data.additional_data = (void *)&curr_interfacestack_data;
					if (DM_LINK_INST_OBJ(dmctx, parent_node, &curr_data, inst) == DM_STOP)
						goto end;
				}
			}

			/* Now emit standard entries per-Port using LowerLayers */
			uci_path_foreach_sections(bbfdm, "Bridging", "Port", port_s) {
				char *sec_name = port_s->e.name;
				char *port_inst = NULL;
				char prefix[32] = {0};

				dmuci_get_value_by_section_string(port_s, "__instance__", &port_inst);

				snprintf(prefix, sizeof(prefix), "Bridge_%sPort_", br_inst);
				if (DM_STRLEN(sec_name) == 0 || DM_STRLEN(port_inst) == 0 || DM_STRNCMP(sec_name, prefix, DM_STRLEN(prefix)) != 0)
					continue;

				char path[128] = {0};
				snprintf(path, sizeof(path), "Device.Bridging.Bridge.%s.Port.", br_inst);

				/* Skip management port here; its LowerLayers may be a list. */
				if (mg_port_inst && DM_STRCMP(port_inst, mg_port_inst) == 0)
					continue;

				if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, port_s,
						path, "__instance__", "Alias", &idx))
					goto end;
			}
		}
	} else {
		/* Old dmmap method: use dmmap_bridge and dmmap_bridge_port */
		uci_path_foreach_sections(bbfdm, "dmmap_bridge", "device", s) {
			struct uci_section *port_s = NULL;
			char *br_instance = NULL;
			char *mg_port_instnace = NULL;
			char *mg_port_alias = NULL;
			char *inst = NULL;

			dmuci_get_value_by_section_string(s, "bridge_instance", &br_instance);
			if (DM_STRLEN(br_instance) == 0)
				continue;

			struct uci_section *mg_port_s = get_bridge_management_port_section(br_instance);
			if (!mg_port_s)
				continue;

			dmuci_get_value_by_section_string(mg_port_s, "bridge_port_instance", &mg_port_instnace);
			dmuci_get_value_by_section_string(mg_port_s, "bridge_port_alias", &mg_port_alias);

			uci_path_foreach_option_eq(bbfdm, "dmmap_bridge_port", "bridge_port", "br_inst", br_instance, port_s) {
				struct dm_data curr_data = {0};
				char *management = NULL;
				char *instance_value = NULL;
				char *alias_value = NULL;
				char *config = NULL;
				char path[128] = {0};

				dmuci_get_value_by_section_string(port_s, "management", &management);
				if (DM_STRCMP(management, "1") == 0)
					continue;

				dmuci_get_value_by_section_string(port_s, "bridge_port_instance", &instance_value);
				dmuci_get_value_by_section_string(port_s, "bridge_port_alias", &alias_value);

				dmasprintf(&curr_interfacestack_data.HigherLayer, "Device.Bridging.Bridge.%s.Port.%s", br_instance, mg_port_instnace);
				curr_interfacestack_data.HigherAlias = mg_port_alias;
				dmasprintf(&curr_interfacestack_data.LowerLayer, "Device.Bridging.Bridge.%s.Port.%s", br_instance, instance_value);
				curr_interfacestack_data.LowerAlias = alias_value;

				inst = handle_instance_without_section(dmctx, parent_node, ++idx);
				curr_data.additional_data = (void *)&curr_interfacestack_data;
				if (DM_LINK_INST_OBJ(dmctx, parent_node, &curr_data, inst) == DM_STOP)
					goto end;

				/* Higher Layer is Device.Bridging.Bridge.{i}.Port.{i}. */
				snprintf(path, sizeof(path), "Device.Bridging.Bridge.%s.Port.", br_instance);

				if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, port_s,
						path, "bridge_port_instance", "bridge_port_alias", &idx))
					goto end;

				/* Higher Layer is Device.WiFi.SSID.{i}. */
				dmuci_get_value_by_section_string(port_s, "config", &config);
				if (DM_STRCMP(config, "wireless") == 0) {
					struct uci_section *wl_s = NULL;
					char *port = NULL;

					dmuci_get_value_by_section_string(port_s, "port", &port);
					wl_s = get_dup_section_in_config_opt("wireless", "wifi-iface", "ifname", port);
					if (wl_s != NULL) {
						char *ssid = NULL, *device = NULL;

						dmuci_get_value_by_section_string(wl_s, "device", &device);
						dmuci_get_value_by_section_string(wl_s, "ssid", &ssid);

						wl_s = get_section_in_dmmap_with_options_eq("WiFi", "SSID", "device", device, "ssid", ssid);

						if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, wl_s,
								"Device.WiFi.SSID.", "__instance__", "Alias", &idx))
							goto end;
					}
				}
			}
		}
	}

	/* Higher Layer is Device.WiFi.SSID.{i}. */
	uci_path_foreach_sections(bbfdm, "WiFi", "SSID", s) {

		if (create_interface_stack_instance(dmctx, parent_node, &curr_interfacestack_data, s,
				"Device.WiFi.SSID.", "__instance__", "Alias", &idx))
			goto end;
	}

end:
	return 0;
}

/*************************************************************
* GET & SET PARAM
**************************************************************/
static int get_Device_InterfaceStackNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseInterfaceStackInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_InterfaceStack_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmasprintf(value, "cpe-%s", instance);
	return 0;
}

static int set_InterfaceStack_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return 0;
}

static int get_InterfaceStack_HigherLayer(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = ((struct interfacestack_data *)((struct dm_data *)data)->additional_data)->HigherLayer;
	return 0;
}

static int get_InterfaceStack_LowerLayer(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = ((struct interfacestack_data *)((struct dm_data *)data)->additional_data)->LowerLayer;
	return 0;
}

static int get_InterfaceStack_HigherAlias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = ((struct interfacestack_data *)((struct dm_data *)data)->additional_data)->HigherAlias;
	return 0;
}

static int get_InterfaceStack_LowerAlias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = ((struct interfacestack_data *)((struct dm_data *)data)->additional_data)->LowerAlias;
	return 0;
}

/**********************************************************************************************************************************
*                                            OBJ & PARAM DEFINITION
***********************************************************************************************************************************/
/* *** Device. *** */
DMOBJ tDeviceInterfaceStackObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"InterfaceStack", &DMREAD, NULL, NULL, "file:/etc/config/network", browseInterfaceStackInst, NULL, NULL, NULL, tInterfaceStackParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMLEAF tDeviceInterfaceStackParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"InterfaceStackNumberOfEntries", &DMREAD, DMT_UNINT, get_Device_InterfaceStackNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.InterfaceStack.{i}. *** */
DMLEAF tInterfaceStackParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_InterfaceStack_Alias, set_InterfaceStack_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"HigherLayer", &DMREAD, DMT_STRING, get_InterfaceStack_HigherLayer, NULL, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"LowerLayer", &DMREAD, DMT_STRING, get_InterfaceStack_LowerLayer, NULL, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"HigherAlias", &DMREAD, DMT_STRING, get_InterfaceStack_HigherAlias, NULL, BBFDM_BOTH},
{"LowerAlias", &DMREAD, DMT_STRING, get_InterfaceStack_LowerAlias, NULL, BBFDM_BOTH},
{0}
};
