/*
 * scan.c - scan related code
 *
 * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
 *
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "scan.h"

#include <easy/utils.h>
#include <easy/if_utils.h>
#include <wifidefs.h>
#include <string.h>

#include "agent.h"
#include "agent_cmdu.h"
#include "agent_map.h"
#include "utils/utils.h"
#include "wifi.h"
#include "wifi_opclass.h"

int scan_init_scanlist(struct agent *a, struct wifi_radio_element *re)
{
	struct wifi_radio_opclass_channel *channel;
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass *opclass;
	int opclass_supported;
	int num_channels;
	int i, j, k;

	agnt_trace(LOG_SCAN, "agent: %s: --->\n", __func__);

	opclass = &re->opclass;
	k = 0;

	scan_free_scanlist(re);

	re->scanlist = calloc(1, sizeof(*re->scanlist));
	if (!re->scanlist) {
		agnt_dbg(LOG_SCAN, "%s: out of memory!\n", __func__);
		return -1;
	}

	opclass_supported = wifi_opclass_num_supported_bw20(opclass);
	re->scanlist->opclass_scanlist =
		calloc(opclass_supported,
			sizeof(struct wifi_scanres_opclass_element));

	if (!re->scanlist->opclass_scanlist) {
		agnt_dbg(LOG_SCAN, "%s: out of memory!\n", __func__);
		goto error;
	}

	re->scanlist->num_opclass = 0;
	for (i = 0; i < opclass->entry_num; i++) {
		entry = &opclass->entry[i];

		if (!wifi_opclass_id_supported(opclass, entry->id)
				|| entry->bandwidth != 20) /* caps */
			continue;

		re->scanlist->opclass_scanlist[k].opclass = entry->id;
		re->scanlist->opclass_scanlist[k].bandwidth = entry->bandwidth;
		/* Store for all supported channels in that opclass */
		num_channels = entry->channel_num;
		re->scanlist->opclass_scanlist[k].num_channels =
				num_channels;

		re->scanlist->opclass_scanlist[k].channel_scanlist =
			calloc(num_channels,
				sizeof(struct wifi_scanres_channel_element));

		if (!re->scanlist->opclass_scanlist[k].channel_scanlist) {
			agnt_dbg(LOG_SCAN, "%s: out of memory!\n", __func__);
			goto error;
		}
		/* Store for all supported opclasses */
		re->scanlist->num_opclass++;

		for (j = 0; j < entry->channel_num; j++) {
			channel = &entry->channel[j];
			re->scanlist->opclass_scanlist[k].channel_scanlist[j].channel =
							channel->channel;
		}

		k++;
	}

	return 0;

error:
	scan_free_scanlist(re);
	return -1;
}

void scan_free_scanlist(struct wifi_radio_element *re)
{
	agnt_trace(LOG_SCAN, "agent: %s: --->\n", __func__);

	struct wifi_scanres_element *scanres_el;
	int j;

	/* return if no scanresults to free */
	if (!re || !re->scanlist)
		return;

	scanres_el = re->scanlist;
	for (j = 0; j < scanres_el->num_opclass; j++) {
		int k;
		struct wifi_scanres_opclass_element *opclass;

		opclass = &scanres_el->opclass_scanlist[j];
		if (!opclass)
			continue;

		for (k = 0; k < opclass->num_channels; k++) {
			struct wifi_scanres_channel_element *ch_el;

			ch_el = &opclass->channel_scanlist[k];
			if (!ch_el)
				continue;

			free(ch_el->nbrlist);
			ch_el->nbrlist = NULL;
		}

		free(opclass->channel_scanlist);
		opclass->channel_scanlist = NULL;
	}

	free(scanres_el->opclass_scanlist);
	scanres_el->opclass_scanlist = NULL;
	free(scanres_el);
	re->scanlist = NULL;
}



static struct wifi_scanres_channel_element *wifi_get_scanres_ch_element(
		struct wifi_radio_element *re, uint8_t classid, uint8_t ch)
{
	int i, j;

	if (!re || !re->scanlist)
		return NULL;

	for (i = 0; i < re->scanlist->num_opclass; i++) {
		struct wifi_scanres_opclass_element *op_el;

		op_el = &re->scanlist->opclass_scanlist[i];
		if (op_el->opclass != classid)
			continue;

		for (j = 0; j < op_el->num_channels; j++) {
			if (op_el->channel_scanlist[j].channel == ch)
				return &op_el->channel_scanlist[j];
		}
	}

	agnt_dbg(LOG_SCAN, "No operating class with channel %d\n", ch);
	return NULL;
}

static void free_scanlist_neighbors(struct wifi_radio_element *re)
{
	struct wifi_scanres_element *scanres_el;
	int j;

	scanres_el = re->scanlist;
	if (!scanres_el)
		return;

	for (j = 0; j < scanres_el->num_opclass; j++) {
		int k;
		struct wifi_scanres_opclass_element *opclass = &scanres_el->opclass_scanlist[j];

		for (k = 0; k < opclass->num_channels; k++) {
			struct wifi_scanres_channel_element *ch_el = &opclass->channel_scanlist[k];

			if (ch_el->nbrlist)
				free(ch_el->nbrlist);
			ch_el->nbrlist = NULL;
			ch_el->num_neighbors = 0;
		}
	}
}

/* Propagate radio's scanlist with cached scan results */
int scan_cache_to_scanlist(const struct agent *a,
		struct wifi_radio_element *re, bool fresh)
{
	int i;

	if (WARN_ON(!a || !re))
		return -1;

	/* Empty the scanlist first */
	free_scanlist_neighbors(re);

	for (i = 0; i < re->scanresults.entry_num; i++) {
		struct wifi_scanresults_entry *entry;
		struct wifi_scanres_channel_element *ch;
		struct wifi_scanres_neighbor_element *nbr;
		struct wifi_bss *bss;
		uint8_t classid_bw20;

		entry = &re->scanresults.entry[i];
		bss = &entry->bss;

		if (fresh && entry->expired) {
			/* don't use expired entries if fresh requested */
			agnt_dbg(LOG_SCAN, "%s [%d/%d] won't report expired nbr " MACFMT "\n",
			    __func__, entry->opclass, bss->channel, MAC2STR(bss->bssid));
			continue;
		}

		/* Use BW20 classids, but duplicate entries for 124(BW20) & 125(BW20) */
		if (entry->opclass == 124 || entry->opclass == 125)
			classid_bw20 = entry->opclass;
		else
			classid_bw20 = wifi_opclass_find_id_from_channel(
							&re->opclass,
							bss->channel,
							20); /* caps */

		ch = wifi_get_scanres_ch_element(re, classid_bw20, bss->channel);
		if (!ch)
			/* unsupported opclass or channel */
			continue;

		if (!ch->num_neighbors)
			nbr = calloc(1, sizeof(*(ch->nbrlist)));
		else {
			size_t size = (ch->num_neighbors + 1) * (sizeof(*(ch->nbrlist)));

			nbr = (struct wifi_scanres_neighbor_element *)realloc(
						ch->nbrlist, size);
		}

		if (WARN_ON(!nbr))
			return -ENOMEM;

		/* bssid */
		memcpy(nbr[ch->num_neighbors].bssid, bss->bssid, 6);
		/* ssid */
		strncpy(nbr[ch->num_neighbors].ssid, (char *)bss->ssid,
				sizeof(nbr[0].ssid) - 1);
		/* rssi */
		nbr[ch->num_neighbors].rssi = bss->rssi;
		/* bw */
		nbr[ch->num_neighbors].bw = wifi_bw_to_bw(bss->curr_bw);
		/* utilization */
		nbr[ch->num_neighbors].utilization = bss->load.utilization;
		/* num_stations */
		nbr[ch->num_neighbors].num_stations = bss->load.sta_count;

		ch->nbrlist = nbr;
		ch->num_neighbors++;
	}

	return 0;
}

int scan_radio(struct agent *a, const char *radio,
			       const char *ssid, int num_opclass,
			       uint8_t *opclass, int num_channel,
			       uint8_t *channel)
{
	struct scan_param_ex param = {};
	int ret = 0;

	if (ssid) {
		param.flag |= WIFI_SCAN_REQ_SSID;
		param.num_ssid = 1;

		strncpy(param.ssid[0], ssid, strlen(param.ssid[0]) - 1);
	}

	ret = wifi_scan(radio, &param, num_opclass, opclass, num_channel, channel);

	return ret;
}

int scan_radio_all_channels(const char *radio)
{
	struct scan_param_ex param = {0};
	int ret;

	/* Use native WiFi scan without specifying channels - let driver decide */
	ret = wifi_scan(radio, &param, 0, NULL, 0, NULL);
	if (ret)
		agnt_dbg(LOG_SCAN, "%s: wifi_scan (all channels) failed with error %d for radio %s\n",
		    __func__, ret, radio);

	return ret;
}

int scan_radio_opclass(const char *radio, int num_opclasses, uint8_t *opclasses)
{
	struct scan_param_ex param = {0};
	int ret;

	/* Use native WiFi scan with opclasses only - let driver handle channel selection */
	ret = wifi_scan(radio, &param, num_opclasses, opclasses, 0, NULL);
	if (ret)
		agnt_dbg(LOG_SCAN, "%s: wifi_scan (opclass only) failed with error %d for radio %s\n",
		    __func__, ret, radio);

	return ret;
}

int issue_channel_scan(struct agent *a, const char *radio_name,
			      struct wifi_radio_opclass *opclass,
			      struct wifi_scan_request_radio *req)
{
	agnt_trace(LOG_SCAN, "%s: --->\n", __func__);

	uint8_t opclasses[128] = {};
	uint8_t channels[128] = {};
	int op_idx = 0, ch_idx = 0;
	int i, j;
	int ret = 0;

	if (WARN_ON(!a || !req))
		return -1;

	agnt_trace(LOG_SCAN, "Attempting to scan radio %s neighbors\n", radio_name);

	/* pass all opclasses & channels */
	for (i = 0; i < req->num_opclass; i++) {

		for (j = 0; j < req->opclass[i].num_channel; j++) {
			if (ch_idx >= ARRAY_SIZE(channels))
				break;

			if (req->opclass[i].channel[j] > 0) {
				channels[ch_idx++] =
					req->opclass[i].channel[j];
			}
		}

		/* Channels listed explicitly */
		if (req->opclass[i].num_channel)
			continue;

		if (req->opclass[i].classid == 0) {
			/* only channels provided, skip opclass */
			// TODO: revisit
			continue;
		}

		if (op_idx >= ARRAY_SIZE(opclasses))
			break;

		opclasses[op_idx++] = req->opclass[i].classid;

		/* num_channel == 0 indicates that the Multi-AP Agent is
		 * requested to scan on all channels in the Operating Class.
		 */
		if (req->opclass[i].num_channel == 0 && ch_idx < ARRAY_SIZE(channels)) {
			int idx = ARRAY_SIZE(channels) - ch_idx;

			wifi_opclass_get_supported_ctrl_channels(
				opclass,
				req->opclass[i].classid,
				&channels[ch_idx],
				&idx);

			ch_idx += idx;
		}
	}

	ch_idx = remove_uint8_duplicates(channels, ch_idx);

	/* Pass all the opclasses & channels from the request */
	if (op_idx > 0 || ch_idx > 0)
		ret = scan_radio(a, radio_name, NULL, op_idx, opclasses,
					  ch_idx, channels);
	else
		agnt_dbg(LOG_SCAN, "%s: no opclass/channel to scan\n", __func__);

	return ret;
}

/* Returns false if any of the requested opc/channel is not available
 * for scan. True otherwise.
 */
static bool scan_supported(struct agent *a, struct wifi_scan_request_radio *req,
		struct wifi_radio_element *re)
{
	agnt_trace(LOG_SCAN, "%s: --->\n", __func__);

	uint8_t classid;
	int i, j;

	if (!re)
		/* No such radio */
		return false;

	if (req->num_opclass == 0)
		/* Scan all opclasses */
		return true;

	for (i = 0; i < req->num_opclass; i++) {
		if (req->opclass[i].num_channel == 0)
			/* Scan all channels */
			continue;

		for (j = 0; j < req->opclass[i].num_channel; j++) {
			uint8_t channel;

			channel = req->opclass[i].channel[j];

			if (req->opclass[i].classid)
				classid = req->opclass[i].classid;
			else
				classid = wifi_opclass_get_id(&re->opclass,
							      channel,
							      20);

			if (!is_channel_supported_by_radio(re, classid, channel))
				return false;
		}
	}

	return true;
}

int scan_process_request(struct agent *a, struct wifi_scan_request *ch_scan_req)
{
	int i;

	for (i = 0; i < ch_scan_req->num_radio; i++) {
		struct wifi_scan_request_radio *scan_req;
		struct wifi_radio_element *re;
		bool scan = false;
		int ret;

		scan_req = &ch_scan_req->radio[i];

		re = agent_get_radio(a, scan_req->radio);
		if (!re) {
			agnt_dbg(LOG_SCAN, "%s: radio not found:"MACFMT"\n",
				__func__, MAC2STR(scan_req->radio));
			continue;
		}

		if (!(ch_scan_req->mode & SCAN_REQUEST_FRESH_SCAN)) {
			agnt_dbg(LOG_SCAN, "%s: return scan cache for radio:%s\n", __func__, re->name);
			if (a->cfg.scan_on_boot_only &&
			    re->scan_state != SCAN_DONE) {
				/* Some boot scan results missing yet */
				scan_req->status = CH_SCAN_STATUS_SCAN_NOT_COMPLETED;
			} else
				scan_req->status = CH_SCAN_STATUS_SUCCESS;
			scan_send_response(a, re, scan_req);
			continue;
		}

		if (!scan_req->num_opclass) {
			agnt_dbg(LOG_SCAN, "%s: no opclass provided, dropping scan req for radio:"MACFMT"\n",
			    __func__, MAC2STR(scan_req->radio));
			continue;
		}

		/* 'Pefrorm Fresh Scan' while 'On boot only' set in Caps */
		if (a->cfg.scan_on_boot_only
				&& ch_scan_req->mode & SCAN_REQUEST_FRESH_SCAN) {
			agnt_dbg(LOG_SCAN, "%s: [Scan Status] radio %s: BOOT SCAN ONLY\n\n", __func__,
			    re->name);

			/* Special status in 'boot only' mode for 'fresh scan' */
			scan_req->status = CH_SCAN_STATUS_BOOT_SCAN_ONLY;
		}
		/* Check all requested opc/chan pairs supported by radio */
		else if (!scan_supported(a, scan_req, re)) {
			/* Scan not supported for some opc/channel pairs */
			agnt_dbg(LOG_SCAN, "%s: [Status code] SCAN NOT SUPPORTED\n\n", __func__);

			//TODO: separate status for individual opc/ch pairs

			scan_req->status = CH_SCAN_STATUS_SCAN_NOT_SUPPORTED;
		}
		/* Scan too soon */
		else if (!timestamp_expired(&re->last_scan_tsp,
						MIN_SCAN_ITV_SEC * 1000)) {
			agnt_dbg(LOG_SCAN, "%s: [Status code] SCAN TOO SOON\n\n", __func__);

			scan_req->status = CH_SCAN_STATUS_TOO_SOON;
		}
		/* Ongoing scan in progress */
		else if (re->scan_state == SCAN_SCANNING) {
			agnt_dbg(LOG_SCAN, "%s: [Status code] ONGOING SCAN NOT COMPLETED\n\n", __func__);

			scan_req->status = CH_SCAN_STATUS_SCAN_NOT_COMPLETED;
		} else
			scan = true;

		/* Update scan timestamp */
		timestamp_update(&re->last_scan_tsp);

		if (!scan) {
			/* Do not scan - report failure or stored results */
			ret = scan_send_response(a, re, scan_req);
			if (ret)
				return -1;

			continue;
		}

		/* SCAN */

		/* Mark radio unscanned prior to a new scan (only) */
		re->scan_state = SCAN_NONE;

		agnt_trace(LOG_SCAN, "%s: Trying to issue channel scan on the request of mid: %d\n", __func__,
			  scan_req->mid);

		/* Issue channel scan & check return code */
		ret = issue_channel_scan(a, re->name, &re->opclass, scan_req);
		if (ret) {
			agnt_dbg(LOG_SCAN, "%s: [Status code] RADIO BUSY\n\n", __func__);

			/* Send the 'busy' response */
			scan_req->status = CH_SCAN_STATUS_TOO_BUSY;
			ret = scan_send_response(a, re, scan_req);
			if (ret)
				return -1;

			continue;
		}

		agnt_trace(LOG_SCAN, "%s: Scan started successfully.\n", __func__);
		re->scan_state = SCAN_REQUESTED;

		/* Wait (up to 5min) for the results */
		timer_set(&re->available_scan_timer, 300 * 1000);

		/* Store the request data */
		re->scan_req = *scan_req;

		/* Mark as success only after results available */
		re->scan_req.status = CH_SCAN_STATUS_SCAN_NOT_COMPLETED;
	}

	return 0;
}

static int agent_radio_update_scanresults_element(struct wifi_radio_element *re,
						  struct wifi_bss *bsss,
						  uint32_t classid)
{
	struct wifi_scanres_channel_element *scanres_el;
	int idx;

	agnt_dbg(LOG_SCAN, "%s: classid = %d\n", __func__, classid);

	scanres_el = wifi_get_scanres_ch_element(re, classid, bsss->channel);
	if (!scanres_el)
		return -1;

	// no neighbors, allocate first
	if (!scanres_el->num_neighbors) {
		scanres_el->nbrlist = calloc(1, sizeof(*(scanres_el->nbrlist)));
		if (!scanres_el->nbrlist)
			return -1;

		scanres_el->num_neighbors = 1;
	}
	// at least one neighbor, allocate one more
	else {
		struct wifi_scanres_neighbor_element *nbr;
		int size;

		size = (scanres_el->num_neighbors + 1) * sizeof(*nbr);
		nbr = (struct wifi_scanres_neighbor_element *)realloc(
					scanres_el->nbrlist, size);

		if (!nbr)
			return -1;

		scanres_el->num_neighbors++;
		scanres_el->nbrlist = nbr;
	}

	idx = scanres_el->num_neighbors - 1; // fill index

	/* Fill in the neighbour data */
	memcpy(scanres_el->nbrlist[idx].bssid, bsss->bssid, 6);

	strncpy(scanres_el->nbrlist[idx].ssid, (char *) bsss->ssid,
			sizeof(scanres_el->nbrlist->ssid) - 1);

	scanres_el->nbrlist[idx].rssi = bsss->rssi;
	scanres_el->nbrlist[idx].bw = wifi_bw_to_bw(bsss->curr_bw);
	scanres_el->nbrlist[idx].utilization = bsss->load.utilization;
	scanres_el->nbrlist[idx].num_stations = bsss->load.sta_count;
	return 0;
}

/* scan_duration expected as ms */
static int update_scan_duration(struct wifi_radio_element *re,
					      uint8_t classid, uint8_t channel,
					      uint64_t scan_duration)
{
	struct wifi_scanres_channel_element *scanres_el;

	scanres_el = wifi_get_scanres_ch_element(re, classid, channel);
	if (!scanres_el)
		return -1;

	scanres_el->scan_duration = scan_duration;
	return 0;
}

static int radio_update_scanlist(struct agent *a,
				       struct wifi_radio_element *re,
				       struct wifi_bss *bsss, int bss_num,
				       struct chan_entry *ch_entry,
				       int ch_entry_num)
{
	int i;

	agnt_trace(LOG_SCAN, "%s: update results for [%s]\n", __func__, re->name);

	/* add neighbors */
	for (i = 0; i < bss_num; i++) {
		uint8_t classid = wifi_opclass_find_id_from_channel(
					&re->opclass,
					bsss[i].channel,
					20); /* caps */

		if (agent_radio_update_scanresults_element(re, &bsss[i], classid))
			continue;

		if (classid == OPCLASS_124) {
			agnt_dbg(LOG_SCAN, "%s: duplicate entries from classid %d to %d\n",
			    __func__, OPCLASS_124, OPCLASS_125);
			if (agent_radio_update_scanresults_element(re, &bsss[i], OPCLASS_125))
				continue;
		}
	}

	/* add scan duration */
	for (i = 0; i < ch_entry_num; i++) {
		uint8_t classid = wifi_opclass_find_id_from_channel(
					&re->opclass,
					ch_entry[i].channel,
					20); /* caps */

		/* TODO: current method is not an accurate measurement
		 * of scan duration for current channel
		 */
		if (ch_entry[i].channel == re->current_channel)
			continue;

		update_scan_duration(re, classid, ch_entry[i].channel,
				ch_entry[i].survey.cca_time / 1000);
		/* duplicate scan duration for opclass 125 channel overlap */
		if (classid == OPCLASS_124) {
			update_scan_duration(re, OPCLASS_125, ch_entry[i].channel,
					ch_entry[i].survey.cca_time / 1000);
		}
	}

	return 0;
}

/* Uses data collected in scan cache to update ALL neighbors */
static void update_neighbors_from_scanresults(struct agent *a,
		struct wifi_scanresults *results)
{
	int i;
	struct wifi_scanresults_entry *e;

	for (i = 0; i < results->entry_num; i++) {
		e = &results->entry[i];
		update_neighbor_params(a,
				e->bss.bssid,
				e->opclass,
				e->bss.channel,
				&e->tsp);
	}
}

int scan_get_results(struct agent *a, struct wifi_radio_element *re)
{
	struct wifi_bss bss[128];
	struct chan_entry ch_entry[128];
	int bss_num = ARRAY_SIZE(bss);
	int ch_entry_num = ARRAY_SIZE(ch_entry);
	int ret;

	if (!re || re->name[0] == '\0' || !strlen(re->name))
		return -1;

	if (!re->scanlist)
		scan_init_scanlist(a, re);

	agnt_trace(LOG_SCAN, "%s: radio %s scanresults\n", __func__, re->name);

	/* Get scan results from the driver */
	ret = wifi_get_scan_results(re->name, bss, &bss_num, false);

	if (ret) {
		agnt_dbg(LOG_SCAN, "%s: failed to get radio %s scanresults\n", __func__, re->name);
		return -1;
	}

	/* Update scan cache */
	ret = wifi_scanresults_add(&re->scanresults, &re->opclass, bss, bss_num);
	if (ret) {
		agnt_dbg(LOG_SCAN, "%s: failed to update scan cache for radio %s\n", __func__, re->name);
		return -1;
	}
	ret = wifi_get_channels_info(re->name, ch_entry, &ch_entry_num);
	if (ret) {
		ch_entry_num = 0;
		agnt_dbg(LOG_SCAN, "%s: failed to fetch channels info for radio %s\n", __func__, re->name);
	}

	/* TODO: update scanlist using fresh results from the cache */
	/* Empty scanlist & add most recent results for reporting */
	free_scanlist_neighbors(re);
	ret = radio_update_scanlist(a, re, bss, bss_num, ch_entry, ch_entry_num);

	/* Use scan results to update neighbor data (channel & opclass) */
	update_neighbors_from_scanresults(a, &re->scanresults);

	return ret;
}

static bool independent_channel_scan_supported(struct agent *a)
{
	struct agent_config *cfg = &a->cfg;
	struct policy_cfg *c;

	if (!cfg)
		return false;

	c = cfg->pcfg;

	if (!c || !c->report_scan)
		return false;

	return true;
}

static void radio_scan_post_action_opclass(struct agent *a,
		struct wifi_radio_element *re)
{
	bool action_required = false;

	agnt_trace(LOG_SCAN, "[%s] scan request finished - opclass action\n", re->name);

	/* Update requested on demand or due to age */
	if (re->post_scan_action.opclass_preferences) {
		agnt_trace(LOG_SCAN, "[%s] scan request finished - opclass action on demand\n",
			  re->name);
		action_required = true;
	} else if (wifi_opclass_expired(&re->opclass, 120)) {
		agnt_trace(LOG_SCAN, "[%s] scan request finished - opclass action due to age\n",
			  re->name);
		action_required = true;
	} else
		agnt_trace(LOG_SCAN, "[%s] scan request finished - opclass action skip age\n",
			  re->name);

	if (!action_required)
		return;

	if (wifi_radio_update_opclass_preferences(a, re, true))
		return;

	re->post_scan_action.opclass_preferences = false;
}

static void radio_scan_post_actions(struct agent *a,
		struct wifi_radio_element *re)
{
	agnt_trace(LOG_SCAN, "%s --->\n", __func__);

	radio_scan_post_action_opclass(a, re);

	/* Get scan results from driver and store in radio element */
	scan_get_results(a, re);
}

int handle_wifi_radio_scan_finished(struct agent *a,
		struct wifi_radio_element *re)
{
	agnt_trace(LOG_SCAN, "%s: --->\n", __func__);

	/* If the scan was not completed in available time (0x04) */
	int ret = 0;

	if (WARN_ON(!re))
		return -1;

	if (re->scan_state != SCAN_CANCELED)
		/* Get scan results & update scanlist */
		radio_scan_post_actions(a, re);

	/* Request induced scan */
	if (re->scan_req.mid) {
		/* Scan finished, stop waiting */
		timer_del(&re->available_scan_timer);
		agnt_trace(LOG_SCAN, "%s ---> scan_finished for radio %s\n",
			  __func__, re->name);

		if (re->scan_state == SCAN_CANCELED)
			re->scan_req.status = CH_SCAN_STATUS_SCAN_ABORTED;
		else
			re->scan_req.status = CH_SCAN_STATUS_SUCCESS;

		/* Send the response */
		ret = scan_send_response(a, re, &re->scan_req);

		/* Clean up stored request */
		re->scan_req = (const struct wifi_scan_request_radio){ 0 };
	} else if (independent_channel_scan_supported(a))
		/* Independent Channel Scan (scan results w/o query) */
		ret = scan_send_response(a, re, NULL);

	return ret;
}

int scan_send_response(struct agent *a, struct wifi_radio_element *re,
		struct wifi_scan_request_radio *req)
{
	struct cmdu_buff *cmdu_data = NULL;
	int ret = 0;

	agnt_dbg(LOG_SCAN, "%s: called.\n", __func__);

	/* Generate the response cmdu */
	if (WARN_ON(!re))
		return -1;

	if (!req)
		/* Independent channel scan response */
		cmdu_data = agent_gen_independent_ch_scan_response(a, re);
	else
		cmdu_data = agent_gen_ch_scan_response_radio(a,
						re, req, req->status);

	if (!cmdu_data)
		return -1;

	/* Send the response cmdu */
	agent_send_cmdu(a, cmdu_data);
	cmdu_free(cmdu_data);
	return ret;
}
