/*
 * cntlr_plugin.c - Controller plugin helper functions.
 *
 * Copyright (C) 2025 Genexis Sweden AB.
 *
 * Author: anjan.chanda@genexis.eu
 *
 * See LICENSE file for license related information.
 *
 */

#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"

#include "cntlr_plugin.h"


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);
}

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

	snprintf(fname, 127, "%s.so", name);
	ret = plugin_load(CNTLR_PLUGIN_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 cntlr_plugin));
	if (!p) {
		plugin_unload(handle);
		return -1;
	}

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

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

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

static int cntlr_unload_plugin(struct cntlr_plugin *p)
{
	int ret;

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

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

	return !ret ? 0 : -1;
}

struct cntlr_plugin *cntlr_lookup_plugin(struct controller *c, const char *name)
{
	struct cntlr_plugin *p = NULL;

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

	return NULL;
}

int cntlr_register_plugin(struct controller *c, const char *name)
{
	struct cntlr_plugin *p;
	int ret;

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

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

	ret = cntlr_load_plugin(c, name, &p);
	if (!ret) {
		list_add_tail(&p->list, &c->plugins);
		return 0;
	}

	return -1;
}

int cntlr_unregister_plugin(struct controller *c, char *name)
{
	struct cntlr_plugin *p;

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

	p = cntlr_lookup_plugin(c, name);
	if (!p)
		return -1;

	return cntlr_unload_plugin(p);
}

int cntlr_configure_plugin(struct controller *c, struct cntlr_plugin *p, void *cfg)
{
	UNUSED(c);
	if (p && p->config)
		return p->config(p->priv, cfg);

	return 0;
}

int cntlr_notify_plugin(struct controller *c, struct cntlr_plugin *p, struct cmdu_buff *cmdu)
{
	UNUSED(c);
	if (p && p->cmdu_notifier)
		return p->cmdu_notifier(p->priv, cmdu);

	return 0;
}

int cntlr_process_plugin(struct controller *c, struct cntlr_plugin *p, void *data, uint16_t cmdu_type)
{
	UNUSED(c);
	if (p && p->process)
		return p->process(p->priv, data, cmdu_type);

	return 0;
}

/* Load (missing) plugins based on config */
void cntlr_load_plugins(struct controller *c)
{
	struct cntlr_plugin_name *e = NULL;
	struct cntlr_plugin *p = NULL;


	list_for_each_entry(p, &c->plugins, list) {
		p->enabled = false;
	}

	list_for_each_entry(e, &c->cfg.plugins, list) {
		p = cntlr_lookup_plugin(c, e->name);
		if (!p) {
			int ret = 0;

			info("Loading plugin '%s'\n", e->name);
			ret = cntlr_load_plugin(c, e->name, &p);
			if (!ret)
				list_add_tail(&p->list, &c->plugins);
			else {
				warn("Failed to load plugin %s\n", e->name);
				continue;
			}
		} else {
			dbg("Plugin '%s', already loaded\n", e->name);
		}

		p->enabled = true;
	}

	/* unload plugins no longer in config */
	list_for_each_entry(p, &c->plugins, list) {
		if (!p->enabled)
			cntlr_unload_plugin(p);
	}
}

void cntlr_unload_plugins(struct controller *c)
{
	struct cntlr_plugin *p = NULL;

	list_for_each_entry(p, &c->plugins, list) {
		cntlr_unload_plugin(p);
	}
}

int cntlr_notify_plugins(struct controller *c, struct cmdu_buff *cmdu)
{
	struct cntlr_plugin *p = NULL;
	int ret = 0;
	int i;

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

	return ret;
}

int cntlr_process_plugins(struct controller *c, void *data, uint16_t cmdu_type)
{
	struct cntlr_plugin *p = NULL;
	int ret = 0;
	int i;

	list_for_each_entry(p, &c->plugins, list) {
		for (i = 0; i < p->num_cmdu; i++) {
			if (cmdu_type == p->cmdulist[i]) {
				ret = cntlr_process_plugin(c, p, data, cmdu_type);
				break;
			}
		}
	}

	return ret;
}
