/*
 * collector.c
 * WiFi Data Elements Collector main
 *
 *
 * Author: anjan.chanda@iopsys.eu
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 *
 */

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

#include <libubox/list.h>

#include <1905_tlvs.h>
#include <easymesh.h>
#include <map_module.h>

#include "decollector_i.h"
#include "decollector.h"

#include "backhaul_topology.h"
#include "config.h"
#include "debug.h"
#include "wifi_dataelements.h"
#include "timer.h"

struct getter_txn txnlist[] = {
	{
		.request = CMDU_TYPE_TOPOLOGY_QUERY,
		.expect = CMDU_TYPE_TOPOLOGY_RESPONSE,
		.applicability = MANDATORY,
	},
	{
		.request = CMDU_AP_CAPABILITY_QUERY,
		.expect = CMDU_AP_CAPABILITY_REPORT,
		.applicability = MANDATORY,
	},
	{
		.request = CMDU_AP_METRICS_QUERY,
		.expect = CMDU_AP_METRICS_RESPONSE,
		.applicability = CONDITIONAL,
	},
	{
		.request = CMDU_BACKHAUL_STA_CAPABILITY_QUERY,
		.expect = CMDU_BACKHAUL_STA_CAPABILITY_REPORT,
		.applicability = CONDITIONAL,
	},
	{
		.request = CMDU_TYPE_LINK_METRIC_QUERY,
		.expect = CMDU_TYPE_LINK_METRIC_RESPONSE,
		.applicability = MANDATORY,
	},
	{	.request = CMDU_CLIENT_CAPABILITY_QUERY,
		.expect = CMDU_CLIENT_CAPABILITY_REPORT,
		.applicability = CONDITIONAL,
	},
	/* {
	 *	.request = CMDU_UNASSOC_STA_LINK_METRIC_QUERY,
	 *	.expect = CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE,
	 *	.applicability = OPTIONAL,
	 *}
	 */
	{
		.request = CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH,
		.expect = CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
		.applicability = MANDATORY,
	},
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_OPERATING_CHANNEL_REPORT,
		.applicability = MANDATORY,
	},
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_CHANNEL_SCAN_REPORT,
		.applicability = OPTIONAL,
	},
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_CHANNEL_PREFERENCE_REPORT,
		.applicability = OPTIONAL,
	},
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_BEACON_METRICS_RESPONSE,
		.applicability = OPTIONAL,
	},
#if (EASYMESH_VERSION >= 6)
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_EARLY_AP_CAPABILITY_REPORT,
		.applicability = OPTIONAL,
	},
#endif
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_TYPE_AP_AUTOCONFIGURATION_WSC,
		.applicability = OPTIONAL,
	},
#if (EASYMESH_VERSION >= 3)
	{
		.request = CMDU_TYPE_NONE,
		.expect = CMDU_SERVICE_PRIORITIZATION_REQUEST,
		.applicability = OPTIONAL,
	},
#endif
};

const char *applicability_type2str(uint8_t t)
{
	switch (t) {
	case MANDATORY:
		return "mandatory";
	case CONDITIONAL:
		return "conditional";
	case OPTIONAL:
	default:
		return "optional";
	}
}

const char *getter_state2str(uint8_t s)
{
#define T2STR(s)	case s: return #s;

	switch (s) {
	T2STR(GT_IDLE)
	T2STR(GT_RUNNING)
	T2STR(GT_DONE)
	}

	return "UNKNOWN";

#undef T2STR
}

int getter_next_request(struct getter_context *gt)
{
	int i;

	for (i = 0; i < NUM_TXN; i++) {
		if (gt->txn[i].request != CMDU_TYPE_NONE &&
		    gt->txn[i].cnt == 0 &&
		    gt->txn[i].applicability == MANDATORY) {

			gt->retry = 0;
			gt->txni = i;
			break;
		}
	}

	return i;
}

void getter_clear_expect(struct getter_context *gt, uint16_t cmdutype)
{
	int i;

	for (i = 0; i < NUM_TXN; i++) {
		if (gt->txn[i].expect == cmdutype) {
			time(&gt->txn[i].tsp);
			gt->txn[i].cnt++;
			gt->rxcnt[i]++;
			return;
		}
	}
}

void getter_set_applicability(struct getter_context *gt, uint16_t cmdutype, uint8_t typ)
{
	int i;

	for (i = 0; i < NUM_TXN; i++) {
		if (gt->txn[i].expect == cmdutype) {
			gt->txn[i].applicability = typ;
			return;
		}
	}
}

void getter_update_error(struct getter_context *gt, uint16_t cmdutype)
{
	int i;

	for (i = 0; i < NUM_TXN; i++) {
		if (gt->txn[i].expect == cmdutype) {
			gt->error[i]++;
			return;
		}
	}
}

bool getter_expect_pending_any(struct getter_context *gt)
{
	int i;

	for (i = 0; i < NUM_TXN; i++) {
		if (gt->txn[i].applicability == MANDATORY && gt->rxcnt[i] == 0)
			return true;
	}

	return false;
}

bool getter_expect_pending(struct getter_context *gt, uint16_t cmdutype)
{
	int i;

	for (i = 0; i < NUM_TXN; i++) {
		if (gt->txn[i].request == cmdutype && gt->txn[i].cnt == 0)
			return true;
	}

	return false;
}

void decollector_stop_getter_all(struct decollector_private *p)
{
	struct wifi_network *net = &p->dm->network;
	struct wifi_network_device *dev = NULL;

	list_for_each_entry(dev, &net->devicelist, list) {
		decollector_stop_getter(p, dev);
	}
}

int decollector_stop_getter(struct decollector_private *p,
			    struct wifi_network_device *dev)
{
	struct getter_context *gt;

	if (!dev->priv)
		return -1;

	gt = dev->priv;
	gt->state = GT_IDLE;
	timer_del(&gt->t);
	time(&gt->end);
	info(MACFMT ": Getter stopped. Runtime = %fs\n", MAC2STR(dev->macaddr),
	     difftime(gt->end, gt->start));

	return 0;
}

int decollector_getter_get_state(struct decollector_private *p,
				 struct wifi_network_device *dev,
				 uint8_t *state)
{
	struct getter_context *gt;

	gt = dev->priv;
	*state = gt->state;
	return 0;
}

int decollector_getter_running(struct decollector_private *p,
			       struct wifi_network_device *dev)
{
	struct getter_context *gt = (struct getter_context *) dev->priv;

	if (gt && (gt->state == GT_RUNNING)) {
		dbg("Getter for " MACFMT " is still running.\n",
				MAC2STR(dev->macaddr));
		return 1;
	}

	return 0;
}

void getter_timer_cb(atimer_t *t)
{
	struct getter_context *gt = container_of(t, struct getter_context, t);
	struct decollector_private *p = (struct decollector_private *)gt->collector;
	struct wifi_network_device *dev = (struct wifi_network_device *)gt->agent;
	uint32_t resp_timeout = 2; /* in seconds */
	uint32_t err_timeout = 2; /* in seconds */
	int ret;


	ret = getter_next_request(gt);
	if (ret == NUM_TXN) {
		/* no more requests pending */
		decollector_stop_getter(p, dev);
		return;
	}

	dbg("Getter for %s to " MACFMT " --->\n",
	    map_cmdu_type2str(gt->txn[gt->txni].request), MAC2STR(dev->macaddr));

	ret = prepare_1905_query(p, gt->txn[gt->txni].request, dev, dev->macaddr);
	if (!ret) {
		/* successfully sent the cmdu */
		gt->retry = 0;
		gt->tmo = resp_timeout * 1000;
		timer_set(&gt->t, gt->tmo);
	} else {
		/* retry after 2s */
		gt->retry++;
		gt->tmo = err_timeout * 1000;
		timer_set(&gt->t, gt->tmo);
		info(MACFMT ": Send %s (retry = %d)\n", MAC2STR(dev->macaddr),
		     map_cmdu_type2str(gt->txn[gt->txni].request), gt->retry);
	}
}

int decollector_alloc_getter(struct decollector_private *p,
			     struct wifi_network_device *dev)
{
	struct getter_context *gt;

	gt = calloc(1, sizeof(struct getter_context));
	if (!gt)
		return -1;

	dev->priv = gt;
	timer_init(&gt->t, getter_timer_cb);
	gt->collector = p;
	gt->agent = dev;
	memcpy(gt->txn, txnlist, sizeof(txnlist));
	gt->txni = 0;

	dbg("Alloc getter timer %p for " MACFMT "\n", &gt->t, MAC2STR(dev->macaddr));

	return 0;
}

int decollector_reset_getter(struct decollector_private *priv,
			     struct wifi_network_device *dev)
{
	struct getter_context *gt;

	if (!dev->priv)
		return -1;

	gt = dev->priv;
	memcpy(gt->txn, txnlist, sizeof(txnlist));
	gt->txni = 0;
	time(&gt->start);

	return 0;
}

int decollector_free_getter(struct wifi_network_device *dev)
{
	struct getter_context *gt;

	if (!dev->priv)
		return 0;

	gt = dev->priv;
	timer_del(&gt->t);
	gt->agent = NULL;
	gt->collector = NULL;

	free(gt);
	dev->priv = NULL;

	return 0;
}

int decollector_sched_getter(struct decollector_private *p,
			     struct wifi_network_device *dev,
			     uint32_t after_ms)
{
	struct getter_context *gt = dev->priv;
	int ret;

	ret = getter_next_request(gt);
	if (ret == NUM_TXN) {
		/* no more requests pending */
		decollector_stop_getter(p, dev);
		return 0;
	}

	return timer_set(&gt->t, after_ms);
}

bool is_network_device_alive(struct wifi_network_device *dev)
{
	struct getter_context *gt = dev->priv;

	return gt->state == GT_IDLE ?
		!getter_expect_pending(gt, CMDU_TYPE_TOPOLOGY_QUERY) : true;
}

struct wifi_network_device *find_network_device(struct wifi_data_element *dm,
						uint8_t *alid)
{
	struct wifi_network_device *dev = NULL;

	list_for_each_entry(dev, &dm->network.devicelist, list) {
		if (!memcmp(dev->macaddr, alid, 6))
			return dev;
	}

	return NULL;
}

struct wifi_radio_element *find_radio(const struct wifi_network_device *dev, const uint8_t *macaddr)
{
	struct wifi_radio_element *r = NULL;

	list_for_each_entry(r, &dev->radiolist, list) {
		if (!memcmp(r->macaddr, macaddr, 6))
			return r;
	}

	return NULL;
}

static void decolloctor_remove_not_found_devs(const uint8_t *discovered_macs,
					      int discovered_macs_number,
					      struct decollector_private *priv)
{
	struct wifi_network_device *dev = NULL, *tmp = NULL;

	list_for_each_entry_safe(dev, tmp, &priv->dm->network.devicelist, list) {
		int i;
		bool device_found = false;

		for (i = 0; i < discovered_macs_number; i++) {
			if (!memcmp(dev->macaddr, &discovered_macs[i * 6], 6)) {
				device_found = true;
				break;
			}
		}

		if (!device_found) {
			list_del(&dev->list);
			decollector_free_getter(dev);
			free_network_device(dev);
			priv->dm->network.num_devices--;
		}
	}
}

static uint8_t *decolloctor_create_list_of_new_devices(
	const uint8_t *discovered_macs, int discovered_macs_number,
	const struct decollector_private *priv, uint32_t *new_devs_number)
{
	struct wifi_network_device *dev;
	int i;
	uint32_t new_devices_number = 0;
	uint8_t *new_devices = calloc(discovered_macs_number, 6);

	if (!new_devices)
		return NULL;

	for (i = 0; i < discovered_macs_number; i++) {
		bool known_device = false;

		list_for_each_entry(dev, &priv->dm->network.devicelist, list) {
			if (!memcmp(dev->macaddr, &discovered_macs[i * 6], 6)) {
				known_device = true;
				break;
			}
		}

		if (!known_device) {
			memcpy(&new_devices[new_devices_number * 6],
			       &discovered_macs[i * 6], 6);
			++new_devices_number;
		}
	}

	*new_devs_number = new_devices_number;

	return new_devices;
}

static struct wifi_network_device *
decollector_alloc_network_device(const uint8_t *macaddr, struct decollector_private *priv)
{
	struct wifi_network_device *dev =
		calloc(1, sizeof(struct wifi_network_device));
	if (!dev) {
		err("%s: %d -ENOMEM\n", __func__, __LINE__);
		return NULL;
	}

	memcpy(dev->macaddr, macaddr, 6);
	if (decollector_alloc_getter(priv, dev)) {
		err("%s: %d -ENOMEM\n", __func__, __LINE__);
		free_network_device(dev);
		return NULL;
	}

	dev->map_profile = MULTIAP_PROFILE_1;
	INIT_LIST_HEAD(&dev->radiolist);
	INIT_LIST_HEAD(&dev->apmldlist);
	INIT_LIST_HEAD(&dev->bstamld.affbstalist);
	INIT_LIST_HEAD(&dev->cac_statuslist);
	INIT_LIST_HEAD(&dev->sp_rulelist);
	INIT_LIST_HEAD(&dev->sta_steer_disallowlist);
	INIT_LIST_HEAD(&dev->sta_btmsteer_disallowlist);
	INIT_LIST_HEAD(&dev->anticipated_channel_usagelist);
	INIT_LIST_HEAD(&dev->bh_downlist);

	INIT_LIST_HEAD(&dev->ev.assoclist);
	INIT_LIST_HEAD(&dev->ev.disassoclist);
	INIT_LIST_HEAD(&dev->ev.failconnlist);

	return dev;

}

int decollector_alloc_deagents(struct decollector_private *priv)
{

	uint8_t *discovered_macs = NULL;
	int num_of_discovered = 0;
	uint8_t *new_devices = NULL;
	uint32_t new_devs_number = 0;
	struct wifi_network_device *dev = NULL;
	int ret;
	int i;

	ret = decollector_get_deagents(priv, &num_of_discovered, &discovered_macs);
	if (ret) {
		dbg("%s: ret = %d\n", __func__, ret);
		return ret;
	}

	dbg("%s: current devs number in model:  %d\n", __func__, priv->dm->network.num_devices);
	dbg("%s: discovered devs: %d\n", __func__, num_of_discovered);

	decolloctor_remove_not_found_devs(discovered_macs, num_of_discovered, priv);

	dbg("%s: devs number in model after removal:  %d\n", __func__, priv->dm->network.num_devices);

	new_devices = decolloctor_create_list_of_new_devices(
		discovered_macs, num_of_discovered, priv, &new_devs_number);

	dbg("%s: number of new devs to be added to model:  %d\n", __func__, new_devs_number);

	for (i = 0; i < new_devs_number; i++) {
		dev = decollector_alloc_network_device(&new_devices[i * 6], priv);

		dbg("%s: adding new dev: " MACFMT "\n", __func__, MAC2STR(&new_devices[i * 6]));
		if (dev && dev->priv) {
			list_add_tail(&dev->list, &priv->dm->network.devicelist);
			priv->dm->network.num_devices++;
		} else {
			ret = -1;
			goto out;
		}
	}

	i = 0;
	info("Num devices = %d\n", priv->dm->network.num_devices);
	list_for_each_entry(dev, &priv->dm->network.devicelist, list) {
		info("Device[%d]: " MACFMT "\n", i++, MAC2STR(dev->macaddr));
	}

out:
	free(discovered_macs);
	free(new_devices);
	return ret;
}

void decollector_stop_collection(struct decollector_private *priv)
{
	struct wifi_network_device *dev = NULL;
	struct wifi_data_element *dm = priv->dm;

	list_for_each_entry(dev, &dm->network.devicelist, list) {
		struct getter_context *gt = dev->priv;

		if (timer_pending(&gt->t))
			decollector_stop_getter(priv, dev);

#if 0	//debug
		if (getter_expect_pending_any(gt)) {
			char dbgmsg[512] = {0};

			warn(MACFMT ": Partial DataElements gathered in this collection cycle\n",
			     MAC2STR(dev->macaddr));

			for (int i = 0; i < NUM_TXN; i++) {
				if (gt->txn[i].cnt == 0)
					snprintf(dbgmsg + strlen(dbgmsg), sizeof(dbgmsg),
						"%s, ", map_cmdu_type2str(gt->txn[i].expect));
			}

			warn(MACFMT ": Pending CMDUs: %s\n", MAC2STR(dev->macaddr), dbgmsg);
		} else {
			info(MACFMT ": Full DataElements gathered in this collection cycle\n",
			     MAC2STR(dev->macaddr));
		}
#endif
	}

	priv->cstate = CT_IDLE;
}

void decollector_start_collection(void *arg)
{
	struct decollector_private *p = (struct decollector_private *)arg;
	struct wifi_network_device *dev = NULL;
	struct wifi_data_element *dm = p->dm;
	int ret;


	dbg("START collection\n");
	ret = collector_get_network_params(p, &dm->network);
	if (ret)
		err("%s: Error get network ssids!\n", __func__);

	time(&p->refresh_time);
	p->refresh_cnt++;
	p->cstate = CT_RUNNING;
	timer_set(&p->ct, DECOLLECTOR_COLLECTION_EPOCH);

	list_for_each_entry(dev, &dm->network.devicelist, list) {
		ret |= decollector_collect_node(p, dev);
	}

	remove_all_bh_topology_devices();

	ret |= decollector_get_network_steer_stats(p);
	ret |= decollector_get_sta_steer_stats(p);
	ret |= decollector_get_policy_config(p);
}

void decollector_refresh_all(struct decollector_private *priv)
{
	uint32_t ignore_refresh_int = DECOLLECTOR_COLLECTION_EPOCH / 1000 + 2;
	time_t now;
	int ret;

	time(&now);
	if (difftime(now, priv->refresh_time) < ignore_refresh_int) {
		dbg("%s: last refresh done within %us\n", __func__, ignore_refresh_int);
		return;
	}

	ret = decollector_alloc_deagents(priv);
	if (ret)
		return;

	decollector_start_collection(priv);
}
