/*
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 *
 *   Copyright (C) 2020 embedd.ch 
 *   Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> 
 *   Copyright (C) 2020 John Crispin <john@phrozen.org> 
 */
#include "usteer.h"
#include "event.h"

#define UEV_LOG_MAXLEN	256

static struct blob_buf b;
static const char * const uev_name[] = {
	[UEV_PROBE_REQ_ACCEPT] = "probe_req_accept",
	[UEV_PROBE_REQ_DENY] = "probe_req_deny",
	[UEV_AUTH_REQ_ACCEPT] = "auth_req_accept",
	[UEV_AUTH_REQ_DENY] = "auth_req_deny",
	[UEV_ASSOC_REQ_ACCEPT] = "assoc_req_accept",
	[UEV_ASSOC_REQ_DENY] = "assoc_req_deny",
	[UEV_LOAD_KICK_TRIGGER] = "load_kick_trigger",
	[UEV_LOAD_KICK_RESET] = "load_kick_reset",
	[UEV_LOAD_KICK_MIN_CLIENTS] = "load_kick_min_clients",
	[UEV_LOAD_KICK_NO_CLIENT] = "load_kick_no_client",
	[UEV_LOAD_KICK_CLIENT] = "load_kick_client",
	[UEV_SIGNAL_KICK] = "signal_kick",

};
static const char * const uev_reason[] = {
	[UEV_REASON_NONE] = "none",
	[UEV_REASON_RETRY_EXCEEDED] = "retry_exceeded",
	[UEV_REASON_LOW_SIGNAL] = "low_signal",
	[UEV_REASON_CONNECT_DELAY] = "connect_delay",
	[UEV_REASON_BETTER_CANDIDATE] = "better_candidate",
};

static const char * const uev_select_reason[] = {
	[UEV_SELECT_REASON_NUM_ASSOC] = "n_assoc",
	[UEV_SELECT_REASON_SIGNAL] = "signal",
	[UEV_SELECT_REASON_LOAD] = "load",
};

static void
usteer_event_add_node_status(struct usteer_node *node)
{
	blobmsg_add_u32(&b, "load", node->load);
	blobmsg_add_u32(&b, "assoc", node->n_assoc);
}

static void
usteer_event_send_ubus(struct uevent *ev)
{
	void *c;
	int i;

	if (!usteer_obj.has_subscribers)
		return;

	blob_buf_init(&b, 0);

	if (ev->node_local)
		blobmsg_add_string(&b, "node", usteer_node_name(ev->node_local));

	if (ev->sta)
		blobmsg_printf(&b, "sta", MAC_ADDR_FMT, MAC_ADDR_DATA(ev->sta->addr));

	if (ev->si_cur)
		blobmsg_add_u32(&b, "signal", (int32_t)ev->si_cur->signal);

	if (ev->reason)
		blobmsg_add_string(&b, "reason", uev_reason[ev->reason]);

	if (ev->threshold.ref) {
		c = blobmsg_open_array(&b, "threshold");
		blobmsg_add_u32(&b, NULL, ev->threshold.cur);
		blobmsg_add_u32(&b, NULL, ev->threshold.ref);
		blobmsg_close_array(&b, c);
	}

	if (ev->select_reasons) {
		c = blobmsg_open_array(&b, "select_reason");
		for (i = 0; i < ARRAY_SIZE(uev_select_reason); i++) {
			if (!(ev->select_reasons & (1 << i)) ||
				!uev_select_reason[i])
				continue;

			blobmsg_add_string(&b, NULL, uev_select_reason[i]);
		}
		blobmsg_close_array(&b, c);
	}

	if (ev->node_cur) {
		c = blobmsg_open_table(&b, "local");
		usteer_event_add_node_status(ev->node_cur);
		blobmsg_close_table(&b, c);
	}

	if (ev->node_other) {
		c = blobmsg_open_table(&b, "remote");
		blobmsg_add_string(&b, "name", usteer_node_name(ev->node_other));
		if (ev->si_other)
			blobmsg_add_u32(&b, "signal", (int32_t)ev->si_other->signal);
		usteer_event_add_node_status(ev->node_other);
		blobmsg_close_table(&b, c);
	}

	if (ev->count)
		blobmsg_add_u32(&b, "count", ev->count);

	ubus_notify(ubus_ctx, &usteer_obj, uev_name[ev->type], b.head, -1);
}

static int
usteer_event_log_node(char *buf, int len, const char *prefix, struct usteer_node *node)
{
	char *cur = buf;
	char *end = buf + len;

	cur += snprintf(cur, end - cur, " %sassoc=%d %sload=%d",
			prefix, node->n_assoc,
			prefix, node->load);

	return cur - buf;
}

static void
usteer_event_log(struct uevent *ev)
{
	char *str, *cur, *end;
	int i;

	if (!(config.event_log_mask & (1 << ev->type)))
		return;

	blob_buf_init(&b, 0);
	cur = str = blobmsg_alloc_string_buffer(&b, NULL, UEV_LOG_MAXLEN);
	end = str + UEV_LOG_MAXLEN;
	cur += snprintf(cur, end - cur, "usteer event=%s", uev_name[ev->type]);
	if (ev->node_local)
		cur += snprintf(cur, end - cur, " node=%s", usteer_node_name(ev->node_local));
	if (ev->sta)
		cur += snprintf(cur, end - cur, " sta=" MAC_ADDR_FMT, MAC_ADDR_DATA(ev->sta->addr));
	if (ev->reason)
		cur += snprintf(cur, end - cur, " reason=%s", uev_reason[ev->reason]);
	if (ev->si_cur)
		cur += snprintf(cur, end - cur, " signal=%d", ev->si_cur->signal);
	if (ev->threshold.ref)
		cur += snprintf(cur, end - cur, " thr=%d/%d", ev->threshold.cur, ev->threshold.ref);
	if (ev->count)
		cur += snprintf(cur, end - cur, " count=%d", ev->count);
	if (ev->node_cur)
		cur += usteer_event_log_node(cur, end - cur, "", ev->node_cur);
	if (ev->select_reasons) {
		bool first = true;

		cur += snprintf(cur, end - cur, " select_reason");
		for (i = 0; i < ARRAY_SIZE(uev_select_reason); i++) {
			if (!(ev->select_reasons & (1 << i)) ||
				!uev_select_reason[i])
				continue;

			cur += snprintf(cur, end - cur, "%c%s", first ? '=' : ',',
						    uev_select_reason[i]);
			first = false;
		}
	}
	if (ev->node_other) {
		cur += snprintf(cur, end - cur, " remote=%s", usteer_node_name(ev->node_other));
		if (ev->si_other)
			cur += snprintf(cur, end - cur, " remote_signal=%d",
					ev->si_other->signal);
		cur += usteer_event_log_node(cur, end - cur, "remote_", ev->node_other);
	}

	log_msg(str);
}

void usteer_event(struct uevent *ev)
{
	if (ev->type >= ARRAY_SIZE(uev_name) || !uev_name[ev->type])
		return;

	if (ev->reason >= ARRAY_SIZE(uev_reason) || !uev_reason[ev->reason])
		return;

	if (ev->si_cur) {
		if (!ev->node_local)
			ev->node_local = ev->si_cur->node;
		if (!ev->sta)
			ev->sta = ev->si_cur->sta;
	}

	if (!ev->node_local && ev->node_cur)
		ev->node_local = ev->node_cur;

	if (ev->si_other && ev->node_cur && !ev->node_other)
		ev->node_other = ev->si_other->node;

	usteer_event_send_ubus(ev);
	usteer_event_log(ev);
}

void config_set_event_log_types(struct blob_attr *attr)
{
	struct blob_attr *cur;
	int i, rem;

	config.event_log_mask = 0;
	if (!attr) {
		static const uint32_t default_log[] = {
			[MSG_INFO] =
				(1 << UEV_LOAD_KICK_CLIENT) |
				(1 << UEV_SIGNAL_KICK) |
				(1 << UEV_AUTH_REQ_DENY) |
				(1 << UEV_ASSOC_REQ_DENY),
			[MSG_VERBOSE] =
				(1 << UEV_PROBE_REQ_DENY),
			[MSG_DEBUG] =
				(1 << UEV_AUTH_REQ_ACCEPT) |
				(1 << UEV_ASSOC_REQ_ACCEPT) |
				(1 << UEV_LOAD_KICK_TRIGGER) |
				(1 << UEV_LOAD_KICK_RESET) |
				(1 << UEV_LOAD_KICK_MIN_CLIENTS) |
				(1 << UEV_LOAD_KICK_NO_CLIENT),
		};

		if (config.debug_level >= MSG_DEBUG_ALL) {
			config.event_log_mask = ~0;
			return;
		}

		for (i = 0; i < ARRAY_SIZE(default_log) && i <= config.debug_level; i++)
			config.event_log_mask |= default_log[i];

		return;
	}

	if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0)
		return;

	blobmsg_for_each_attr(cur, attr, rem) {
		const char *name = blobmsg_get_string(cur);

		for (i = 0; i < ARRAY_SIZE(uev_name); i++) {
			if (!uev_name[i] || strcmp(uev_name[i], name) != 0)
				continue;

			config.event_log_mask |= (1 << i);
			break;
		}
	}
}

void config_get_event_log_types(struct blob_buf *buf)
{
	uint32_t mask = config.event_log_mask;
	void *c;
	int i;

	c = blobmsg_open_array(buf, "event_log_types");
	for (i = 0; mask && i < ARRAY_SIZE(uev_name); i++) {
		bool cur = mask & 1;

		mask >>= 1;
		if (!cur)
			continue;

		blobmsg_add_string(buf, NULL, uev_name[i]);
	}
	blobmsg_close_array(buf, c);
}
