
#include "backhaul_topology.h"
#include "backhaul_topology_dbg.h"
#include "debug.h"
#include "utils.h"

#include <easy/easy.h>
#include <1905_tlvs.h>

#include <libubox/list.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/** Used to store all discovered easy mesh devices and model tree topology. */
struct bh_topology_data {
	struct list_head dev_list; /* list of struct bh_topology_dev */
	uint32_t bh_topo_devs_number;
	bool bh_topology_valid;
};

static struct bh_topology_data priv_bh_topo_data;


static bool is_wifi(uint16_t media_type)
{
	return (media_type >> 8) == 1;
}

void init_bh_topology(void)
{
	INIT_LIST_HEAD(&priv_bh_topo_data.dev_list);
	priv_bh_topo_data.bh_topo_devs_number = 0;
	priv_bh_topo_data.bh_topology_valid = false;
}

void free_bh_topology(void)
{
	remove_all_bh_topology_devices();
}

bool is_bh_topology_valid(void)
{
	return priv_bh_topo_data.bh_topology_valid;
}

uint32_t bh_topology_devs_number(void)
{
	return priv_bh_topo_data.bh_topo_devs_number;
}

struct bh_topology_dev *find_bh_topology_device(const uint8_t *al_macaddr)
{
	struct bh_topology_dev *bh_topo_dev = NULL;

	list_for_each_entry(bh_topo_dev, &priv_bh_topo_data.dev_list, list_element) {
		if (hwaddr_equal(bh_topo_dev->al_macaddr, al_macaddr))
			return bh_topo_dev;
	}

	return NULL;
}

struct bh_topology_dev *add_bh_topology_device(const uint8_t *al_macaddr)
{
	struct bh_topology_dev *bh_topo_dev =
		calloc(1, sizeof(struct bh_topology_dev));

	if (!bh_topo_dev)
		return NULL;

	memcpy(bh_topo_dev->al_macaddr, al_macaddr, 6);
	bh_topo_dev->bh_info.level_in_tree = UNKNOWN_TREE_LEVEL;

	list_add(&bh_topo_dev->list_element, &priv_bh_topo_data.dev_list);

	++priv_bh_topo_data.bh_topo_devs_number;
	priv_bh_topo_data.bh_topology_valid = false;

	return bh_topo_dev;
}

void remove_bh_topology_device(const uint8_t *al_macaddr)
{
	struct bh_topology_dev *bh_topo_dev = NULL, *bh_topo_dev_tmp;

	list_for_each_entry_safe(bh_topo_dev, bh_topo_dev_tmp,
				 &priv_bh_topo_data.dev_list, list_element) {

		if (hwaddr_equal(bh_topo_dev->al_macaddr, al_macaddr)) {
			list_del(&bh_topo_dev->list_element);
			free(bh_topo_dev);

			--priv_bh_topo_data.bh_topo_devs_number;
			priv_bh_topo_data.bh_topology_valid = false;
		}
	}
}

void remove_all_bh_topology_devices(void)
{
	/* cppcheck-suppress uninitvar */
	list_flush(&priv_bh_topo_data.dev_list, struct bh_topology_dev, list_element);

	priv_bh_topo_data.bh_topo_devs_number = 0;
	priv_bh_topo_data.bh_topology_valid = false;
}

bool has_interface_info_changed(const struct tlv_device_info *tlv_dev_info,
				const struct bh_topology_dev *bh_topo_dev)
{
	const uint8_t *tlv_iface_p = (uint8_t *)tlv_dev_info->interface;
	size_t tlv_iface_offset;
	int i;

	if (bh_topo_dev->number_of_interfaces != tlv_dev_info->num_interface)
		return true;

	tlv_iface_offset = 0;
	for (i = 0; i < bh_topo_dev->number_of_interfaces; ++i) {
		const struct local_interface *interface =
			(const struct local_interface *)(tlv_iface_p + tlv_iface_offset);

		if (!hwaddr_equal(bh_topo_dev->ifaces[i].macaddr, interface->macaddr))
			return true;

		tlv_iface_offset += sizeof(struct local_interface) +
				    interface->sizeof_mediainfo;
	}

	return false;
}

void copy_interface_info_from_tlv(const struct tlv_device_info *tlv_dev_info,
				  struct bh_topology_dev *bh_topo_dev)
{
	const uint8_t *tlv_iface_p = (uint8_t *)tlv_dev_info->interface;
	size_t tlv_iface_offset;
	int i;

	priv_bh_topo_data.bh_topology_valid = false;

	/* Clear device old iface data, including neighbors info */
	memset(bh_topo_dev->ifaces, 0, sizeof(bh_topo_dev->ifaces));

	bh_topo_dev->num_of_ifaces_with_neighbors = 0;
	bh_topo_dev->number_of_interfaces = tlv_dev_info->num_interface;

	if (tlv_dev_info->num_interface > IFACE_MAX_NUM) {
		err("%s: Currently max %d interfaces supported!\n", __func__, IFACE_MAX_NUM);
		bh_topo_dev->number_of_interfaces = IFACE_MAX_NUM;
	}

	tlv_iface_offset = 0;
	for (i = 0; i < bh_topo_dev->number_of_interfaces; ++i) {
		const struct local_interface *interface =
			(const struct local_interface *)(tlv_iface_p + tlv_iface_offset);

		memcpy(bh_topo_dev->ifaces[i].macaddr, interface->macaddr, 6);
		bh_topo_dev->ifaces[i].media_type =
			BUF_GET_BE16(interface->mediatype);

		tlv_iface_offset += sizeof(struct local_interface) +
				    interface->sizeof_mediainfo;
	}
}

static bool are_neighbor_lists_the_same(const struct local_iface *iface,
					const struct tlv_1905neighbor *tlv_1905neighbor,
					uint16_t tlv_length)
{
	const uint8_t tlv_neighbors_num =
		(tlv_length - sizeof(tlv_1905neighbor->local_macaddr)) /
		sizeof(struct i1905_neighbor);
	uint8_t i, j;

	if (iface->number_of_neighbors != tlv_neighbors_num)
		return false;

	for (i = 0; i < tlv_neighbors_num; ++i) {
		bool found = false;

		for (j = 0; j < iface->number_of_neighbors; ++j) {
			if (hwaddr_equal(tlv_1905neighbor->nbr[i].aladdr,
					 &iface->neighbors_al_macs[j][0])) {
				found = true;
				break;
			}
		}

		if (!found)
			return false;
	}

	return true;
}

bool has_neighbor_info_changed(const struct tlv_1905neighbor **neighbor_tlvs,
			       const uint16_t *tlv_lengths,
			       uint8_t tlv_number,
			       const struct bh_topology_dev *bh_topo_dev)
{
	uint8_t i;

	/* Every TLV provides neighbors for single local interface. */
	if (bh_topo_dev->num_of_ifaces_with_neighbors != tlv_number)
		return true;

	for (i = 0; i < tlv_number; ++i) {
		const struct tlv_1905neighbor *neighbor_tlv = neighbor_tlvs[i];
		uint8_t j;
		bool found = false;

		for (j = 0; j < bh_topo_dev->number_of_interfaces; ++j) {
			bool same_mac = hwaddr_equal(bh_topo_dev->ifaces[j].macaddr,
					 neighbor_tlv->local_macaddr);

			if (same_mac && are_neighbor_lists_the_same(
						&bh_topo_dev->ifaces[j],
						neighbor_tlv, tlv_lengths[i])) {

				found = true;
				break;
			}
		}

		if (!found)
			return true;
	}

	return false;

}

void copy_neighbor_info_from_tlvs(const struct tlv_1905neighbor **neighbor_tlvs,
				  const uint16_t *tlv_lengths,
				  uint8_t tlv_number,
				  struct bh_topology_dev *bh_topo_dev)
{
	uint8_t i;

	priv_bh_topo_data.bh_topology_valid = false;
	bh_topo_dev->num_of_ifaces_with_neighbors = tlv_number;

	/* Clear device old/previous neighbors info */
	for (i = 0; i < bh_topo_dev->number_of_interfaces; ++i) {
		if (bh_topo_dev->ifaces[i].number_of_neighbors != 0) {
			bh_topo_dev->ifaces[i].number_of_neighbors = 0;
			memset(bh_topo_dev->ifaces[i].neighbors_al_macs, 0,
			       sizeof(bh_topo_dev->ifaces[i].neighbors_al_macs));
		}

	}

	for (i = 0; i < tlv_number; ++i) {
		const struct tlv_1905neighbor *neighbor_tlv = neighbor_tlvs[i];
		const uint8_t neighbors_num =
			(tlv_lengths[i] - sizeof(neighbor_tlv->local_macaddr)) /
			sizeof(struct i1905_neighbor);
		struct local_iface *local_iface = NULL;
		uint8_t j;

		for (j = 0; j < bh_topo_dev->number_of_interfaces; ++j) {
			/*
			 * Because different local physical interfaces can use the same MAC
			 * address, find a one with matching MAC and no neighbors added yet.
			 */
			if (hwaddr_equal(bh_topo_dev->ifaces[j].macaddr,
						neighbor_tlv->local_macaddr) &&
				bh_topo_dev->ifaces[j].number_of_neighbors == 0) {

				local_iface = &bh_topo_dev->ifaces[j];

				if (bh_topo_dev->ifaces[j].media_type != 0xFFFF)
					break;
			}
		}

		if (local_iface) {
			uint8_t k;

			dbg("%s: adding neighbors for local_iface[%u].macaddr: " MACFMT "\n",
				__func__, j, MAC2STR(local_iface->macaddr));

			local_iface->number_of_neighbors = neighbors_num;

			for (k = 0; k < neighbors_num && k < NEIGHBORS_MAX_NUM; ++k) {
				dbg("%s:   neighbor[%u].aladdr: " MACFMT "\n",
					__func__, k, MAC2STR(neighbor_tlv->nbr[k].aladdr));

				memcpy(&local_iface->neighbors_al_macs[k][0],
					neighbor_tlv->nbr[k].aladdr, 6);
			}

		} else {
			err("%s: no iface found, adding neighbors failed for local_macaddr: " MACFMT "\n",
				__func__, MAC2STR(neighbor_tlv->local_macaddr));
		}
	}

}

void set_bh_toplogy_response_timestamp(struct bh_topology_dev *bh_topo_dev)
{
	timestamp_update(&bh_topo_dev->last_topo_response);
}

static void clear_backhaul_tree_relations(struct list_head *bh_topo_dev_list)
{
	struct bh_topology_dev *bh_topo_dev = NULL;

	list_for_each_entry(bh_topo_dev, bh_topo_dev_list, list_element) {
		bh_topo_dev->bh_info =
			(struct backhaul_info){ UNKNOWN_TREE_LEVEL, 0, NULL, NULL, NULL };
	}
}

static const struct local_iface *
find_neighbor_local_iface(const struct bh_topology_dev *next_level_neighbor,
			  const uint8_t *parent_al_macaddr)
{
	int i, j;

	for (i = 0; i < next_level_neighbor->number_of_interfaces; ++i) {
		const struct local_iface *iface = &next_level_neighbor->ifaces[i];

		for (j = 0; j < iface->number_of_neighbors; ++j) {
			if (hwaddr_equal(&iface->neighbors_al_macs[j][0], parent_al_macaddr))
				return iface;
		}
	}

	return NULL;
}

static bool set_device_child_neighbors(struct bh_topology_dev *parent_device,
				       struct list_head *bh_topo_dev_list)
{
	int i, j;
	const int8_t child_level = parent_device->bh_info.level_in_tree + 1;
	bool has_child_neighbor = false;

	for (i = 0; i < parent_device->number_of_interfaces; ++i) {
		const struct local_iface *parent_iface = &parent_device->ifaces[i];

		for (j = 0; j < parent_iface->number_of_neighbors; ++j) {

			struct bh_topology_dev *child_neighbor_dev =
				find_bh_topology_device(&parent_iface->neighbors_al_macs[j][0]);

			if (child_neighbor_dev &&
			    child_neighbor_dev->bh_info.level_in_tree == UNKNOWN_TREE_LEVEL) {
				const struct local_iface *child_iface;

				child_neighbor_dev->bh_info.parent_in_tree = parent_device;
				child_neighbor_dev->bh_info.parent_iface = parent_iface;
				child_neighbor_dev->bh_info.level_in_tree = child_level;
				child_neighbor_dev->bh_info.wifi_hops_from_root =
					parent_device->bh_info.wifi_hops_from_root;
				if (is_wifi(parent_iface->media_type))
					child_neighbor_dev->bh_info.wifi_hops_from_root++;

				child_iface = find_neighbor_local_iface(child_neighbor_dev,
									parent_device->al_macaddr);

				if (!child_iface) {
					warn("BH:%s: child doens't know about parent neighbor.\n", __func__);
				} else {
					memcpy(&parent_device->ifaces[i].neighbors_link_macs[j][0],
							child_iface->macaddr, 6);
				}

				child_neighbor_dev->bh_info.own_iface = child_iface;

				if (child_iface && (child_iface->media_type != parent_iface->media_type)) {
					warn("BH:%s: child and parent iface media type differs, parent: %s, child: %s\n",
					     i1905_media_type_to_str(parent_iface->media_type),
					     i1905_media_type_to_str(child_iface->media_type),
					     __func__);
				}

				dbg("BH: %s New parent-child link set:\n", __func__);
				dbg_dump_bh_topo_link(child_neighbor_dev, INDENT_LVL_0);

				has_child_neighbor = true;
			}
		}
	}

	return has_child_neighbor;
}

static bool set_next_level_neighbors(int8_t current_level,
				     struct list_head *bh_topo_dev_list)
{
	struct bh_topology_dev *bh_topo_dev = NULL;
	bool new_level_discovered = false;

	list_for_each_entry(bh_topo_dev, bh_topo_dev_list, list_element) {
		if (bh_topo_dev->bh_info.level_in_tree == current_level) {
			bool has_next_level =
				set_device_child_neighbors(bh_topo_dev, bh_topo_dev_list);

			new_level_discovered = new_level_discovered || has_next_level;
		}
	}

	dbg("BH: %s: current_level: %d, new_level_discovered: %d\n",
	    __func__, current_level, new_level_discovered);

	return new_level_discovered;
}

void build_bh_topology_tree(const uint8_t *ctrl_al_macaddr)
{
	struct bh_topology_dev *ctrl_dev = NULL;
	bool new_level_discovered = false;
	int8_t current_level;

	dbg("BH: %s\n", __func__);

	clear_backhaul_tree_relations(&priv_bh_topo_data.dev_list);

	/* Mark controller as root node in BH tree (level_in_tree = 0) */
	ctrl_dev = find_bh_topology_device(ctrl_al_macaddr);
	if (!ctrl_dev) {
		err("%s: cannot find controller (root node) and build tree.\n",
			__func__);
		return;
	}
	ctrl_dev->bh_info.level_in_tree = 0;
	ctrl_dev->bh_info.wifi_hops_from_root = 0;

	current_level = ctrl_dev->bh_info.level_in_tree;

	do {
		new_level_discovered =
			set_next_level_neighbors(current_level, &priv_bh_topo_data.dev_list);
		++current_level;
	} while (new_level_discovered);

	priv_bh_topo_data.bh_topology_valid = true;

	info("%s: Backhaul topology tree built. BH topo devs num: %d, cntrl: " MACFMT "\n",
		__func__,  priv_bh_topo_data.bh_topo_devs_number, MAC2STR(ctrl_al_macaddr));

	dbg_dump_bh_topo_devs(&priv_bh_topo_data.dev_list, INDENT_LVL_0);
}
