/*
 * wifi_scanresults.c - scan results cache functions
 *
 * Copyright (C) 2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: filip.matusiak@iopsys.eu
 *
 */

#include "wifi_scanresults.h"

#include <easy/timestamp.h>
#include <easy/utils.h>
#include <stdlib.h>
#include <string.h>

#include "utils/debug.h"
#include "wifi.h"
#include "wifi_opclass.h"


struct wifi_scanresults_entry *wifi_scanresults_get_opclass_entry(
		struct wifi_scanresults *results, uint8_t *bssid, uint32_t opclass)
{
	struct wifi_scanresults_entry *entry = NULL;
	int i;

	if (!results)
		return NULL;

	/* lookup entry in results by bssid */
	for (i = 0; i < results->entry_num; i++) {
		entry = &results->entry[i];

		if (opclass != INVALID_OPCLASS && opclass != entry->opclass)
			continue;

		if (!memcmp(bssid, entry->bss.bssid, 6))
			/* entry found */
			return entry;
	}

	return NULL;
}

struct wifi_scanresults_entry *wifi_scanresults_get_entry(
		struct wifi_scanresults *results, uint8_t *bssid)
{
	return wifi_scanresults_get_opclass_entry(results, bssid, INVALID_OPCLASS);
}

static int _wifi_scanresults_update(struct wifi_scanresults *results,
		struct wifi_radio_opclass *opclass, struct wifi_bss *bss)
{
	struct wifi_scanresults_entry *entry;
	uint8_t classid_bw20;

	if (!results)
		return -1;

	if (!bss)
		return -1;

	entry = wifi_scanresults_get_entry(results, bss->bssid);

	if (!entry) {
		if (results->entry_num < SCANRESULTS_MAX_NUM) {
			/* add new entry at the end */
			entry = &results->entry[results->entry_num];
			results->entry_num++;
		} else {
			agnt_dbg(LOG_SCAN, "%s: error getting entry for bssid " MACFMT "\n",
			    __func__, MAC2STR(bss->bssid));
			return -1;
		}
	}

	timestamp_update(&entry->tsp);
	entry->expired = false;
	/* copy */
	memcpy(&entry->bss, bss, sizeof(struct wifi_bss));
	/* always calculate opclass */
	entry->opclass = wifi_opclass_find_id_from_channel(opclass,
					     bss->channel,
					     wifi_bw_to_bw(bss->curr_bw));

	classid_bw20 = wifi_opclass_find_id_from_channel(opclass, bss->channel, 20);

	if (classid_bw20 == OPCLASS_124) {
		agnt_dbg(LOG_SCAN, "%s: duplicating entry for class id = %d\n", __func__, OPCLASS_124);
		entry = wifi_scanresults_get_opclass_entry(results, bss->bssid, OPCLASS_125);
		if (!entry) {
			if (results->entry_num < SCANRESULTS_MAX_NUM) {
				/* add new entry at the end */
				agnt_dbg(LOG_SCAN, "%s: adding opclass entry for bssid " MACFMT "\n",
					__func__, MAC2STR(bss->bssid));
				entry = &results->entry[results->entry_num];
				results->entry_num++;
			} else {
				agnt_dbg(LOG_SCAN, "%s: error getting entry for bssid " MACFMT "\n",
					__func__, MAC2STR(bss->bssid));
				return -1;
			}
		}
		timestamp_update(&entry->tsp);
		entry->expired = false;
		/* copy */
		memcpy(&entry->bss, bss, sizeof(struct wifi_bss));
		entry->opclass = OPCLASS_125;
	}

	return 0;
}

int wifi_scanresults_add(struct wifi_scanresults *results,
		struct wifi_radio_opclass *opclass, struct wifi_bss *bsss,
		int bss_num)
{
	agnt_trace(LOG_SCAN, "%s: --->\n", __func__);

	int i;

	for (i = 0; i < bss_num; i++)
		_wifi_scanresults_update(results, opclass, &bsss[i]);

	return 0;
}

/* Check for outdated scan results and mark as 'expired' */
void wifi_scanresults_mark_expired(struct wifi_scanresults *results)
{
	int i;
	struct wifi_scanresults_entry *en;

	for (i = 0; i < results->entry_num; i++) {
		en = &results->entry[i];

		if (en->expired == false &&
				timestamp_expired(&en->tsp, SCANRESULTS_MAX_AGE))
			en->expired = true;
	}
}

static struct wifi_bss *bss_get_fresh(char *ifname,
		uint8_t *bssid, struct wifi_bss *out)
{
	struct wifi_bss *bsss;
	char *data;
	/* void *a; */
	int num;
	int i;

	num = 256;
	data = calloc(num, sizeof(struct wifi_bss));
	if (!data) {
		agnt_warn(LOG_SCAN, "OOM scan results\n");
		return NULL;
	}
	bsss = (struct wifi_bss *)data;
	if (wifi_get_scan_results(ifname, bsss, &num, false) != 0) {
		agnt_dbg(LOG_SCAN, "error getting scanresults '%s'\n", ifname);
		goto out_free;
	}

	for (i = 0; i < num; i++) {
		if (!memcmp(bsss[i].bssid, bssid, 6)) {
			if (out)
				memcpy(out, &bsss[i], sizeof(struct wifi_bss));
			free(data);
			return out;
		}
	}
	agnt_dbg(LOG_SCAN, "Neighbor " MACFMT " NOT in scanresults\n", MAC2STR(bssid));
out_free:
	free(data);
	return NULL;
}

struct wifi_bss *wifi_scanresults_get_bss(char *ifname,
		uint8_t *bssid, struct wifi_bss *out)
{
	/* FIXME use stored scan results instead of fresh */
	return bss_get_fresh(ifname, bssid, out);
}
