// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2023 Robin Jarry

#include "log.h"
#include "module.h"
#include "sort.h"
#include "vec.h"

#include <gr_macro.h>
#include <gr_string.h>

#include <assert.h>
#include <fnmatch.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>

LOG_TYPE("main");

struct module_handlers {
	struct api_handler handlers[UINT_NUM_VALUES(uint16_t)];
};

static struct module_handlers *mod_handlers[UINT_NUM_VALUES(uint16_t)];

void __api_handler(
	uint32_t request_type,
	api_handler_func callback,
	const char *name,
	size_t req_size
) {
	uint16_t mod = (request_type >> 16) & 0xffff;
	uint16_t req = request_type & 0xffff;
	struct module_handlers *mh;

	assert(callback != NULL);
	assert(name != NULL);

	mh = mod_handlers[mod];
	if (mh == NULL) {
		mod_handlers[mod] = mh = calloc(1, sizeof(*mh));
		if (mh == NULL)
			ABORT("calloc(module_handlers)");
	}
	if (mh->handlers[req].callback != NULL)
		ABORT("duplicate api handler type=0x%08x '%s'", request_type, name);

	mh->handlers[req].callback = callback;
	mh->handlers[req].name = name;
	mh->handlers[req].req_size = req_size;
}

const struct api_handler *lookup_api_handler(uint32_t request_type) {
	uint16_t mod = (request_type >> 16) & 0xffff;
	uint16_t req = request_type & 0xffff;
	struct module_handlers *mh = mod_handlers[mod];

	if (mh == NULL || mh->handlers[req].callback == NULL)
		return NULL;

	return &mh->handlers[req];
}

static STAILQ_HEAD(, module) modules = STAILQ_HEAD_INITIALIZER(modules);

void module_register(struct module *mod) {
	struct module *m;

	if (mod->name == NULL)
		ABORT("module with no name: %p", mod);

	STAILQ_FOREACH (m, &modules, next) {
		if (strcmp(mod->name, m->name) == 0)
			ABORT("duplicate module name: '%s'", mod->name);
	}

	STAILQ_INSERT_TAIL(&modules, mod, next);
}

static bool module_is_child(const void *mod, const void *maybe_child) {
	const struct module *c = maybe_child;
	const struct module *m = mod;
	char depends_on[512];

	if (c->depends_on == NULL)
		return false;

	// split on commas
	if (gr_strcpy(depends_on, sizeof(depends_on), c->depends_on) < 0)
		ABORT("depends_on too long: \"%s\"", c->depends_on);

	for (char *dep = strtok(depends_on, ","); dep != NULL; dep = strtok(NULL, ",")) {
		if (fnmatch(dep, m->name, 0) == 0)
			return true;
	}

	return false;
}

void modules_init(struct event_base *ev_base) {
	vec const struct module **mods = NULL;
	const struct module *mod;

	STAILQ_FOREACH (mod, &modules, next)
		vec_add(mods, mod);

	if (mods == NULL)
		ABORT("failed to alloc module array");

	if (topo_sort((vec const void **)mods, module_is_child) < 0)
		ABORT("topo_sort failed: %s", strerror(errno));

	vec_foreach (mod, mods) {
		if (mod->init != NULL) {
			LOG(DEBUG, "'%s' (depends on '%s')", mod->name, mod->depends_on ?: "");
			mod->init(ev_base);
		}
	}

	vec_free(mods);
}

void modules_fini(struct event_base *ev_base) {
	vec const struct module **mods = NULL;
	const struct module *mod;

	STAILQ_FOREACH (mod, &modules, next)
		vec_add(mods, mod);

	if (mods == NULL)
		ABORT("failed to alloc module array");

	if (topo_sort((vec const void **)mods, module_is_child) < 0)
		ABORT("topo_sort failed: %s", strerror(errno));

	// call fini() functions in reverse topological order
	for (int i = vec_len(mods) - 1; i >= 0; i--) {
		mod = mods[i];
		if (mod->fini != NULL) {
			LOG(DEBUG, "'%s' (depends on '%s')", mod->name, mod->depends_on ?: "");
			mod->fini(ev_base);
		}
	}

	vec_free(mods);

	for (unsigned i = 0; i < ARRAY_DIM(mod_handlers); i++) {
		free(mod_handlers[i]);
		mod_handlers[i] = NULL;
	}
}
