/*
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 * See LICENSE file for license related information.
 *
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <1905_tlvs.h>
#include <dlfcn.h>
#include <easymesh.h>
#include <libubox/list.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "agent.h"
#include "cmdu_extension.h"
#include "extension.h"

#define EXTENSIONS_PLUGIN_PATH	"./extensions/example"

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) {
		fprintf(stderr, "%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 load_extensions(int argc, char *argv[], struct list_head *plugins)
{
	struct registered_plugin *p;
	int i;

	for (i = 0; i < argc && argv[i]; i++) {
		char plugin_file[128] = {0};
		struct plugin *pp = NULL;
		void *handle;
		int ret;

		snprintf(plugin_file, 127, "%s.so", argv[i]);
		ret = plugin_load(EXTENSIONS_PLUGIN_PATH, plugin_file, &handle);
		if (ret)
			continue;

		pp = dlsym(handle, argv[i]);
		if (!pp) {
			fprintf(stderr, "Symbol '%s' not found\n", argv[i]);
			continue;
		}

		p = calloc(1, sizeof(struct registered_plugin));
		if (!p) {
			ret = plugin_unload(handle);
			continue;
		}

		p->plugin = pp;
		((struct plugin *)p->plugin)->handle = handle;
		list_add_tail(&p->list, plugins);
		if (((struct plugin *)p->plugin)->init)
			((struct plugin *)p->plugin)->init(&((struct plugin *)p->plugin)->priv, NULL);
	}

	return 0;
}

int unload_extensions(struct list_head *plugins)
{
	struct registered_plugin *r = NULL, *tmp;
	int ret = 0;

	list_for_each_entry_safe(r, tmp, plugins, list) {
		struct plugin *p = r->plugin;

		list_del(&r->list);
		if (p->exit)
			p->exit(p->priv);

		ret |= plugin_unload(p->handle);
		free(r);
	}

	return ret;
}

void cmdu_print_extension_mask(cmdu_extend_mask_t mask)
{
	fprintf(stderr, "\n CMDUs setup for extension\n");
	for (int i = 0; i < 128; i++) {
		if (mask[i].loc == 0)
			continue;

		fprintf(stderr, "[%d] cmdu = 0x%04x, mask = %02x\n",
			i, mask[i].type, mask[i].loc);
	}

	fprintf(stderr, "\n");
}

#define _cmdu_set_extension_mask(m, f, p)		\
do {							\
	if (f >= 0x8000 && f <= MAP_CMDU_TYPE_MAX) {	\
		int idx = 32 + (f - 0x8000);		\
		m[idx].type = (f);			\
		m[idx].loc |= (p);			\
	} else if (f < CMDU_TYPE_1905_END) {		\
		int idx = (f);				\
		m[idx].type = (f);			\
		m[idx].loc |= (p);			\
	}						\
} while(0)

int cmdu_set_extension_mask(void *plugin, ...)
{
	cmdu_extend_mask_t mask = {0};
	struct plugin *p = plugin;
	va_list args;

	va_start(args, plugin);
	do {
		unsigned int l;
		uint16_t v;

		v = (uint16_t)va_arg(args, int);
		if (v > MAP_CMDU_TYPE_MAX )
			break;

		l = (unsigned int)va_arg(args, int);
		_cmdu_set_extension_mask(mask, v, l);
	} while(1);

	va_end(args);

	memset(p->mask, 0, sizeof(cmdu_extend_mask_t));
	memcpy(p->mask, mask, sizeof(cmdu_extend_mask_t));

	return 0;
}

static int cmdu_process_hook(void *app, uint16_t type, uint16_t mid, uint8_t *src,
			     void *cmdu, size_t cmdulen, int hook)
{
	struct registered_plugin *r = NULL;
	struct agent *a = app;
	int ret = CMDU_OK;

	if (!a || !cmdu || cmdulen == 0)
		return 0;

	if (list_empty(&a->extlist))
		return 0;

	list_for_each_entry(r, &a->extlist, list) {
		struct plugin *p = r->plugin;

		if (!p)
			continue;

		if ((hook == CMDU_RX && cmdu_rx_mask_isset(p->mask, type)) ||
		    (hook == CMDU_PRE_TX  && cmdu_pre_tx_mask_isset(p->mask, type))) {
			if (p->process_cmdu)
				ret = p->process_cmdu(p->priv, src, cmdu, cmdulen);

			if (ret == CMDU_DONE)
				return CMDU_OK;

			if (ret == CMDU_DROP)
				return CMDU_DROP;
		}
	}

	return ret;
}

int CMDU_HOOK(void *app, uint16_t type, uint16_t mid, uint8_t *src, void *cmdu,
	      size_t cmdulen, int hook)
{
	return cmdu_process_hook(app, type, mid, src, cmdu, cmdulen, hook);
}

int map_send_cmdu(void *cookie, uint8_t *dst, uint8_t *src, uint16_t type,
		  uint16_t *mid, uint8_t *data, size_t len, uint16_t tag)
{
	return 0;
	//TODO:
	//return ieee1905_send_cmdu(cookie, dst, src, type, mid, data, len, tag);
}
