/*
 * Copyright (C) 2020 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: Anis Ellouze <anis.ellouze@pivasoftware.com>
 *	Author: Amin Ben Ramdhane <amin.benramdhane@pivasoftware.com>
 *
 */

#include "dmlayer.h"

static int depth = 0;

void ip___update_child_interfaces(char *device, const char *option_name, const char *option_value)
{
	struct uci_section *s = NULL;

	if (DM_STRLEN(device) == 0)
		return;

	uci_foreach_option_eq("network", "interface", "device", device, s) {
		dmuci_set_value_by_section(s, option_name, option_value);
	}
}

struct uci_section *ethernet___get_ethernet_interface_section(const char *device_name)
{
	struct uci_section *s = NULL;

	uci_foreach_sections("network", "device", s) {
		char *name = NULL;

		if (!dmuci_is_option_value_empty(s, "type"))
			continue;

		dmuci_get_value_by_section_string(s, "name", &name);

		if (DM_STRCMP(name, device_name) == 0)
			return s;
	}

	return NULL;
}

char *ethernet___get_ethernet_interface_name(const char *device_name)
{
	char *dev_name = dmstrdup(device_name);

	if (!ethernet___get_ethernet_interface_section(dev_name)) {
		struct uci_section *dev_s = NULL;

		dev_s = get_dup_section_in_config_opt("network", "device", "name", dev_name);

		char *has_vid = DM_STRRCHR(dev_name, '.');
		if (has_vid)
			*has_vid = '\0';

		if (dev_s) { // Verify if the device has dual tags
			char *type = NULL;

			dmuci_get_value_by_section_string(dev_s, "type", &type);
			if (DM_STRCMP(type, "8021ad") == 0) {
				char *has_vid_d = DM_STRRCHR(dev_name, '.');
				if (has_vid_d)
					*has_vid_d = '\0';
			}
		}
	}

	return dev_name;
}

static void ip___Update_IP_Interface_Layer(char *path, const char *linker)            
{
	struct uci_section *dmmap_s = NULL;

	uci_path_foreach_option_eq(bbfdm, "IP", "Interface", "LowerLayers", path, dmmap_s) {
		struct uci_section *iface_s = NULL;
		char *sec_name = NULL;
		char *curr_device = NULL;

		dmuci_get_value_by_section_string(dmmap_s, "__section_name__", &sec_name);
		if (!DM_STRLEN(sec_name))
			continue;

		iface_s = get_config_section_from_dmmap_section_name(sec_name);
		if (!iface_s)
			continue;

		dmuci_get_value_by_section_string(iface_s, "device", &curr_device);

		ip___update_child_interfaces(curr_device, "device", DM_STRLEN(linker) ? linker : section_name(iface_s));
	}
}

static void ppp___Update_PPP_Interface_Layer(char *path, const char *linker)          
{
	struct uci_section *dmmap_s = NULL;

	uci_path_foreach_option_eq(bbfdm, "PPP", "Interface", "LowerLayers", path, dmmap_s) {
		struct uci_section *iface_s = NULL;
		char *sec_name = NULL;
		char *instance = NULL;
		char curr_path[128] = {0};
		char proto[8] = {0};

		dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
		if (!DM_STRLEN(instance))
			continue;

		dmuci_get_value_by_section_string(dmmap_s, "__section_name__", &sec_name);
		if (!DM_STRLEN(sec_name))
			continue;

		iface_s = get_config_section_from_dmmap_section_name(sec_name);

		snprintf(proto, sizeof(proto), "ppp%s", (DM_STRLEN(linker)) ? (!DM_LSTRNCMP(linker, "atm", 3) || !DM_LSTRNCMP(linker, "ptm", 3)) ? "oa" : "oe" : "");
		// Update proto option
		dmuci_set_value_by_section(dmmap_s, "proto", proto);
		if (iface_s) dmuci_set_value_by_section(iface_s, "proto", proto);

		// Update device option
		dmuci_set_value_by_section(dmmap_s, "device", linker);
		if (iface_s) dmuci_set_value_by_section(iface_s, "device", linker);

		snprintf(curr_path, sizeof(curr_path), "Device.PPP.Interface.%s", instance);

		// Update IP Interface instance if exists
		ip___Update_IP_Interface_Layer(curr_path, linker);
	}
}

static void ethernet___Update_MAC_VLAN_Layer(char *path, const char *linker)          
{
	struct uci_section *dmmap_s = NULL;

	uci_path_foreach_option_eq(bbfdm, "Ethernet", BBF_VENDOR_PREFIX"MACVLAN", "LowerLayers", path, dmmap_s) {
		struct uci_section *dev_s = NULL;
		char *sec_name = NULL;
		char *instance = NULL;
		char curr_path[128] = {0};
		char name[32] = {0};

		dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
		if (!DM_STRLEN(instance))
			continue;

		dmuci_get_value_by_section_string(dmmap_s, "__section_name__", &sec_name);
		if (!DM_STRLEN(sec_name))
			continue;

		dev_s = get_config_section_from_dmmap_section_name(sec_name);
		if (!dev_s)
			continue;

		if (DM_STRLEN(linker)) {
			char *dev_name = ethernet___get_ethernet_interface_name(linker);

			snprintf(name, sizeof(name), "%s_%s", dev_name, instance);
		}

		dmuci_set_value_by_section(dev_s, "ifname", linker);
		dmuci_set_value_by_section(dev_s, "name", name);

		snprintf(curr_path, sizeof(curr_path), "Device.Ethernet."BBF_VENDOR_PREFIX"MACVLAN.%s", instance);

		// Update PPP Interface instance if exists
		ppp___Update_PPP_Interface_Layer(curr_path, name);

		// Update IP Interface instance if exists
		ip___Update_IP_Interface_Layer(curr_path, name);
	}
}

static void ethernet___Update_VLAN_Termination_Layer(char *path, const char *linker)
{
	struct uci_section *dmmap_s = NULL;

	// since we are calling ourselves in this function, avoid infinite recursion
	if (depth++ > 2)
		return;

	uci_path_foreach_option_eq(bbfdm, "Ethernet", "VLANTermination", "LowerLayers", path, dmmap_s) {
		char *sec_name = NULL;
		char *instance = NULL;
		char curr_path[128] = {0};
		char name[32] = {0};

		dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
		if (!DM_STRLEN(instance))
			continue;

		dmuci_get_value_by_section_string(dmmap_s, "__section_name__", &sec_name);
		if (!DM_STRLEN(sec_name))
			continue;

		if (DM_STRLEN(linker)) {
			char *vid = NULL;

			// Always read vid from dmmap — it is the authoritative store
			// for both bridge and 8021q cases
			dmuci_get_value_by_section_string(dmmap_s, "VLANID", &vid);
			snprintf(name, sizeof(name), "%s%s%s",
				linker,
				DM_STRLEN(vid) ? "." : "",
				DM_STRLEN(vid) ? vid : "");
		}

		// Always update dmmap regardless of bridge or 8021q
		dmuci_set_value_by_section(dmmap_s, "__ifname__", linker);
		dmuci_set_value_by_section(dmmap_s, "Name", name);

		// Only update the network config section if it exists (non-bridge case)
		struct uci_section *dev_s = get_config_section_from_dmmap_section_name(sec_name);
		if (dev_s) {
			dmuci_set_value_by_section(dev_s, "ifname", linker);
			dmuci_set_value_by_section(dev_s, "name", name);
		}

		snprintf(curr_path, sizeof(curr_path), "Device.Ethernet.VLANTermination.%s", instance);

		// Always propagate upward regardless of bridge or 8021q
		ethernet___Update_VLAN_Termination_Layer(curr_path, name);
		ethernet___Update_MAC_VLAN_Layer(curr_path, name);
		ppp___Update_PPP_Interface_Layer(curr_path, name);
		ip___Update_IP_Interface_Layer(curr_path, name);
	}

	depth--;
}

void ethernet___Update_Link_Top_Layers(char *path, const char *linker)
{
	char *p = DM_STRRCHR(path, '.');
	if (p) *p = 0;

	// Update VLAN Termination instance if exists
	ethernet___Update_VLAN_Termination_Layer(path, linker);

	// Update MACVLAN instance if exists
	ethernet___Update_MAC_VLAN_Layer(path, linker);

	// Update PPP Interface instance if exists
	ppp___Update_PPP_Interface_Layer(path, linker);

	// Update IP Interface instance if exists
	ip___Update_IP_Interface_Layer(path, linker);
}  

void ethernet___Update_VLAN_Termination_Top_Layers(char *path, const char *linker)
{
	char *p = DM_STRRCHR(path, '.');
	if (p) *p = 0;

	// Update VLAN Termination instance if exists
	ethernet___Update_VLAN_Termination_Layer(path, linker);

	// Update MACVLAN instance if exists
	ethernet___Update_MAC_VLAN_Layer(path, linker);

	// Update PPP Interface instance if exists
	ppp___Update_PPP_Interface_Layer(path, linker);

	// Update IP Interface instance if exists
	ip___Update_IP_Interface_Layer(path, linker);
}

void ethernet___Update_MAC_VLAN_Top_Layers(char *path, const char *linker)
{
	char *p = DM_STRRCHR(path, '.');
	if (p) *p = 0;

	// Update PPP Interface instance if exists
	ppp___Update_PPP_Interface_Layer(path, linker);

	// Update IP Interface instance if exists
	ip___Update_IP_Interface_Layer(path, linker);
}
