/*
 * steer_module.c - STA steering module
 *
 * Copyright (C) 2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 * See LICENSE file for license related information.
 *
 */

#include "steer_module.h"

#include <cmdu.h>
#include <dlfcn.h>
#include <libubox/list.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "utils/debug.h"
#include "cntlr.h"
#include "config.h"

#define CNTLR_STEER_MODULE_PATH		"/usr/lib/mapcontroller"

struct plugin_handle {
	void *controller;
	void *handle;
};

static int plugin_load(const char *path, const char *name, void **handle)
{
	char abspath[256] = {0};
	int flags = 0;
	void *h;

	if (!handle || !name || !path)
		return -1;

	flags |= RTLD_NOW | RTLD_GLOBAL;
	snprintf(abspath, sizeof(abspath) - 1, "%s/%s", path, name);
	h = dlopen(abspath, flags);
	if (!h) {
		err("%s: Error: %s\n", __func__, dlerror());
		return -1;
	}

	*handle = h;
	return 0;
}

static int plugin_unload(void *handle)
{
	if (!handle)
		return -1;

	return dlclose(handle);
}

static int cntlr_unload_steer_module(struct steer_control *sc)
{
	int ret;

	ret = plugin_unload(sc->handle);
	sc->controller = NULL;
	list_del(&sc->list);
	free(sc);

	return !ret ? 0 : -1;
}

int cntlr_load_steer_module(struct controller *priv, const char *name,
			    struct steer_control **sc)
{
	struct steer_control *p, *pp = NULL;
	char fname[128] = {0};
	void *handle;
	int ret;


	snprintf(fname, 127, "%s.so", name);
	ret = plugin_load(CNTLR_STEER_MODULE_PATH, fname, &handle);
	if (ret)
		return -1;

	pp = dlsym(handle, name);
	if (!pp) {
		err("Symbol '%s' not found\n", name);
		return -1;
	}

	p = calloc(1, sizeof(struct steer_control));
	if (!p) {
		plugin_unload(handle);
		return -1;
	}

	memcpy(p, pp, sizeof(struct steer_control));
	p->handle = handle;
	p->controller = priv;
	pp->controller = priv;
	*sc = p;

	if (p->init)
		p->init(&p->priv);

	info("Registered %s (priv = 0x%p)\n", name, p->priv);
	return 0;
}

static struct steer_control *cntlr_lookup_steer_module(struct controller *c,
						       const char *name)
{
	struct steer_control *sc = NULL;

	list_for_each_entry(sc, &c->sclist, list) {
		if (!strncmp(sc->name, name, strlen(sc->name)))
			return sc;
	}

	return NULL;
}

/* reorder steer plugins as per the order they appear in config */
void cntlr_reorder_steer_modules(struct controller *c)
{
	struct cntlr_plugin_name *e = NULL;

	list_for_each_entry(e, &c->cfg.steerplugins, list) {
		struct steer_control *sc = NULL, *tmp;
		bool found = false;

		list_for_each_entry_safe(sc, tmp, &c->sclist, list) {
			if (!strncmp(sc->name, e->name, strlen(sc->name))) {
				list_del(&sc->list);
				found = true;
				break;
			}
		}
		if (found) {
			info("Adding plugin '%s' to sclist, steer=%p\n", sc->name, sc->steer);
			list_add_tail(&sc->list, &c->sclist);
		}
	}
}

/* Load missing steer plugins based on config */
void cntlr_load_steer_modules(struct controller *c)
{
	struct cntlr_plugin_name *e = NULL;
	struct steer_control *sc = NULL;

	list_for_each_entry(sc, &c->sclist, list) {
		sc->enabled = false;
	}

	list_for_each_entry(e, &c->cfg.steerplugins, list) {
		int ret = 0;

		sc = cntlr_lookup_steer_module(c, e->name);
		if (!sc) {
			info("Loading steer module '%s'\n", e->name);
			ret = cntlr_load_steer_module(c, e->name, &sc);
			if (!ret) {
				list_add_tail(&sc->list, &c->sclist);
			} else {
				warn("Failed to load plugin %s\n", e->name);
				continue;
			}
		} else {
			dbg("Steer module '%s', already loaded\n", e->name);
		}

		sc->enabled = c->cfg.steer.plugin_enabled ? true : false;
		dbg("Steer module '%s' %s in cfg\n", e->name,
		    sc->enabled ? "enabled" : "disabled");
	}
}

int cntlr_configure_steer_module(struct steer_control *sc, void *cfg)
{
	if (sc && sc->config)
		return sc->config(sc->priv, cfg);

	return 0;
}

int cntlr_maybe_steer_sta(struct steer_control *sc, struct steer_sta *s, uint16_t rxcmdu_type)
{
	if (sc && sc->steer)
		return sc->steer(sc->priv, s, rxcmdu_type);

	return 0;
}

int cntlr_notify_steer_module(struct steer_control *sc, struct cmdu_buff *cmdu)
{
	if (sc && sc->cmdu_notifier)
		return sc->cmdu_notifier(sc->priv, cmdu);

	return 0;
}

void cntlr_unload_steer_modules(struct controller *c)
{
	struct steer_control *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp, &c->sclist, list) {
		if (p->exit)
			p->exit(p->priv);

		list_del(&p->list);
		plugin_unload(p->handle);
		free(p);
	}
}

int cntlr_register_steer_module(struct controller *c, const char *name)
{
	struct steer_control *sc;
	int ret;


	if (!name || name[0] == '\0')
		return -1;

	if (cntlr_lookup_steer_module(c, name)) {
		info("Steer module '%s' already registered\n", name);
		return 0;
	}

	ret = cntlr_load_steer_module(c, name, &sc);
	if (!ret) {
		list_add_tail(&sc->list, &c->sclist);
		return 0;
	}

	return -1;
}

int cntlr_unregister_steer_module(struct controller *c, char *name)
{
	struct steer_control *sc;


	if (!name || name[0] == '\0')
		return -1;

	sc = cntlr_lookup_steer_module(c, name);
	if (!sc)
		return -1;

	return cntlr_unload_steer_module(sc);
}

int cntlr_notify_steer_modules(struct controller *c, struct cmdu_buff *cmdu)
{
	struct steer_control *sc = NULL;
	int ret = 0;
	int i;

	list_for_each_entry(sc, &c->sclist, list) {
		for (i = 0; i < sc->num_cmdu; i++) {
			if (cmdu_get_type(cmdu) == sc->cmdulist[i]) {
				ret = cntlr_notify_steer_module(sc, cmdu);
				break;
			}
		}
	}

	return ret;
}
