/*
 * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
 */
#include "scan.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <easy/easy.h>
#include <cmdu.h>
#include <easymesh.h>
#include <wifidefs.h>

#include "cntlr.h"
#include "cntlr_map.h"
#include "cntlr_cmdu.h"
#include "config.h"
#include "utils/utils.h"
#include "utils/debug.h"
#include "wifi_dataelements.h"


void cntlr_scan_caps_opclass_dump(struct wifi_radio_scan_capabilities *scan_caps)
{
	int i, j;

	dbg(">>> scan caps: opclass num: %d\n", scan_caps->opclass.num_opclass);
	for (i = 0; i < scan_caps->opclass.num_opclass; i++) {
		dbg("opclass: %u\n", scan_caps->opclass.opclass[i].id);
		for (j = 0; j < scan_caps->opclass.opclass[i].num_channel; j++) {
			dbg("\tchan %u\n", scan_caps->opclass.opclass[i].channel[j].channel);
		}
	}
	dbg("<<<\n");
}


/* Check Channel Scan Capabilities TLV */
int cntlr_parse_radio_scan_caps(struct controller *c, struct node *n,
		struct tlv *t)
{
	struct tlv_channel_scan_capability *tlv;
	uint8_t *tv_data;
	int i, j, k, offset = 0;

	tlv = (struct tlv_channel_scan_capability *)t->data;
	tv_data = (uint8_t *)tlv;

	offset += sizeof(*tlv); /* num_radio */

	for (i = 0; i < tlv->num_radio; i++) {
		struct netif_radio *nr;
		struct wifi_radio_scan_capabilities caps;
		struct channel_scan_capability_radio *csr =
			(struct channel_scan_capability_radio *)&tv_data[offset];

		caps.boot_only = !!(csr->cap & SCAN_CAP_ON_BOOT_ONLY);
		caps.impact = !!(csr->cap & SCAN_CAP_IMPACT);
		caps.interval = csr->min_scan_interval;

		caps.opclass.num_opclass = csr->num_opclass;

		offset += sizeof(*csr); /* radio, cap, min_scan_interval, num_opclass */

		for (j = 0; j < csr->num_opclass; j++) {
			struct channel_scan_capability_opclass *opc =
				(struct channel_scan_capability_opclass *)&tv_data[offset];

			caps.opclass.opclass[j].id = opc->classid;
			caps.opclass.opclass[j].num_channel = opc->num_channel;

			offset += sizeof(*opc); /* classid & num_channel */

			for (k = 0; k < opc->num_channel; k++) {
				caps.opclass.opclass[j].channel[k].channel = opc->channel[k];
			}

			offset += opc->num_channel;
		}

		/* scan capabilities debug dump */
		cntlr_scan_caps_opclass_dump(&caps);

		nr = cntlr_find_radio(c, csr->radio);
		if (!nr)
			/* no such radio - try next */
			continue;

		/* updt caps: no pointers - let the compiler optimize */
		nr->radio_el->scan_caps = caps;


	}


	return 0;
}


struct wifi_scanres_element *find_scanres_el(struct controller *c,
		uint8_t *radio, char *timestamp)
{
	struct netif_radio *r = NULL;
	struct wifi_scanres_element *b = NULL;

	r = cntlr_find_radio(c, radio);
	if (!r)
		return NULL;

	if (list_empty(&r->radio_el->scanlist))
		return NULL;

	list_for_each_entry(b, &r->radio_el->scanlist, list) {
		if (!strncmp(timestamp, b->tsp, strlen(timestamp)))
			return b;
	}

	return NULL;
}

struct wifi_scanres_element *get_scanlist_element(struct controller *c,
		uint8_t *radio, char *timestamp)
{
	trace("%s: --->\n", __func__);

	struct wifi_scanres_element *b = NULL;
	struct netif_radio *r;

	/* Reuse element if data from same radio and time */
	b = find_scanres_el(c, radio, timestamp);
	if (!b) {
		r = cntlr_find_radio(c, radio);
		if (!r)
			return NULL;

		b = calloc(1, sizeof(*b));
		if (!b)
			return NULL;

		/* Keep only SCANRES_MAX_NUM results per radio */
		if (r->radio_el->num_scanresult >= SCANRES_MAX_NUM) {
			struct wifi_scanres_element *el = NULL;

			/* remove oldest (fifo queue) element */
			el = list_first_entry(&r->radio_el->scanlist, struct wifi_scanres_element, list);
			if (el) {
				if (!cntlr_radio_clean_scanlist_el(el))
					r->radio_el->num_scanresult--;
			}
		}

		/* add new element (to fifo queue) */
		list_add_tail(&b->list, &r->radio_el->scanlist);
		r->radio_el->num_scanresult++;

		strncpy(b->tsp, timestamp, sizeof(b->tsp) - 1);

		/* Initialize opclass list for the measurement */
		INIT_LIST_HEAD(&b->opclass_scanlist);
	}

	return b;
}

int add_scanres_element(struct controller *c,
		struct tlv_channel_scan_result *tlv, char *timestamp)
{
	trace("%s: --->\n", __func__);

	struct wifi_scanres_element *el = NULL;
	struct wifi_scanres_opclass_element *op = NULL, *otmp = NULL;
	struct wifi_scanres_channel_element *ch = NULL, *ctmp = NULL;
	uint8_t *tv_data = NULL;
	int offset = 0;
	int i;
	int new_neighbors = 0;

	/* Reuse old or add new element to the list */
	el = get_scanlist_element(c, tlv->radio, timestamp);
	if (!el)
		return -1; /* error condition */

	list_for_each_entry(otmp, &el->opclass_scanlist, list) {
		if (otmp->opclass == tlv->opclass) {
			op = otmp;
			break;
		}
	}

	if (!op) {
		op = calloc(1, sizeof(*op));
		if (!op)
			goto error; /* error condition */

		/* add opclass element to the list */
		list_add(&op->list, &el->opclass_scanlist);
		el->num_opclass_scanned++;

		op->opclass = tlv->opclass;

		/* Initialize channel list for this measurement */
		INIT_LIST_HEAD(&op->channel_scanlist);
	}

	list_for_each_entry(ctmp, &op->channel_scanlist, list) {
		if (ctmp->channel == tlv->channel) {
			dbg("%s: channel %d already on the list\n", __func__, tlv->channel);
			ch = ctmp;
			break;
		}
	}

	if (!ch) {
		ch = calloc(1, sizeof(*ch));
		if (!ch)
			goto error; /* error condition */

		/* add channel element to the list */
		list_add(&ch->list, &op->channel_scanlist);
		op->num_channels_scanned++;

		/* channel */
		ch->channel = tlv->channel;

		/* Initialize nbrlist for this channel of current measurement */
		ch->num_neighbors = 0;
		INIT_LIST_HEAD(&ch->nbrlist);
	}

	/* tsp */
	// TODO: compare tsp before squashing neighbors
	memset(ch->tsp, 0, sizeof(ch->tsp)); /* null term */
	memcpy(ch->tsp, tlv->detail[0].tsp.timestamp,
		sizeof(ch->tsp) - 1 < tlv->detail[0].tsp.len ?
		sizeof(ch->tsp) - 1 : tlv->detail[0].tsp.len);

	/* don't use struct detail anylonger due to variable tsp len */
	tv_data = (uint8_t *) &tlv->detail[0];
	offset += sizeof(tlv->detail[0].tsp) + tlv->detail[0].tsp.len;

	ch->utilization = tv_data[offset];
	offset++; /* uint8_t utilization; */
	ch->anpi = tv_data[offset];
	offset++; /* uint8_t noise; */
	new_neighbors = BUF_GET_BE16(tv_data[offset]);
	ch->num_neighbors += new_neighbors;
	offset += 2; /* uint16_t num_neighbor; */

	dbg("%s: channel %d, tsp: %s, util: %d, noise: %d, num_nbr: %d\n",
	    __func__, ch->channel, ch->tsp, ch->utilization, ch->anpi, ch->num_neighbors);

	/* Update nbrlist for this channel of current measurement */
	for (i = 0; i < new_neighbors; i++) {
		struct wifi_scanres_neighbor_element *nbr = NULL;
		uint8_t len = 0, ssidlen;
		uint8_t info = 0x00;
		uint8_t bw_len;
		char buf[6] = {0};

		nbr = calloc(1, sizeof(*nbr));
		if (!nbr)
			goto error; /* error condition */

		/* add nbr element to the list */
		list_add(&nbr->list, &ch->nbrlist);

		memcpy(nbr->bssid, &tv_data[offset], 6);
		offset += 6;
		ssidlen = tv_data[offset++];
		len = (ssidlen + 1 > sizeof(nbr->ssid)
				? sizeof(nbr->ssid) : ssidlen + 1);
		snprintf(nbr->ssid, len, "%s", (char *)&tv_data[offset]);
		offset += ssidlen;
		nbr->rssi = (uint8_t)tv_data[offset];
		offset++;
		bw_len = tv_data[offset++];
		if (bw_len > sizeof(buf)) {
			warn("%s: bw_len %d is too long\n", __func__, bw_len);
			goto error;
		} else {
			char *endptr = NULL;

			errno = 0;
			memcpy(buf, &tv_data[offset], bw_len);
			nbr->bw = strtol(buf, &endptr, 10);
			if (errno || *endptr != '\0') {
				err("%s: Error parsing bw value: %s\n",
				    __func__, (char *)&tv_data[offset]);
				goto error;
			}
		}
		offset += bw_len;
		info = tv_data[offset];
		offset++;

		if (info & CH_SCAN_RESULT_BSSLOAD_PRESENT) {
			nbr->utilization = tv_data[offset];
			offset++;
			nbr->num_stations = BUF_GET_BE16(tv_data[offset]);
			offset += 2;
		}
	}

	return 0;

error:
	err("%s: hit error condition\n", __func__);
	cntlr_radio_clean_scanlist_el(el);
	return -1;
}

void cntlr_radio_remove_old_scanres(struct controller *c)
{
	struct node *n = NULL;
	struct netif_radio *r = NULL;
	struct wifi_scanres_element *el = NULL, *tmp;

	list_for_each_entry(n, &c->nodelist, list) {
		list_for_each_entry(r, &n->radiolist, list) {
			list_for_each_entry_safe(el, tmp, &r->radio_el->scanlist, list) {
				struct timespec ts;

				ts = timestamp_to_timespec(el->tsp, true);
				if (timestamp_expired(&ts, SCANRES_MAX_AGE * 1000)) {
					cntlr_radio_clean_scanlist_el(el);
					r->radio_el->num_scanresult--;
				}
			}
		}
	}
}

int cntlr_radio_update_scanlist(void *cntlr, char *timestamp,
		struct tlv **tv_scan, int num_result)
{
	struct controller *c = (struct controller *) cntlr;
	int i;

	/* remove outdated entries from all radio scanlists */
	cntlr_radio_remove_old_scanres(c);

	/* Go trhough all results */
	for (i = 0; i < num_result; i++) {
		struct tlv_channel_scan_result *tlv =
				(struct tlv_channel_scan_result *)tv_scan[i]->data;

		dbg("%s: radio " MACFMT " scan status: %d, opclass/channel: %d/%d\n",
		    __func__,  MAC2STR(tlv->radio), tlv->status,
		    tlv->opclass, tlv->channel);

		/* Skip unsuccesfull scans */
		if (tlv->status != CH_SCAN_STATUS_SUCCESS)
			continue;

		if (add_scanres_element(c, tlv, timestamp))
			return -1;
	}

	return 0;
}


int cntlr_radio_clean_scanlist_el(struct wifi_scanres_element *el)
{
	struct wifi_scanres_opclass_element *op = NULL, *otmp;
	struct wifi_scanres_channel_element *ch = NULL, *ctmp;
	struct wifi_scanres_neighbor_element *nbr = NULL, *ntmp;

	if (!el)
		return -1; /* error condition */

	list_for_each_entry_safe(op, otmp, &el->opclass_scanlist, list) {
		list_for_each_entry_safe(ch, ctmp, &op->channel_scanlist, list) {
			list_for_each_entry_safe(nbr, ntmp, &ch->nbrlist, list) {
				list_del(&nbr->list);
				free(nbr);
			}
			list_del(&ch->list);
			free(ch);
		}
		list_del(&op->list);
		free(op);
	}

	list_del(&el->list);
	free(el);

	return 0;
}

void cntlr_request_scan_cache(struct controller *c, struct node *n, const struct netif_radio *re)
{
	trace("%s---->\n", __func__);

	struct scan_req_data req = {};
	struct cmdu_buff *cmdu;
	uint16_t mid;
	struct netif_radio *r = NULL;

	if (!c || !n)
		return;

	req.is_fresh_scan = false;
	req.num_radio = 0;

	list_for_each_entry(r, &n->radiolist, list) {
		if (re && r != re)
			continue;

		memcpy(req.radios[req.num_radio].radio_mac, r->radio_el->macaddr, 6);
		req.radios[req.num_radio].num_opclass = 0;
		req.num_radio++;
	}

	cmdu = cntlr_gen_channel_scan_request(c, n->almacaddr, &req);
	if (cmdu) {
		mid = send_cmdu(c, cmdu);
		cmdu_free(cmdu);
		dbg("%s: triggered cached scan request (mid=%u) for node " MACFMT "\n",
				__func__, mid, MAC2STR(n->almacaddr));
	}
}
