/*
 * cntlr_acs.c - Auto Channel Selection
 *
 * Copyright (C) 2021 IOPSYS Software Solutions AB. All rights reserved.
 *
 */
#include "acs.h"

#include <easymesh.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <easy/easy.h>
#include <libubox/list.h>
#include <wifidefs.h>
#include <time.h>

#include "cntlr.h"
#include "cntlr_cmdu.h"
#include "cntlr_map.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "wifi_dataelements.h"
#include "wifi_opclass.h"
#include "timer.h"
#include "sta.h"

/* Per radio ACS recalc timer */
static void cntlr_radio_acs_timer(atimer_t *t);

/* Opclass helpers - move it to other place */
int cntlr_radio_pref_opclass_reset(struct wifi_radio_element *radio)
{
	/*
	 * Build initial preferred opclasses from supported opclasses
	 * we receive in basic radio capabilities.
	 */
	memcpy(&radio->pref_opclass, &radio->supp_opclass, sizeof(radio->pref_opclass));
	wifi_opclass_set_preferences(&radio->pref_opclass, 15 << 4);
	timestamp_update(&radio->pref_opclass.entry_time);

	return radio->pref_opclass.num_opclass;
}

enum wifi_radio_opclass_dfs dfs_state_from_preference(uint8_t preference)
{
	uint8_t reas = preference & CHANNEL_PREF_REASON;

	switch(reas) {
	case CHANNEL_PREF_REASON_DFS_USABLE:
		return WIFI_RADIO_OPCLASS_CHANNEL_DFS_USABLE;
	case CHANNEL_PREF_REASON_DFS_AVAILABLE:
		return WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE;
	case CHANNEL_PREF_REASON_DFS_NOP:
		return WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP;
	default:
		break;
	}

	return WIFI_RADIO_OPCLASS_CHANNEL_DFS_NONE;
}

int cntlr_radio_pref_opclass_add(struct wifi_radio_element *radio, uint8_t classid,
				 uint8_t channel, uint8_t preference)
{
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass_channel *chan;
	struct wifi_radio_opclass_channel new_chan = {};
	struct wifi_radio_opclass *opclass;

	opclass = &radio->pref_opclass;

	entry = wifi_opclass_find_entry(opclass, classid);
	if (!entry)
		return -1;

	entry->id = classid;
	entry->bandwidth = wifi_opclass_get_bw(classid);

	new_chan.channel = channel;
	new_chan.preference = preference;
	new_chan.dfs = dfs_state_from_preference(preference);

	timestamp_update(&opclass->entry_time);

	/* Don't clean cac_methods/cac_time */
	chan = wifi_opclass_find_channel(entry, channel);
	if (chan) {
		chan->channel = channel;
		chan->preference = preference;

		chan->dfs = dfs_state_from_preference(preference);

		return 0;
	}

	return wifi_opclass_add_channel(entry, &new_chan);
}

int cntlr_radio_pref_opclass_set_dfs_status(struct wifi_radio_element *radio, uint8_t classid,
					    uint8_t channel, enum wifi_radio_opclass_dfs state)
{
	struct wifi_radio_opclass_channel *chan;
	struct wifi_radio_opclass *opclass;

	opclass = &radio->pref_opclass;

	chan = wifi_opclass_get_channel(opclass, classid, channel);
	if (!chan)
		return -1;

	chan->dfs = state;
	return 0;
}

void cntlr_radio_pref_opclass_dump(struct wifi_radio_element *radio)
{
	wifi_opclass_dump(&radio->pref_opclass, "dev_pref_opclass", radio->macaddr);
}

void cntlr_radio_cur_opclass_reset(struct wifi_radio_element *radio)
{
	wifi_opclass_reset(&radio->cur_opclass);
}

int cntlr_radio_cur_opclass_add(struct wifi_radio_element *radio, uint8_t classid,
				uint8_t channel, uint8_t txpower)
{
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass_channel chan = {};

	entry = wifi_opclass_find_entry(&radio->cur_opclass, classid);
	if (!entry)
		entry = wifi_opclass_new_entry(&radio->cur_opclass);
	if (!entry)
		return -1;

	entry->id = classid;
	entry->bandwidth = wifi_opclass_get_bw(classid);
	entry->max_txpower = txpower;

	chan.channel = channel;
	chan.preference = 15 << 4;

	timestamp_update(&radio->cur_opclass.entry_time);
	return wifi_opclass_add_channel(entry, &chan);
}

uint8_t ctrl_radio_cur_opclass_id(struct wifi_radio_element *radio)
{
	uint8_t id = 0;
	int ret;

	ret = wifi_opclass_get_attr(&radio->cur_opclass, NULL, NULL, &id, NULL);
	if (ret)
		return 0;
	return id;
}

uint8_t ctrl_radio_cur_opclass_ctrl_chan(struct wifi_radio_element *radio)
{
	uint8_t ctrl_chan = 0;
	int ret;

	ret = wifi_opclass_get_attr(&radio->cur_opclass, &ctrl_chan, NULL, NULL, NULL);
	if (ret)
		return 0;
	return ctrl_chan;
}

uint8_t ctrl_radio_cur_opclass_chan(struct wifi_radio_element *radio)
{
	uint8_t chan = 0;
	int ret;

	ret = wifi_opclass_get_attr(&radio->cur_opclass, NULL, NULL, NULL, &chan);
	if (ret)
		return 0;
	return chan;
}

uint16_t ctrl_radio_cur_opclass_max_bw(struct wifi_radio_element *radio)
{
	uint32_t bw = 0;
	int ret;

	ret = wifi_opclass_get_attr(&radio->cur_opclass, NULL, &bw, NULL, NULL);
	if (ret)
		return 0;
	return bw;
}

void cntlr_radio_pref_opclass_set_pref(struct wifi_radio_element *radio, uint8_t id, uint8_t preference)
{
	wifi_opclass_id_set_preferences(&radio->pref_opclass, id, preference);
}

void cntlr_radio_cur_opclass_dump(struct wifi_radio_element *radio)
{
	wifi_opclass_dump(&radio->cur_opclass, "dev_cur_opclass", radio->macaddr);
}

static bool cntlr_radio_pref_opclass_expired(struct wifi_radio_element *radio, int age)
{
	return wifi_opclass_expired(&radio->pref_opclass, age);
}

bool cntlr_node_pref_opclass_expired(struct node *node, int age)
{
	struct netif_radio *r = NULL;
	bool expired = false;

	list_for_each_entry(r, &node->radiolist, list) {
		expired |= cntlr_radio_pref_opclass_expired(r->radio_el, age);
	}

	return expired;
}

enum acs_cleanup_status {
	ACS_CLEANUP_STATUS_SKIPPED,
	ACS_CLEANUP_STATUS_SELECTED,
	ACS_CLEANUP_STATUS_REQUESTED,
	ACS_CLEANUP_STATUS_REJECTED,
	ACS_CLEANUP_STATUS_RADAR,
	ACS_CLEANUP_STATUS_ACCEPTED,
	ACS_CLEANUP_STATUS_DONE,
	ACS_CLEANUP_STATUS_ALL_CLEAN,
	ACS_CLEANUP_STATUS_NOT_SUPPORTED,
	ACS_CLEANUP_STATUS_NO_APS,
	ACS_CLEANUP_STATUS_ALREADY_ONGOING,
	ACS_CLEANUP_STATUS_TIMEOUT,
};

enum acs_recalc_status {
	ACS_RECALC_STATUS_SKIPPED,
	ACS_RECALC_STATUS_SCAN_REQUESTED,
	ACS_RECALC_STATUS_SCAN_DONE,
	ACS_RECALC_STATUS_BEST_SELECTED,
	ACS_RECALC_STATUS_BEST_REQUESTED,
	ACS_RECALC_STATUS_BEST_REJECTED,
	ACS_RECALC_STATUS_BEST_ACCEPTED,
	ACS_RECALC_STATUS_BEST_SET,
	ACS_RECALC_STATUS_BEST_ALREADY_SET,
	ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT,
	ACS_RECALC_STATUS_BSTA_CONNECTED,
	ACS_RECALC_STATUS_INVALID_DATA,
	ACS_RECALC_STATUS_BEST_SET_TIMEOUT,
	ACS_RECALC_STATUS_BEST_CAC,
	ACS_RECALC_STATUS_BEST_CAC_RADAR,
};

struct acs_params {
	struct timespec entry_time;

	/* Input params */
	int opclass;			/* recalc for this opclass */
	int bw;				/* recalc for this bandwidth */

	bool skip_dfs;			/* Skip all DFS channels */
	bool skip_dfs_not_available;	/* Prevent CAC */
	bool higest_bandwidth;		/* Use highest possible bandwidth */

	/* Output params - for channel selection request */
	int best_channel;
	int best_opclass;
	int best_bw;
	int best_pref;
	uint32_t best_cac_time;

	enum acs_recalc_status status;	/* current recalc state */
	uint16_t scan_mid;		/* scan request mid */
	uint16_t mid;			/* channel selection mid */

	bool recalc;			/* run new recalc */
};

struct acs_cac_data {
	struct cac_data data;		/* data for CAC CMDU */

	struct timespec entry_time;
	enum acs_cleanup_status status;	/* current CAC state */
	uint16_t mid;			/* CAC request mid */
	uint32_t cac_time;		/* CAC time for current request */
};

struct acs_radio_metrics_entry {
	struct timespec time;

	uint8_t anpi;			/* Noise level */
	uint8_t obss;			/* Other BSS busy factor */
	uint8_t tx;			/* tx self */
	uint8_t rx;			/* rx self */
};

struct acs_radio_metrics {
	struct timespec time;

	uint8_t anpi;	/* EWMA value */
	uint8_t obss;	/* EWMA value */
	uint8_t tx;	/* EWMA value */
	uint8_t rx;	/* EWMA value */

	/* Ring buffer for more data */
	uint8_t idx;
	struct acs_radio_metrics_entry entry[16];
};

/* acs per radio structure */
struct acs {
	struct acs_params last_acs;
	struct acs_cac_data last_cac_data;
	struct acs_radio_metrics radio_metrics;

	struct controller *cntlr;
	struct node *node;
	struct netif_radio *radio;

	atimer_t acs_timer;
};

/* Called when conroller alloc radio structure */
void *cntlr_radio_acs_alloc(struct controller *c, struct node *n, struct netif_radio *r)
{
	struct acs *acs;

	acs = calloc(1, sizeof(struct acs));
	if (!acs)
		return NULL;

	acs->cntlr = c;
	acs->node = n;
	acs->radio = r;

	timer_init(&acs->acs_timer, cntlr_radio_acs_timer);
	return acs;
}

/* Called before controller destroy radio structure */
void cntlr_radio_acs_free(void *acs)
{
	struct acs *ptr = acs;

	if (!ptr)
		return;

	timer_del(&ptr->acs_timer);
	ptr->cntlr = NULL;
	ptr->node = NULL;
	ptr->radio = NULL;

	/* Do required cleanup here before free memory */
	free(ptr);
}

/* acs/cac/metrics - helpers */
static struct acs_params *cntlr_radio_get_last_acs(struct netif_radio *r)
{
	struct acs *acs;

	acs = r->radio_el->acs;
	if (!acs)
		return NULL;

	return &acs->last_acs;
}

static struct acs_cac_data *cntlr_radio_get_last_cac(struct netif_radio *r)
{
	struct acs *acs;

	acs = r->radio_el->acs;
	if (!acs)
		return NULL;

	return &acs->last_cac_data;
}

static struct acs_radio_metrics *cntlr_radio_get_metrics(struct netif_radio *r)
{
	struct acs *acs;

	acs = r->radio_el->acs;
	if (!acs)
		return NULL;

	return &acs->radio_metrics;
}

static int cntlr_radio_acs_timer_set(struct netif_radio *r, uint32_t tmo_ms)
{
	struct acs *acs;

	acs = r->radio_el->acs;
	if (!acs)
		return -1;

	return timer_set(&acs->acs_timer, tmo_ms);
}

/* JSON status print - for UBUS radio status and trigger_acs/trigger_channel_clearing */
static char *cntlr_acs_radio_status(enum acs_recalc_status status)
{
	switch (status) {
	case ACS_RECALC_STATUS_SKIPPED:
		return "skipped";
	case ACS_RECALC_STATUS_SCAN_REQUESTED:
		return "scan requested";
	case ACS_RECALC_STATUS_SCAN_DONE:
		return "scan done";
	case ACS_RECALC_STATUS_BEST_SELECTED:
		return "best_selected";
	case ACS_RECALC_STATUS_BEST_REQUESTED:
		return "best_requested";
	case ACS_RECALC_STATUS_BEST_ACCEPTED:
		return "best_accepted";
	case ACS_RECALC_STATUS_BEST_REJECTED:
		return "best_rejected";
	case ACS_RECALC_STATUS_BEST_SET:
		return "best_set";
	case ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT:
		return "insufficient_data";
	case ACS_RECALC_STATUS_BEST_ALREADY_SET:
		return "current_best";
	case ACS_RECALC_STATUS_BSTA_CONNECTED:
		return "skipped - bsta connected";
	case ACS_RECALC_STATUS_INVALID_DATA:
		return "skipped - invalid data";
	case ACS_RECALC_STATUS_BEST_SET_TIMEOUT:
		return "best_set timeout";
	case ACS_RECALC_STATUS_BEST_CAC:
		return "best_set CAC ongoing";
	case ACS_RECALC_STATUS_BEST_CAC_RADAR:
		return "best_set CAC RADAR hit";
	default:
		break;
	}

	return "recalc_unknown";
}

void cntlr_acs_radio_info(struct blob_buf *bb, struct netif_radio *r)
{
	struct acs_params *acs;
	void *t, *a;

	acs = cntlr_radio_get_last_acs(r);
	if (!acs)
		return;

	blobmsg_add_u32(bb, "acs_request_age", (uint32_t) timestamp_elapsed_sec(&acs->entry_time));
	a = blobmsg_open_array(bb, "acs_request");
	t = blobmsg_open_table(bb, "");
	blobmsg_add_string(bb, "status", cntlr_acs_radio_status(acs->status));
	blobmsg_add_u32(bb, "channel", acs->best_channel);
	blobmsg_add_u32(bb, "bandwidth", acs->best_bw);

	if (acs->status == ACS_RECALC_STATUS_BEST_REQUESTED)
		blobmsg_add_u32(bb, "mid", acs->mid);
	blobmsg_close_table(bb, t);
	blobmsg_close_array(bb, a);
}

void cntlr_acs_node_info(struct node *node, enum wifi_band band, struct blob_buf *bb)
{
	struct netif_radio *r = NULL;
	void *t;

	list_for_each_entry(r, &node->radiolist, list) {
		struct acs_params *acs;

		acs = cntlr_radio_get_last_acs(r);
		if (!acs)
			continue;

		if (band && band != BAND_ANY && band != r->radio_el->band)
			continue;

		if (acs->status == ACS_RECALC_STATUS_SKIPPED)
			continue;

		t = blobmsg_open_table(bb, "");
		blobmsg_add_macaddr(bb, "agent", node->almacaddr);
		blobmsg_add_macaddr(bb, "radio", r->radio_el->macaddr);
		blobmsg_add_string(bb, "status", cntlr_acs_radio_status(acs->status));
		blobmsg_add_u32(bb, "channel", acs->best_channel);
                blobmsg_add_u32(bb, "bandwidth", acs->best_bw);

		if (acs->status == ACS_RECALC_STATUS_BEST_REQUESTED)
			blobmsg_add_u32(bb, "mid", acs->mid);

		blobmsg_close_table(bb, t);
	}
}

static char *cntlr_acs_radio_cleanup_status(enum acs_cleanup_status status)
{
	switch (status) {
	case ACS_CLEANUP_STATUS_SKIPPED:
		return "none";
	case ACS_CLEANUP_STATUS_SELECTED:
		return "selected";
	case ACS_CLEANUP_STATUS_REQUESTED:
		return "requested";
	case ACS_CLEANUP_STATUS_REJECTED:
		return "rejected";
	case ACS_CLEANUP_STATUS_RADAR:
		return "radar";
	case ACS_CLEANUP_STATUS_ACCEPTED:
		return "accepted";
	case ACS_CLEANUP_STATUS_DONE:
		return "done";
	case ACS_CLEANUP_STATUS_ALL_CLEAN:
		return "all clean";
	case ACS_CLEANUP_STATUS_NOT_SUPPORTED:
		return "not supported";
	case ACS_CLEANUP_STATUS_NO_APS:
		return "no APs iface";
	case ACS_CLEANUP_STATUS_ALREADY_ONGOING:
		return "CAC already ongoing";
	case ACS_CLEANUP_STATUS_TIMEOUT:
		return "timeout";
	default:
		break;
	}
	return "unknown";
}

void cntlr_dfs_radio_cleanup_info(struct node *n, struct netif_radio *r, struct blob_buf *bb)
{
	struct acs_cac_data *cac;
	void *t;

	cac = cntlr_radio_get_last_cac(r);
	if (!cac)
		return;

	t = blobmsg_open_table(bb, "");
	blobmsg_add_macaddr(bb, "agent", n->almacaddr);
	blobmsg_add_macaddr(bb, "radio", r->radio_el->macaddr);
	blobmsg_add_string(bb, "status", cntlr_acs_radio_cleanup_status(cac->status));
	blobmsg_add_u32(bb, "channel", cac->data.channel);
	blobmsg_add_u32(bb, "opclass", cac->data.opclass);

	if (cac->status == ACS_CLEANUP_STATUS_REQUESTED)
		blobmsg_add_u32(bb, "mid", cac->mid);
	blobmsg_close_table(bb, t);
}

void cntlr_acs_radio_cleanup_info(struct blob_buf *bb, struct netif_radio *r)
{
	struct acs_cac_data *cac;
	void *t, *a;

	cac = cntlr_radio_get_last_cac(r);
	if (!cac)
		return;

	blobmsg_add_u32(bb, "cleanup_request_age", (uint32_t) timestamp_elapsed_sec(&cac->entry_time));
	a = blobmsg_open_array(bb, "cleanup_request");
	t = blobmsg_open_table(bb, "");
	blobmsg_add_string(bb, "status", cntlr_acs_radio_cleanup_status(cac->status));
	blobmsg_add_u32(bb, "channel", cac->data.channel);
	blobmsg_add_u32(bb, "opclass", cac->data.opclass);

	if (cac->status == ACS_CLEANUP_STATUS_REQUESTED)
		blobmsg_add_u32(bb, "mid", cac->mid);
	blobmsg_close_table(bb, t);
	blobmsg_close_array(bb, a);
}

/* CMDU ACS builders - for scan request / preference request */
static uint16_t cntlr_acs_send_scan_request(struct node *n, struct netif_radio *r, bool fast)
{
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass *opclass;
	struct scan_req_data req = {};
	enum wifi_band band;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int num = 0;
	int i;

	cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - scan request (%s)\n",
		  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), fast ? "short": "full");

	opclass = &r->radio_el->pref_opclass;

	if (!opclass->num_opclass)
		goto out;

	/* Build proper scan request - all 20MHz channels */
	req.is_fresh_scan = true;
	req.num_radio = 1;
	memcpy(req.radios[0].radio_mac, r->radio_el->macaddr, 6);
	band = r->radio_el->band;

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

		if (entry->bandwidth != 20)
			continue;
		if (entry->num_channel == 0)
			continue;

		if (num >= ARRAY_SIZE(req.radios[0].opclasses))
			break;

		if (fast) {
			if (band == BAND_2 && entry->id == 81) {
				req.radios[0].opclasses[num].channels[0] = 1;
				req.radios[0].opclasses[num].channels[1] = 6;
				req.radios[0].opclasses[num].channels[2] = 11;
				req.radios[0].opclasses[num].num_channel = 3;
			}

			if (band == BAND_5 && entry->id == 115) {
				req.radios[0].opclasses[num].channels[0] = 36;
				req.radios[0].opclasses[num].num_channel = 1;
			}

			if (band == BAND_5 && entry->id == 118) {
				req.radios[0].opclasses[num].channels[0] = 52;
				req.radios[0].opclasses[num].num_channel = 1;
			}

			if (band == BAND_5 && entry->id == 121) {
				req.radios[0].opclasses[num].channels[0] = 100;
				req.radios[0].opclasses[num].channels[1] = 116;
				req.radios[0].opclasses[num].num_channel = 2;
			}

			if (band == BAND_5 && entry->id == 124) {
				req.radios[0].opclasses[num].channels[0] = 149;
				req.radios[0].opclasses[num].num_channel = 1;
			}

			if (band == BAND_5 && entry->id == 125)
				continue;

			if (band == BAND_6 && entry->id == 131) {
				/* scan PSC channels */
				req.radios[0].opclasses[num].channels[0] = 5;
				req.radios[0].opclasses[num].channels[1] = 21;
				req.radios[0].opclasses[num].channels[2] = 37;
				req.radios[0].opclasses[num].channels[3] = 53;
				req.radios[0].opclasses[num].channels[4] = 69;
				req.radios[0].opclasses[num].channels[5] = 85;
				req.radios[0].opclasses[num].channels[6] = 101;
				req.radios[0].opclasses[num].channels[7] = 117;
				req.radios[0].opclasses[num].channels[8] = 133;
				req.radios[0].opclasses[num].channels[9] = 149;
				req.radios[0].opclasses[num].channels[10] = 165;
				req.radios[0].opclasses[num].channels[11] = 181;
				req.radios[0].opclasses[num].channels[12] = 197;
				req.radios[0].opclasses[num].num_channel = 13;
			}
		}

		req.radios[0].opclasses[num].classid = entry->id;
		num++;
	}

	req.radios[0].num_opclass = num;

	cmdu = cntlr_gen_channel_scan_request(n->cntlr, n->almacaddr, &req);
	if (!cmdu)
		goto out;

	mid = send_cmdu(n->cntlr, cmdu);
	cmdu_free(cmdu);

out:
	cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - scan request mid %u\n",
		  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), mid);
	return mid;
}

static uint16_t cntlr_acs_send_pref_request(struct node *n)
{
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;

	cntlr_dbg(LOG_CHANNEL, "acs node send pref request " MACFMT "\n", MAC2STR(n->almacaddr));
	cmdu = cntlr_gen_channel_preference_query(n->cntlr, n->almacaddr);
	if (!cmdu)
		return mid;

	mid = send_cmdu(n->cntlr, cmdu);
	cmdu_free(cmdu);

	return mid;
}

/* ACS recalc code */
uint8_t cntrl_acs_radio_ctrl_channel(struct wifi_radio_element *radio, uint8_t opclass, uint8_t channel)
{
	struct wifi_radio_opclass_channel *chan;

	chan = wifi_opclass_get_channel(&radio->pref_opclass, opclass, channel);
	if (!chan)
		return 0;

	return wifi_get_best_ctrl_channel(&radio->pref_opclass,
					  chan->ctrl_channels,
					  ARRAY_SIZE(chan->ctrl_channels));
}

static bool cntlr_acs_radio_is_bsta_connected(struct controller *cntlr, struct netif_radio *radio)
{
	struct netif_iface *iface = NULL;

	list_for_each_entry(iface, &radio->iflist, list) {
		struct node *n = NULL;

		/* Check if sta iface */
		if (iface->bss->is_bbss || iface->bss->is_fbss)
			continue;

		/* Check all nodes */
		list_for_each_entry(n, &cntlr->nodelist, list) {
			struct sta *s = NULL;

			if (!n->sta_count)
				continue;

			list_for_each_entry(s, &n->stalist, list) {
				if (!s->de_sta)
					continue;
				/* skip other band */
				if (memcmp(iface->bss->bssid, s->de_sta->macaddr, sizeof(iface->bss->bssid)))
					continue;

				if (s->is_bsta && s->state == STA_CONNECTED)
					return true;
			}
		}
	}

	return false;
}

static void cntrl_acs_radio_common_opclass(struct controller *cntlr, struct netif_radio *rd,
					   struct wifi_radio_opclass *opclass)
{
	memcpy(opclass, &rd->radio_el->pref_opclass, sizeof(*opclass));

	/*
	 * For future use just in case nodes using different supported
	 * channels. Prevent switch to channel/opclass not supported
	 * by leaf(s).
	 * We could skip this step when ethernet backhaul used.
	 */
}

int cntlr_acs_radio_channel_recalc(struct node *node, struct netif_radio *rd, struct acs_params *params)
{
	struct wifi_radio_element *radio = rd->radio_el;
	struct wifi_radio_opclass common_opclass = {};
	struct acs_params acs_params[64] = {};
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass *opclass;
	struct acs_params *last_acs;
	int acs_params_num = 0;
	int chan, pref, reas;
	int pref_best = 0;
	int pref_cur = 0;
	uint32_t cac_time = 0;
	int prefered[64] = {0};
	int i, j, r;

	cntlr_trace(LOG_CHANNEL, "acs radio channel recalc " MACFMT " opclass %d bw %d skip_dfs %d\n",
	    MAC2STR(radio->macaddr),
	    params->opclass, params->bw, params->skip_dfs);

	last_acs = cntlr_radio_get_last_acs(rd);
	if (!last_acs)
		return -1;

	memset(last_acs, 0, sizeof(*last_acs));
	last_acs->scan_mid = 0xffff;

	if (!radio->pref_opclass.num_opclass) {
		/* We don't know node preferences yet */
		cntlr_dbg(LOG_CHANNEL, "acs radio channel recalc " MACFMT " - no pref opclass - skip recalc\n",
			  MAC2STR(radio->macaddr));

		params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT;
		goto out;
	}

	if (cntlr_acs_radio_is_bsta_connected(node->cntlr, rd)) {
		/*
		 * Node is leaf with active wifi backhaul. In such leaf should
		 * simple follow parent node. Skip channel switch request.
		 */
		cntlr_dbg(LOG_CHANNEL, "acs radio " MACFMT " - skip switch - bsta connected\n",
			  MAC2STR(radio->macaddr));

		params->status = ACS_RECALC_STATUS_BSTA_CONNECTED;
		goto out;
	}

	cntrl_acs_radio_common_opclass(node->cntlr, rd, &common_opclass);
	opclass = &common_opclass;

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

		/* First check if opclass set */
		if (params->opclass && params->opclass != entry->id)
			continue;

		/* Next check if bandwidth set */
		if (params->bw && params->bw != entry->bandwidth)
			continue;

		for (j = 0; j < entry->num_channel; j++) {
			chan = entry->channel[j].channel;
			pref = (entry->channel[j].preference & CHANNEL_PREF_MASK) >> 4;
			reas = entry->channel[j].preference & CHANNEL_PREF_REASON;
			cac_time = entry->channel[j].cac_time;

			cntlr_trace(LOG_CHANNEL, "\tacs check/cmp chan %d pref %d reas %d\n", chan, pref, reas);

			/* Always skip disabled channels */
			if (reas == CHANNEL_PREF_REASON_DFS_NOP)
				continue;
			if (reas == CHANNEL_PREF_REASON_REG_DISALLOWED)
				continue;

			/* Current channel preference */
			if (chan == params->best_channel)
				pref_cur = pref;

			/* Skip DFS channels if requested */
			if (params->skip_dfs) {
				if (reas == CHANNEL_PREF_REASON_DFS_AVAILABLE ||
				    reas == CHANNEL_PREF_REASON_DFS_USABLE)
					continue;
			}

			/* Skip non available DFS channels if requested */
			if (params->skip_dfs_not_available && reas == CHANNEL_PREF_REASON_DFS_USABLE)
				continue;

			/* If background CAC supported and enabled - wait background CAC complete */
			if (radio->bgcac_supported &&
			    reas == CHANNEL_PREF_REASON_DFS_USABLE &&
			    node->cntlr->cfg.dfs_cleanup)
				continue;

			if (WARN_ON(acs_params_num >= ARRAY_SIZE(acs_params)))
				break;

			/* Kick best value */
			if (pref > pref_best)
				pref_best = pref;

			acs_params[acs_params_num].best_channel = chan;
			acs_params[acs_params_num].best_opclass = entry->id;
			acs_params[acs_params_num].best_bw = entry->bandwidth;
			acs_params[acs_params_num].best_pref = pref;
			acs_params[acs_params_num].best_cac_time = cac_time;

			acs_params_num++;
		}
	}

	if (!pref_best) {
		cntlr_dbg(LOG_CHANNEL, "acs radio channel recalc " MACFMT " - no pref best - skip recalc\n",
			  MAC2STR(radio->macaddr));

		params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT;
		goto out;
	}

	cntlr_trace(LOG_CHANNEL, "acs radio " MACFMT " best pref %d vs current pref %d\n",
	    MAC2STR(radio->macaddr), pref_best, pref_cur);

	/* If current channel equal to best don't switch */
	if (pref_cur == pref_best) {
		cntlr_warn(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " current channel %d is the best\n",
			   MAC2STR(node->almacaddr), MAC2STR(radio->macaddr), params->best_channel);

		params->status = ACS_RECALC_STATUS_BEST_ALREADY_SET;
		goto out;
	}

	/* Get random channel from best performance */
	for (i = 0, j = 0; i < acs_params_num; i++) {
		if (acs_params[i].best_pref != pref_best)
			continue;

		if (j >= ARRAY_SIZE(prefered) - 1)
			break;

		/* Save index in table */
		prefered[j] = i;
		j++;
	}

	if (WARN_ON(!j)) {
		params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT;
		goto out;
	}

	srandom(time(NULL));
	r = random() % j;

	cntlr_trace(LOG_CHANNEL, "acs radio " MACFMT " table size %d - rand %d, index %d\n",
	    MAC2STR(radio->macaddr), j, r, prefered[r]);

	if (prefered[r] >= acs_params_num) {
		params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT;
		goto out;
	}

	params->best_channel = acs_params[prefered[r]].best_channel;
	params->best_bw = acs_params[prefered[r]].best_bw;
	params->best_opclass = acs_params[prefered[r]].best_opclass;
	params->best_cac_time = acs_params[prefered[r]].best_cac_time;
	params->best_pref = acs_params[prefered[r]].best_pref;
	params->status = ACS_RECALC_STATUS_BEST_SELECTED;


	cntlr_dbg(LOG_CHANNEL, "acs radio " MACFMT " best chan %d/%d opclass %d\n",
	    MAC2STR(radio->macaddr),
	    params->best_channel, params->best_bw, params->best_opclass);

out:
	memcpy(last_acs, params, sizeof(*last_acs));
	timestamp_update(&last_acs->entry_time);

	return params->status != ACS_RECALC_STATUS_BEST_SELECTED;
}

static int cntlr_get_current_acs_params(struct wifi_radio_element *radio, struct acs_params *params)
{
	memset(params, 0, sizeof(*params));

	if (!radio->cur_opclass.num_opclass)
		return -1;

	params->opclass = ctrl_radio_cur_opclass_id(radio);
	params->bw = ctrl_radio_cur_opclass_max_bw(radio);

	params->best_channel = ctrl_radio_cur_opclass_chan(radio);
	params->best_bw = params->bw;
	params->best_opclass = params->opclass;

	return 0;
}

void cntlr_acs_node_channel_recalc(struct node *node, enum wifi_band band, uint8_t opclass,
				   uint32_t bandwidth, bool skip_dfs, bool prevent_cac,
				   bool highest_bandwidth)
{
	struct acs_params cur_acs_params = {};
	struct acs_params acs_params = {};
	uint32_t bw = bandwidth;
	struct netif_radio *r = NULL;
	int ret;

	acs_params.skip_dfs = skip_dfs;
	acs_params.skip_dfs_not_available = prevent_cac;

	cntlr_dbg(LOG_CHANNEL, "acs node channel recalc " MACFMT " opclass %u bandwidth %u skip_dfs %d prevent_cac %d higest_bandwidth %d\n",
	    MAC2STR(node->almacaddr), opclass, bandwidth, acs_params.skip_dfs, acs_params.skip_dfs_not_available, highest_bandwidth);

	list_for_each_entry(r, &node->radiolist, list) {
		struct wifi_radio_opclass req_opclass = {};
		struct acs_params *last_acs;
		uint8_t ctrl_channel;

		if (band && band != BAND_ANY && band != r->radio_el->band)
			continue;

		last_acs = cntlr_radio_get_last_acs(r);
		if (!last_acs)
			continue;

		memset(&acs_params, 0, sizeof(acs_params));
		acs_params.skip_dfs = skip_dfs;
		acs_params.skip_dfs_not_available = prevent_cac;

		/* Try best bandwidth */
		if (highest_bandwidth && !bandwidth && !opclass) {
			bw = wifi_opclass_highest_bandwidth(&r->radio_el->pref_opclass, prevent_cac);
			cntlr_dbg(LOG_CHANNEL, "acs node channel recalc " MACFMT " radio " MACFMT " highest bandwidth %d\n",
				  MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr), bw);
		}

		if (opclass) {
			cntlr_get_current_acs_params(r->radio_el, &cur_acs_params);
			acs_params.opclass = opclass;

			if (cur_acs_params.opclass == opclass) {
				acs_params.best_channel = cur_acs_params.best_channel;
				acs_params.best_bw = cur_acs_params.best_bw;
			}
		} else if (bw) {
			cntlr_get_current_acs_params(r->radio_el, &cur_acs_params);
			acs_params.bw = bw;

			if (cur_acs_params.bw == bw) {
				acs_params.best_channel = cur_acs_params.best_channel;
				acs_params.best_bw = cur_acs_params.best_bw;
			}
		} else {
			if (cntlr_get_current_acs_params(r->radio_el, &cur_acs_params)) {
				cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - current channel not known\n",
					  MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr));
				continue;
			}

			acs_params.opclass = cur_acs_params.opclass;
			acs_params.best_channel = cur_acs_params.best_channel;
			acs_params.best_bw = cur_acs_params.best_bw;
		}

		ret = cntlr_acs_radio_channel_recalc(node, r, &acs_params);
		if (WARN_ON(ret))
			continue;

		cntlr_trace(LOG_CHANNEL, "acs node " MACFMT " radio " MACFMT " new %d/%d opclass %d vs old %d/%d opclass %d cac_time %u\n",
		    MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr), acs_params.best_channel,
		    acs_params.best_bw, acs_params.best_opclass, cur_acs_params.best_channel,
		    cur_acs_params.bw, cur_acs_params.opclass, acs_params.best_cac_time);

		/* Pick up control channel */
		if (acs_params.best_bw > 40) {
			ctrl_channel = cntrl_acs_radio_ctrl_channel(r->radio_el,
							acs_params.best_opclass,
							acs_params.best_channel);
		} else {
			ctrl_channel = acs_params.best_channel;
		}

		/* Build opclass we would like to send */
		memcpy(&req_opclass, &r->radio_el->supp_opclass, sizeof(req_opclass));
		wifi_opclass_set_preferences(&req_opclass, 0x0);

		if (wifi_radio_opclass_update_channel(&req_opclass,
						      wifi_band_to_freqband(r->radio_el->band),
						      ctrl_channel, acs_params.best_bw, 15)) {

			cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - update opclass failed\n",
				  MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr));
			continue;
		}

		wifi_opclass_dump_ex(&req_opclass, "acs_selection", r->radio_el->macaddr, false);

		ret = cntlr_send_channel_selection(node->cntlr, node->almacaddr,
						   r->radio_el->macaddr, &req_opclass);
		if (ret == 0xffff) {
			cntlr_warn(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " send channel switch request failed\n",
				   MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr));
			continue;
		}

		cntlr_warn(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " switch to best channel %d/%d ctrl %d mid %u\n",
			   MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr), acs_params.best_channel,
			   acs_params.best_bw, ctrl_channel, ret);

		last_acs->mid = ret;
		last_acs->status = ACS_RECALC_STATUS_BEST_REQUESTED;
		timestamp_update(&last_acs->entry_time);
	}
}

static void cntlr_radio_acs_timer(atimer_t *t)
{
	struct acs *acs = container_of(t, struct acs, acs_timer);
	struct netif_radio *r = acs->radio;
	struct controller *c = acs->cntlr;
	struct node *n = acs->node;
	struct acs_params *last_acs;

	last_acs = cntlr_radio_get_last_acs(r);
	if (!last_acs)
		return;

	if (cntlr_acs_radio_is_bsta_connected(c, r)) {
		cntlr_dbg(LOG_CHANNEL, "acs timer recalc (bsta connected) " MACFMT " " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));
		last_acs->status = ACS_RECALC_STATUS_BSTA_CONNECTED;
		timestamp_update(&last_acs->entry_time);
		return;
	}

	if (last_acs->recalc) {
		cntlr_dbg(LOG_CHANNEL, "acs timer recalc (recalc already set) " MACFMT " " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));
		return;
	}

	if (c->cfg.acs_scan_before_recalc == false) {
		cntlr_dbg(LOG_CHANNEL, "acs timer recalc node (don't scan) " MACFMT " " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

		last_acs->scan_mid = 0xffff;
		last_acs->recalc = true;

		/* Send node preference request */
		cntlr_acs_send_pref_request(n);
	} else {
		struct acs_radio_metrics *m;
		bool fast = false;

		cntlr_dbg(LOG_CHANNEL, "acs timer recalc node (scan) " MACFMT " " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

		m = cntlr_radio_get_metrics(r);
		if (m && (m->tx || m->rx))
			fast = true;

		/* Send scan request */
		last_acs->recalc = true;
		last_acs->scan_mid = cntlr_acs_send_scan_request(n, r, fast);
		if (last_acs->scan_mid != 0xffff) {
			last_acs->status = ACS_RECALC_STATUS_SCAN_REQUESTED;
			timestamp_update(&last_acs->entry_time);
		}
	}
}

void cntlr_acs_recalc(struct controller *c)
{
	struct node *n = NULL;
	uint32_t delay = 10;

	/* Handle main timer - prevent scan/switch at same time */
	list_for_each_entry(n, &c->nodelist, list) {
		struct netif_radio *r = NULL;

		/* Run fresh scan before preference recalc */
		list_for_each_entry(r, &n->radiolist, list) {
			cntlr_dbg(LOG_CHANNEL, "acs setup recalc timer %u seconds " MACFMT " " MACFMT "\n",
				  delay, MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

			/* In the future check more params - possible recalc postpond */
			cntlr_radio_acs_timer_set(r, delay * 1000);
			delay += 60;
		}
	}
}


/* DFS background CAC cleanup */
static uint8_t cntlr_dfs_cac_method(uint32_t methods)
{
	uint8_t cac_method = 0;

	/* allow only bgcac methods */
	if (methods & (1 << WIFI_CAC_MIMO_REDUCED))
		cac_method = CAC_METHOD_MIMO_DIM_REDUCED;
	else if (methods & (1 << WIFI_CAC_DEDICATED))
		cac_method = CAC_METHOD_DEDICATED_RADIO;
	else if (methods & (1 << WIFI_CAC_TIME_SLICED))
		cac_method = CAC_METHOD_TIME_SLICED;

	return cac_method;
}

static bool cntlr_dfs_cac_ongoing(struct wifi_radio_element *radio)
{
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass *opclass;
	int i, j;

	opclass = &radio->pref_opclass;

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

		for (j = 0; j < entry->num_channel; j++) {
			if (entry->channel[j].dfs == WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC)
				return true;
		}
	}

	return false;
}

static bool cntlr_dfs_get_usable(struct wifi_radio_opclass_entry *entry, struct acs_cac_data *cac)
{
	uint8_t reas;
	int i;

	for (i = 0; i < entry->num_channel; i++) {
		reas = entry->channel[i].preference & CHANNEL_PREF_REASON;

		if (reas != CHANNEL_PREF_REASON_DFS_USABLE)
			continue;

		/* Check if background CAC supported */
		cac->data.cac_method = cntlr_dfs_cac_method(entry->channel[i].cac_methods);
		if (!cac->data.cac_method)
			continue;

		cac->data.channel = entry->channel[i].channel;
		cac->data.opclass = entry->id;
		cac->cac_time = entry->channel[i].cac_time;
		return true;
	}

	return false;
}

static bool cntlr_radio_is_ap_iface(struct netif_radio *radio)
{
	struct netif_iface *iface = NULL;

	list_for_each_entry(iface, &radio->iflist, list) {
		/* Check if AP iface connected */
		if (iface->bss->is_fbss)
			return true;
		if (iface->bss->is_bbss)
			return true;
	}

	return false;
}

static bool cntlr_dfs_get_cac_data(struct wifi_radio_element *radio, struct acs_cac_data *cac, uint16_t bw)
{
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass *opclass;
	uint32_t cur_bw = 0;
	uint32_t cur_chan = 0;
	int i;

	if (!bw) {
		cur_bw = ctrl_radio_cur_opclass_max_bw(radio);
		cur_chan = ctrl_radio_cur_opclass_chan(radio);
	} else {
		cur_bw = bw;
		cur_chan = 0;
	}

	if (!cur_bw)
		return false;

	opclass = &radio->pref_opclass;

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

		if (entry->bandwidth != cur_bw)
			continue;

		if (!cntlr_dfs_get_usable(entry, cac))
			continue;

		if (cac->data.channel == cur_chan)
			continue;

		memcpy(cac->data.radio, radio->macaddr, sizeof(cac->data.radio));

		return true;
	}

	return false;
}

void cntlr_dfs_radio_cleanup(struct node *node, struct netif_radio *radio)
{
	struct acs_cac_data cac_data = {};
	struct acs_cac_data *last_cac_data;

	last_cac_data = cntlr_radio_get_last_cac(radio);
	if (!last_cac_data)
		return;

	if (!radio->radio_el->pref_opclass.num_opclass ||
	    !radio->radio_el->cur_opclass.num_opclass) {
		/* We don't know preferences and channels DFS state */
		cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC not yet " MACFMT "\n", MAC2STR(radio->radio_el->macaddr));
		timestamp_update(&last_cac_data->entry_time);
		last_cac_data->status = ACS_CLEANUP_STATUS_SKIPPED;
		return;
	}

	if (!radio->radio_el->bgcac_supported) {
		/* Some nodes don't even support it */
		cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC not supported " MACFMT "\n", MAC2STR(radio->radio_el->macaddr));
		timestamp_update(&last_cac_data->entry_time);
		last_cac_data->status = ACS_CLEANUP_STATUS_NOT_SUPPORTED;
		return;
	}

	cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup " MACFMT "\n", MAC2STR(radio->radio_el->macaddr));
	if (!cntlr_radio_is_ap_iface(radio)) {
		/* Wait controller get proper AP list */
		cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC no AP ifaces, skip radio\n");
		timestamp_update(&last_cac_data->entry_time);
		last_cac_data->status = ACS_CLEANUP_STATUS_NO_APS;
		return;
	}

	/* So far keep it simple - don't start new CAC request if one ongoing */
	if (cntlr_dfs_cac_ongoing(radio->radio_el)) {
		/* Could be device run own background CAC */
		cntlr_dbg(LOG_CHANNEL, "dfs radio CAC ongoing " MACFMT "\n", MAC2STR(radio->radio_el->macaddr));
		timestamp_update(&last_cac_data->entry_time);
		last_cac_data->status = ACS_CLEANUP_STATUS_ALREADY_ONGOING;
		return;
	}

	if (!cntlr_dfs_get_cac_data(radio->radio_el, &cac_data, 0)) {
		/* First use current bandwidth for clearing. Finally switch to 20MHz for left channels */
		cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup no channels left check 20MHz\n");
		if (!cntlr_dfs_get_cac_data(radio->radio_el, &cac_data, 20)) {
			cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup no channels left\n");
			timestamp_update(&last_cac_data->entry_time);
			last_cac_data->status = ACS_CLEANUP_STATUS_ALL_CLEAN;
			return;
		}
	}

	cntlr_warn(LOG_CHANNEL, "dfs node " MACFMT " radio " MACFMT " preCAC run chan %d opclass %d\n",
		   MAC2STR(node->almacaddr), MAC2STR(radio->radio_el->macaddr),
		   cac_data.data.channel, cac_data.data.opclass);
	memcpy(last_cac_data, &cac_data, sizeof(*last_cac_data));
	timestamp_update(&last_cac_data->entry_time);
	last_cac_data->status = ACS_CLEANUP_STATUS_SELECTED;

	cac_data.mid = cntlr_send_cac_req(node->cntlr, node->almacaddr, 1, &cac_data.data);

	last_cac_data->mid = cac_data.mid;
	if (cac_data.mid != 0xffff)
		last_cac_data->status = ACS_CLEANUP_STATUS_REQUESTED;
}

void cntlr_dfs_node_cleanup(struct node *node)
{
	struct netif_radio *radio = NULL;

	cntlr_trace(LOG_CHANNEL, "dfs node preCAC cleanup " MACFMT "\n", MAC2STR(node->almacaddr));

	list_for_each_entry(radio, &node->radiolist, list) {
		if (radio->radio_el->band != BAND_5)
			continue;
		cntlr_dfs_radio_cleanup(node, radio);
	}
}

void cntlr_dfs_cleanup(struct controller *c)
{
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		cntlr_dfs_node_cleanup(n);
	}
}

/* CMDU callbacks we expect to receive from nodes */
void cntlr_acs_channel_sel_response(struct netif_radio *r, uint16_t mid, uint8_t status)
{
	struct acs_params *acs;

	cntlr_dbg(LOG_CHANNEL, "new channel selection response radio " MACFMT " mid %u status %u\n",
		  MAC2STR(r->radio_el->macaddr), mid, status);

	acs = cntlr_radio_get_last_acs(r);
	if (!acs)
		return;

	if (acs->status != ACS_RECALC_STATUS_BEST_REQUESTED)
		return;

	if (acs->mid != mid)
		return;

	if (status == 0)
		acs->status = ACS_RECALC_STATUS_BEST_ACCEPTED;
	else
		acs->status = ACS_RECALC_STATUS_BEST_REJECTED;

	timestamp_update(&acs->entry_time);
}

void cntlr_acs_oper_channel_report(struct netif_radio *r)
{
	struct acs_params *acs;
	struct wifi_radio_opclass_entry *entry;
	struct wifi_radio_opclass *cur;
	int i, j;

	cntlr_dbg(LOG_CHANNEL, "new oper channel report radio " MACFMT " %u/%u\n",
		  MAC2STR(r->radio_el->macaddr),
		  ctrl_radio_cur_opclass_ctrl_chan(r->radio_el),
		  ctrl_radio_cur_opclass_max_bw(r->radio_el));

	acs = cntlr_radio_get_last_acs(r);
	if (!acs)
		return;

	if (acs->status != ACS_RECALC_STATUS_BEST_REQUESTED &&
	    acs->status != ACS_RECALC_STATUS_BEST_ACCEPTED)
		return;

	cur = &r->radio_el->cur_opclass;
	for (i = 0; i < cur->num_opclass; i++) {
		entry = &cur->opclass[i];

		if (entry->bandwidth != acs->best_bw)
			continue;

		for (j = 0; j < entry->num_channel; j++) {
			if (entry->channel[j].channel == acs->best_channel) {
				acs->status = ACS_RECALC_STATUS_BEST_SET;
				timestamp_update(&acs->entry_time);
				return;
			}
		}
	}

	/* timeout check - worst case when continous CAC required */
	if (timestamp_elapsed_sec(&acs->entry_time) > 30 + acs->best_cac_time) {
		acs->status = ACS_RECALC_STATUS_BEST_SET_TIMEOUT;
		timestamp_update(&acs->entry_time);
	}
}

void cntlr_acs_cac_completion(struct netif_radio *r, uint8_t classid,
			      uint8_t channel, uint8_t status)
{
	struct acs_params *acs;
	struct acs_cac_data *cac;

	cntlr_dbg(LOG_CHANNEL, "new cac completion radio " MACFMT " opclass %u channel %u status %u\n",
		  MAC2STR(r->radio_el->macaddr), classid, channel, status);

	cac = cntlr_radio_get_last_cac(r);
	if (!cac)
		return;

	acs = cntlr_radio_get_last_acs(r);

	if (cac->status != ACS_CLEANUP_STATUS_REQUESTED)
		return;

	if (cac->data.channel != channel)
		return;
	if (cac->data.opclass != classid)
		return;


	switch (status) {
	case CAC_COMP_REPORT_STATUS_SUCCESSFUL:
		cac->status = ACS_CLEANUP_STATUS_DONE;

		/* Kick ACS recalc - we have new available channel */
		if (acs && acs->scan_mid == 0xffff)
			cntlr_radio_acs_timer_set(r, 5 * 1000);
		break;
	case CAC_COMP_REPORT_STATUS_CAC_NOT_SUPPORTED:
	case CAC_COMP_REPORT_STATUS_TOO_BUSY:
	case CAC_COMP_REPORT_STATUS_NON_CONFORMANT:
	case CAC_COMP_REPORT_STATUS_OTHER:
		cac->status = ACS_CLEANUP_STATUS_REJECTED;
		break;
	case CAC_COMP_REPORT_STATUS_RADAR_DETECTED:
		cac->status = ACS_CLEANUP_STATUS_RADAR;
		break;
	default:
		break;
	}
}

void cntlr_acs_channel_pref_report(struct node *n, struct netif_radio *r)
{
	struct acs_params *acs;
	struct acs_cac_data *cac;

	cntlr_dbg(LOG_CHANNEL, "new pref report node " MACFMT " radio " MACFMT "\n",
		  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

	cac = cntlr_radio_get_last_cac(r);
	if (!cac)
		return;

	acs = cntlr_radio_get_last_acs(r);
	if (!acs)
		return;

	/* ACS status update */
	if (acs->status == ACS_RECALC_STATUS_BEST_REQUESTED ||
	    acs->status == ACS_RECALC_STATUS_BEST_ACCEPTED) {
		struct wifi_radio_opclass_channel *chan;

		chan = wifi_opclass_get_channel(&r->radio_el->pref_opclass,
						acs->best_opclass,
						acs->best_channel);
		if (chan) {
			switch (chan->dfs) {
			case WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC:
				acs->status = ACS_RECALC_STATUS_BEST_CAC;
				timestamp_update(&acs->entry_time);
				break;
			case WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP:
				acs->status = ACS_RECALC_STATUS_BEST_CAC_RADAR;
				timestamp_update(&acs->entry_time);
				break;
			default:
				break;
			}
		}
	}

	/* Background CAC status update */
	if (cac->status == ACS_CLEANUP_STATUS_REQUESTED ||
	    cac->status == ACS_CLEANUP_STATUS_ACCEPTED ||
	    cac->status == ACS_CLEANUP_STATUS_TIMEOUT) {
		struct wifi_radio_opclass_channel *chan;

		chan = wifi_opclass_get_channel(&r->radio_el->pref_opclass,
						cac->data.opclass,
						cac->data.channel);
		if (chan) {
			switch (chan->dfs) {
			case WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE:
				cac->status = ACS_CLEANUP_STATUS_DONE;
				break;
			case WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC:
				cac->status = ACS_CLEANUP_STATUS_ACCEPTED;
				break;
			case WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP:
				cac->status = ACS_CLEANUP_STATUS_RADAR;
				break;
			case WIFI_RADIO_OPCLASS_CHANNEL_DFS_USABLE:
				/* change CAC -> USABLE - abort possible */
				if (cac->status == ACS_CLEANUP_STATUS_ACCEPTED)
					cac->status = ACS_CLEANUP_STATUS_REJECTED;
				if (timestamp_elapsed_sec(&cac->entry_time) > 30 + cac->cac_time)
					cac->status = ACS_CLEANUP_STATUS_TIMEOUT;
				break;
			default:
				break;
			}
		}
	}

	/* Kick CAC background clearing */
	if (n->cntlr->cfg.dfs_cleanup &&
	    r->radio_el->band == BAND_5 &&
	    r->radio_el->bgcac_supported &&
	    cac->status != ACS_CLEANUP_STATUS_REQUESTED &&
	    cac->status != ACS_CLEANUP_STATUS_ACCEPTED) {
		cntlr_dbg(LOG_CHANNEL, "kick dfs cleanup " MACFMT " radio " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

		cntlr_dfs_radio_cleanup(n, r);
	}

	/* Kick ACS recalc code */
	if (acs->recalc && n->cntlr->cfg.acs && acs->scan_mid == 0xffff) {
		cntlr_dbg(LOG_CHANNEL, "kick acs reclac " MACFMT " radio " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

		acs->recalc = false;
		cntlr_acs_node_channel_recalc(n, r->radio_el->band, 0, 0,
				n->cntlr->cfg.acs_skip_dfs,
				n->cntlr->cfg.acs_prevent_cac,
				n->cntlr->cfg.acs_highest_bandwidth);
	}
}

static uint8_t ewma(uint8_t cur, uint8_t prev, uint8_t alpha)
{
	uint8_t new;

	if (alpha > 100)
		alpha = 100;

	new = (alpha * cur)/100 + ((100 - alpha) * prev) / 100;

	return new;
}

static bool cntlr_acs_radio_add_and_check_metrics(struct netif_radio *r, uint8_t anpi, uint8_t obss,
						  uint8_t tx, uint8_t rx)
{
	struct acs_radio_metrics *m;
	struct acs_radio_metrics_entry *last, *next;

	m = cntlr_radio_get_metrics(r);
	if (!m)
		return false;

	timestamp_update(&m->time);
	m->anpi = ewma(anpi, m->anpi, 60);
	m->obss = ewma(obss, m->obss, 60);
	m->tx = ewma(tx, m->tx, 60);
	m->rx = ewma(rx, m->rx, 60);

	last = &m->entry[m->idx];

	cntlr_trace(LOG_CHANNEL, "radio ewma metrics " MACFMT " anpi %u obss %u tx %u rx %u\n",
		    MAC2STR(r->radio_el->macaddr), m->anpi, m->obss, m->tx, m->rx);

	/* Update ring buffer */
	if (timestamp_invalid(&last->time)) {
		timestamp_update(&last->time);
		last->anpi = m->anpi;
		last->obss = m->obss;
		last->tx = m->tx;
		last->rx = m->rx;
	}

	/* Save/check data to ring buffer each 300 seconds */
	if (timestamp_elapsed_sec(&last->time) >= 300) {
		m->idx++;
		m->idx %= ARRAY_SIZE(m->entry);

		next = &m->entry[m->idx];

		timestamp_update(&next->time);
		next->anpi = m->anpi;
		next->obss = m->obss;
		next->tx = m->tx;
		next->rx = m->rx;

		cntlr_dbg(LOG_CHANNEL, "metrics[%u] radio " MACFMT " anpi %u (%d) obss %u (%d) tx %u (%d) rx %u (%d)\n",
			  m->idx, MAC2STR(r->radio_el->macaddr), next->anpi,
			  next->anpi - last->anpi, next->obss, next->obss - last->obss,
			  next->tx, next->tx - last->tx,
			  next->rx, next->rx - last->rx);

		/* If other BSS busy increase - worth to check it (100% == 255) */
		if (next->obss - last->obss > 30)
			return true;

		/* If noise level worst - worth to check it (0.5 dBm) */
		if (next->anpi - last->anpi > 20)
			return true;
	}

	return false;
}

void cntlr_acs_radio_metrics(struct node *n, struct netif_radio *r,
			     uint8_t anpi, uint8_t obss,
			     uint8_t tx, uint8_t rx)
{
	struct acs_params *acs;
	struct acs_cac_data *cac;

	cntlr_trace(LOG_CHANNEL, "new radio metrics node " MACFMT " radio " MACFMT " anpi %u busy %u tx %u rx %u\n",
		    MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), anpi, obss, tx, rx);

	cac = cntlr_radio_get_last_cac(r);
	if (!cac)
		return;

	acs = cntlr_radio_get_last_acs(r);
	if (!acs)
		return;

	/* Kick CAC background clearing - check AP apears */
	if (n->cntlr->cfg.dfs_cleanup &&
	    r->radio_el->band == BAND_5 &&
	    r->radio_el->bgcac_supported &&
	    cac->status == ACS_CLEANUP_STATUS_NO_APS &&
	    timestamp_elapsed_sec(&cac->entry_time) >= 30) {
		cntlr_dbg(LOG_CHANNEL, "kick dfs cleanup (NO_APS before) " MACFMT " radio " MACFMT "\n",
			  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));
		cntlr_dfs_radio_cleanup(n, r);
	}

	if (!n->cntlr->cfg.acs)
		return;

	/* Store data for future use/compare */
	if (!cntlr_acs_radio_add_and_check_metrics(r, anpi, obss, tx, rx))
		return;

	/* Smth changed in current channel noise/busy */
	cntlr_dbg(LOG_CHANNEL, "issue ACS recalc due to metrics (request fresh pref) " MACFMT " radio " MACFMT "\n",
		  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

	/* Trigger scan/pref_req and get fresh preference report */
	cntlr_radio_acs_timer_set(r, 5 * 1000);
}

void cntlr_acs_scan_report(struct node *n, struct netif_radio *r, uint16_t mid)
{
	struct acs_params *acs;

	cntlr_dbg(LOG_CHANNEL, "new scan report node " MACFMT " radio " MACFMT " mid %u\n",
		  MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), mid);

	acs = cntlr_radio_get_last_acs(r);
	if (!acs)
		return;

	if (!n->cntlr->cfg.acs)
		return;

	/* Scan done for this radio, safe to switch channel */
	if (acs->recalc && acs->scan_mid != 0xffff) {
		acs->status = ACS_RECALC_STATUS_SCAN_DONE;
		timestamp_update(&acs->entry_time);
	}

	acs->scan_mid = 0xffff;

	/* Request fresh preferences if required */
	if (cntlr_node_pref_opclass_expired(n, 10) || acs->recalc)
		/* Preference responose with new data will trigger actions */
		cntlr_acs_send_pref_request(n);
}

void cntlr_acs_dev_supp_opclass(struct node *n, struct netif_radio *r)
{
	cntlr_trace(LOG_CHANNEL, "new dev supp opclass " MACFMT " radio " MACFMT "\n",
		    MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr));

	if (r->radio_el->pref_opclass.num_opclass == 0)
		cntlr_acs_send_pref_request(n);
}