/*
 * cntlr_ubus_dbg.c - for testing purpose only
 *
 * Copyright (C) 2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <uci.h>

#include <easy/easy.h>
#include <cmdu.h>
#include <i1905_wsc.h>
#include <1905_tlvs.h>
#include <easymesh.h>
#include <map_module.h>

#include <wifidefs.h>
#include "wifi_dataelements.h"

#include "timer.h"
#include "utils/utils.h"
#include "utils/debug.h"
#include "config.h"
#if (EASYMESH_VERSION > 2)
#include "dpp.h"
#endif
#include "cntlr.h"
#include "cntlr_map.h"
#include "cntlr_ubus.h"
#include "cntlr_tlv.h"
#include "cntlr_cmdu.h"
#include "topology.h"
#include "cntlr_ubus.h"

#define OBJECT_INVALID	((uint32_t)-1)

#ifndef MAP_CNTLR_DISABLE_UBUS_DBG

enum {
	CLR_STEER_SUMMARY_STA,
	__CLR_STEER_SUMMARY_MAX,
};

static const struct blobmsg_policy clr_steer_summary_params[__CLR_STEER_SUMMARY_MAX] = {
	[CLR_STEER_SUMMARY_STA] = { .name = "sta", .type = BLOBMSG_TYPE_STRING},
};

static int cntlr_dbg_list_macs(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct controller *c = container_of(obj, struct controller, obj_dbg);
	struct map_macaddr_entry *entry = NULL;
	struct blob_buf bb = {0};
	char macaddrstr[18] = {0};
	void *t, *tt;
	int i;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	t = blobmsg_open_table(&bb, "mac_table");

	for (i = 0; i < MAC_HTBL_SIZE; i++) {
		if (hlist_empty(&c->mac_table.table[i]))
			continue;

		hlist_for_each_entry(entry, &c->mac_table.table[i], hlist) {
			tt = blobmsg_open_table(&bb, "");

			hwaddr_ntoa(entry->macaddr, macaddrstr);
			blobmsg_add_string(&bb, "macaddr", macaddrstr);
			blobmsg_add_string(&bb, "type", macaddr_type2str(entry->type));

			blobmsg_close_table(&bb, tt);
		}
	}

	blobmsg_close_table(&bb, t);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

static int cntlr_dbg_clear_steer_summary(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct controller *c = container_of(obj, struct controller, obj_dbg);
	struct blob_attr *tb[__CLR_STEER_SUMMARY_MAX];
	uint8_t macaddr[6] = {0};
	char sta[18] = {0};
	struct sta *s = NULL;
	struct node *n = NULL;

	blobmsg_parse(clr_steer_summary_params, __CLR_STEER_SUMMARY_MAX, tb,
			blob_data(msg), blob_len(msg));

	if (tb[CLR_STEER_SUMMARY_STA]) {
		strncpy(sta, blobmsg_data(tb[CLR_STEER_SUMMARY_STA]),
				sizeof(sta) - 1);
		if (!hwaddr_aton(sta, macaddr)) {
			cntlr_warn(LOG_STEER, "%s: Must provide valid STA address!\n", __func__);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
	}

	if (!hwaddr_is_zero(macaddr)) {
		s = cntlr_find_sta(c, macaddr);
		if (s) {
			s->de_sta->mapsta.stats.no_candidate_cnt = 0;
			s->de_sta->mapsta.stats.blacklist_attempt_cnt = 0;
			s->de_sta->mapsta.stats.blacklist_success_cnt = 0;
			s->de_sta->mapsta.stats.blacklist_failure_cnt = 0;
			s->de_sta->mapsta.stats.btm_attempt_cnt = 0;
			s->de_sta->mapsta.stats.btm_success_cnt = 0;
			s->de_sta->mapsta.stats.btm_failure_cnt = 0;
			s->de_sta->mapsta.stats.btm_query_resp_cnt = 0;
			s->de_sta->mapsta.failed_steer_attempts = 0;
			timestamp_reset(&s->de_sta->mapsta.stats.last_attempt_tsp);
			timestamp_reset(&s->de_sta->mapsta.stats.last_steer_tsp);

		}
		return UBUS_STATUS_OK;
	}

	list_for_each_entry(n, &c->agentlist, list) {
		s = NULL;
		list_for_each_entry(s, &n->stalist, list) {
			struct wifi_multiap_sta *msta =
					s->de_sta->mapsta;

			msta.stats.no_candidate_cnt = 0;
			msta.stats.blacklist_attempt_cnt = 0;
			msta.stats.blacklist_success_cnt = 0;
			msta.stats.blacklist_failure_cnt = 0;
			msta.stats.btm_attempt_cnt = 0;
			msta.stats.btm_success_cnt = 0;
			msta.stats.btm_failure_cnt = 0;
			msta.stats.btm_query_resp_cnt = 0;
			msta.stats.failed_steer_attempts = 0;
			timestamp_reset(&msta.stats.last_attempt_tsp);
			timestamp_reset(&msta.stats.last_steer_tsp);
		}
	}

	c->dlem.network.steer_summary.no_candidate_cnt = 0;
	c->dlem.network.steer_summary.blacklist_attempt_cnt = 0;
	c->dlem.network.steer_summary.blacklist_success_cnt = 0;
	c->dlem.network.steer_summary.blacklist_failure_cnt = 0;
	c->dlem.network.steer_summary.btm_attempt_cnt = 0;
	c->dlem.network.steer_summary.btm_success_cnt = 0;
	c->dlem.network.steer_summary.btm_failure_cnt = 0;
	c->dlem.network.steer_summary.btm_query_resp_cnt = 0;

	return UBUS_STATUS_OK;
}

static struct wifi_radio_opclass *
cntlr_dbg_get_cur_opclass_by_bssid(struct controller *c, const unsigned char *bssid)
{
	struct node *node = NULL;

	list_for_each_entry(node, &c->agentlist, list) {
		struct netif_radio *radio = NULL;

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

			list_for_each_entry(iface, &radio->iflist, list) {
				if (!iface->bss)
					continue;
				if (!iface->bss->enabled)
					continue;
				if (!iface->bss->is_bbss)
					continue;
				if (memcmp(iface->bss->bssid, bssid, 6))
					continue;

				return &radio->radio_el->cur_opclass;
			}
		}
	}

	return NULL;
}

static int cntlr_dbg_csr(struct ubus_context *ctx,
			 struct ubus_object *obj,
			 struct ubus_request_data *req,
			 const char *method, struct blob_attr *msg)
{
	struct controller *c = container_of(obj, struct controller, obj_dbg);
	struct node *node = NULL;

	list_for_each_entry(node, &c->agentlist, list) {
		struct netif_radio *r = NULL;

		list_for_each_entry(r, &node->radiolist, list) {
			cntlr_send_channel_selection(c, node->almacaddr, r->radio_el->macaddr, NULL);
		}
	}


	return UBUS_STATUS_OK;
}

static int cntlr_dbg_bh_topology_dump(struct ubus_context *ctx,
				      struct ubus_object *obj,
				      struct ubus_request_data *req,
				      const char *method, struct blob_attr *msg)
{
	struct controller *c = container_of(obj, struct controller, obj_dbg);
	struct node *node = NULL;
	struct blob_buf bb = { 0 };
	char macaddrstr[18] = { 0 };

	blob_buf_init(&bb, 0);

	blobmsg_add_string(&bb, "topology_model_valid", is_bh_topology_valid() ? "Yes":"No");

	if (is_bh_topology_valid()) {
		void *devs_arr = blobmsg_open_array(&bb, "bh_topology_devs");

		list_for_each_entry(node, &c->agentlist, list) {
			const struct bh_topology_dev *topo_dev = topology_find_device(node->almacaddr);
			if (topo_dev &&
			    topo_dev->bh_info.level_in_tree != UNKNOWN_TREE_LEVEL) {
				void *dev_table = blobmsg_open_table(&bb, "");
				const struct backhaul_info *bh_info =
					&topo_dev->bh_info;

				blobmsg_add_string(&bb, "al_mac",
					hwaddr_ntoa(topo_dev->al_macaddr, macaddrstr));
				blobmsg_add_u16(&bb, "hops_from_root", bh_info->level_in_tree);
				blobmsg_add_u16(&bb, "wifi_hops_from_root", bh_info->wifi_hops_from_root);

				/* If device is not root device */
				if (bh_info->parent_in_tree) {
					void  *bh_table = blobmsg_open_table(&bb, "bh_info");
					struct wifi_radio_opclass *opclass;

					blobmsg_add_string(&bb, "parent_in_tree",
						hwaddr_ntoa(bh_info->parent_in_tree->al_macaddr, macaddrstr));
					blobmsg_add_string(&bb, "link_type",
						i1905_media_type_to_str(bh_info->parent_iface->media_type));
					blobmsg_add_string(&bb, "parent_iface",
						hwaddr_ntoa(bh_info->parent_iface->macaddr, macaddrstr));

					if (bh_info->own_iface) {
						blobmsg_add_string(&bb, "own_iface",
							hwaddr_ntoa(bh_info->own_iface->macaddr, macaddrstr));
					}

					if ((bh_info->parent_iface->media_type >> 8) == 1) {
						opclass = cntlr_dbg_get_cur_opclass_by_bssid(c, bh_info->parent_iface->macaddr);
						if (opclass)
							cntlr_status_add_opclass(&bb, opclass, "cur_opclass", 0);
					}

					blobmsg_close_table(&bb, bh_table);
				}

				blobmsg_add_u32(&bb, "time_since_topo_response",
					timestamp_elapsed_sec(&topo_dev->last_topo_response));

				blobmsg_close_table(&bb, dev_table);
			}
		}
		blobmsg_close_array(&bb, devs_arr);
	}

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int cntlr_publish_dbg_object(struct controller *c, const char *objname)
{
	struct ubus_object *obj;
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	struct ubus_method m[] = {
		UBUS_METHOD_NOARG("list_macs", cntlr_dbg_list_macs),
		UBUS_METHOD("clear_steer_summary", cntlr_dbg_clear_steer_summary,
			clr_steer_summary_params),
		UBUS_METHOD_NOARG("bh_topology_dump", cntlr_dbg_bh_topology_dump),
		UBUS_METHOD_NOARG("csr", cntlr_dbg_csr),
	};
	int num_methods = ARRAY_SIZE(m);
	int ret;

	obj = &c->obj_dbg;
	memset(obj, 0, sizeof(*obj));

	obj_type = calloc(1, sizeof(struct ubus_object_type));
	if (!obj_type)
		return -1;

	obj_methods = calloc(num_methods, sizeof(struct ubus_method));
	if (!obj_methods) {
		free(obj_type);
		return -1;
	}

	obj->name = objname;
	memcpy(obj_methods, m, num_methods * sizeof(struct ubus_method));
	obj->methods = obj_methods;
	obj->n_methods = num_methods;

	obj_type->name = obj->name;
	obj_type->n_methods = obj->n_methods;
	obj_type->methods = obj->methods;
	obj->type = obj_type;

	ret = ubus_add_object(c->ubus_ctx, obj);
	if (ret) {
		err("Failed to add '%s' err = %s\n",
				objname, ubus_strerror(ret));
		free(obj_methods);
		free(obj_type);
		return ret;
	}

	info("Published '%s' object\n", objname);

	return 0;
}

void cntlr_remove_dbg_object(struct controller *c)
{
	if (c->ubus_ctx && c->obj_dbg.id != OBJECT_INVALID) {
		ubus_remove_object(c->ubus_ctx, &c->obj_dbg);
		free(c->obj_dbg.type);
		free((void *) c->obj_dbg.methods);
	}
}
#else
int cntlr_publish_dbg_object(struct controller *c, const char *objname)
{
	return 0;
}

void cntlr_remove_object(struct controller *c)
{
	return;
}
#endif /* MAP_CNTLR_DISABLE_UBUS_DBG */
