/*
 * decollector.c
 * WiFi Data Elements plugin
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 * See LICENSE file for license related information.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libubus.h>
#include <libubox/utils.h>

#include <easy/easy.h>
#include <wifiutils.h>

#include <1905_tlvs.h>

#include "utils.h"
#include "timer.h"
#include <cmdu.h>
#include <map_module.h>

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

static int signal_pending;

static void decollector_sighandler(int sig)
{
	signal_pending = sig;
}

static int check_ageout_wifi_assoc_events(void *priv, void *ent, uint32_t age)
{
	struct wifi_assoc_event *e = (struct wifi_assoc_event *)ent;
	time_t now = time(NULL);

	return (difftime(now, e->tm) > age) ? 1 : 0;
}

static int check_ageout_wifi_disassoc_events(void *priv, void *ent, uint32_t age)
{
	struct wifi_disassoc_event *e = (struct wifi_disassoc_event *)ent;
	time_t now = time(NULL);

	return (difftime(now, e->tm) > age) ? 1 : 0;
}

static void free_wifi_assoc_event(void *priv, void *ptr)
{
	struct wifi_network_device *dev = (struct wifi_network_device *)priv;

	dev->ev.num_assoc--;
	free(ptr);
}

static void free_wifi_disassoc_event(void *priv, void *ptr)
{
	struct wifi_network_device *dev = (struct wifi_network_device *)priv;

	dev->ev.num_disassoc--;
	free(ptr);
}

void hb_timer_cb(atimer_t *t)
{
	struct decollector_private *p =
			container_of(t, struct decollector_private, hbt);
	struct wifi_network_device *dev = NULL;
	struct wifi_data_element *dm = p->dm;

	p->ticks++;

	if (signal_pending) {
		int ret;

		if (signal_pending == SIGHUP) {
			ret = collector_get_network_params(p, &dm->network);
			if (ret)
				err("%s: Error get network ssids!\n", __func__);
		}

		signal_pending = 0;
	}

	/* limit per-device events by number and/or duration */
	list_for_each_entry(dev, &dm->network.devicelist, list) {
		if (dev->ev.num_assoc > p->event_maxnum) {
			list_limit_maxnum(dev, &dev->ev.assoclist,
					  struct wifi_assoc_event,
					  list, p->event_maxnum, NULL);

			dev->ev.num_assoc = p->event_maxnum;
		}

		if (dev->ev.num_disassoc > p->event_maxnum) {
			list_limit_maxnum(dev, &dev->ev.disassoclist,
					  struct wifi_disassoc_event,
					  list, p->event_maxnum, NULL);

			dev->ev.num_disassoc = p->event_maxnum;
		}

		if (!(p->ticks % 5) && p->event_maxage) {
			list_limit_maxage(dev, &dev->ev.assoclist,
					  struct wifi_assoc_event,
					  list, p->event_maxage,
					  check_ageout_wifi_assoc_events,
					  free_wifi_assoc_event);

			list_limit_maxage(dev, &dev->ev.disassoclist,
					  struct wifi_disassoc_event,
					  list, p->event_maxage,
					  check_ageout_wifi_disassoc_events,
					  free_wifi_disassoc_event);
		}
	}

	timer_set(t, 1 * 1000);
}

void collection_timer_cb(atimer_t *t)
{
	struct decollector_private *p = container_of(t, struct decollector_private, ct);

	dbg("decollector: %s: Collection timeout\n", __func__);

	/* Stop any getter that may still be running. */
	decollector_stop_collection(p);
}

void refresh_timer_cb(atimer_t *t)
{
	struct decollector_private *p = container_of(t, struct decollector_private, t);

	dbg("%s: periodic = '%u' ms.\n", __func__, p->opts.refresh_int * 1000);

	decollector_refresh_all(p);

	if (p->opts.refresh_int)
		timer_set(t, p->opts.refresh_int * 1000);
}

int decollector_exit(void *priv)
{
	struct decollector_private *p = (struct decollector_private *)priv;
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	struct ubus_object *obj;

	if (p) {
		ubus_unregister_event_handler(p->ctx, &p->ubus_obj_ev);
		map_unsubscribe(p->ctx, p->subscriber);

		decollector_free_dm(p->dm);

		obj = &p->obj;
		obj_methods = (struct ubus_method *)obj->methods;
		if (obj_methods)
			free(obj_methods);

		obj_type = obj->type;
		if (obj_type)
			free(obj_type);

		timer_del(&p->ct);
		timer_del(&p->t);
		timer_del(&p->hbt);
		ubus_free(p->ctx);
		free(p);
	}

	uloop_done();
	return 0;
}

int decollector_init(void **priv, const struct decollector_useropts *opts)
{
	struct decollector_private *p;
	int ret;

	set_sighandler(SIGHUP, decollector_sighandler);
	set_sighandler(SIGPIPE, SIG_IGN);

	*priv = NULL;
	p = calloc(1, sizeof(struct decollector_private));
	if (!p) {
		err("Failed to allocate memory\n");
		return -1;
	}

	init_bh_topology();

	p->dm = NULL;
	p->event_maxnum = NUM_EVENTS_MAX;
	p->event_maxage = AGE_EVENTS_MAX;

	if (opts) {
		uloop_init();
		p->ctx = ubus_connect(opts->ubus_sockpath);
		if (!p->ctx) {
			err("Failed to connect to ubus\n");
			free(p);
			return -1;
		}

		ubus_add_uloop(p->ctx);

		memcpy(&p->opts, opts, sizeof(*opts));
	}

	dbg("%s:  priv = %p\n", __func__, p);

	ret = decollector_wait_for_object_timeout(p, IEEE1905_OBJECT, -1, &p->i1905);
	if (ret) {
		err("Failed to get '%s' object!\n", IEEE1905_OBJECT);
		goto out_err;
	}

	ret = decollector_wait_for_object_timeout(p, MAP_PLUGIN_OBJECT, -1, &p->i1905_map);
	if (ret) {
		err("Failed to get '%s' object!\n", MAP_PLUGIN_OBJECT);
		goto out_err;
	}

	ret = decollector_get_ieee1905id(p);
	if (ret)
		goto out_err;

	ret = decollector_register_events(p);
	if (ret)
		goto out_err;

	ret = decollector_publish_object(p, WIFI_DATAELEMENTS_OBJECT);
	if (ret)
		goto out_err;


	p->dm = calloc(1, sizeof(struct wifi_data_element));
	if (!p->dm) {
		err("Failed to allocate memory\n");
		goto out_err;
	}
	INIT_LIST_HEAD(&p->dm->network.devicelist);
	INIT_LIST_HEAD(&p->dm->network.ssidlist);
	INIT_LIST_HEAD(&p->dm->network.mldlist);
	INIT_LIST_HEAD(&p->dm->network.sta_blocklist);
	INIT_LIST_HEAD(&p->dm->network.dpplist);
	get_timestamp(NULL, p->dm->network.tsp, sizeof(p->dm->network.tsp));

	ret = decollector_alloc_deagents(p);
	if (ret)
		goto out_err;

	/* subscribe for CMDU */
	decollector_subscribe_for_cmdus(p);

	/* setup auto-refresh timer */
	timer_init(&p->t, refresh_timer_cb);
	if (p->opts.refresh_int)
		timer_set(&p->t, p->opts.refresh_int * 1000);

	timer_init(&p->ct, collection_timer_cb);

	timer_init(&p->hbt, hb_timer_cb);
	timer_set(&p->hbt, 1000);

	/* start collection */
	decollector_start_collection(p);

	*priv = p;
	return 0;

out_err:
	free(p);
	return -1;
}
