#include "module.hpp"

#include "../include/dv-sdk/cross/portable_io.h"
#include "../include/dv-sdk/cross/portable_threads.h"

#include "main.hpp"

#include <boost/core/demangle.hpp>

#include <algorithm>
#include <chrono>
#include <regex>
#include <typeinfo>

#if defined(OS_LINUX)
#	include <pthread.h>
#	include <time.h>
#endif

#if defined(OS_MACOS)
#	include <mach/mach_init.h>
#	include <mach/mach_types.h>
#	include <mach/thread_act.h>
#endif

#if defined(OS_WINDOWS)
#	define WIN32_LEAN_AND_MEAN
#	include <windows.h>
#endif

#if defined(ENABLE_HEAP_PROFILER)
#	include <gperftools/malloc_extension.h>
#elif defined(OS_LINUX)
#	include <malloc.h>
#endif

static const std::regex inputConnRegex("^([a-zA-Z-_\\d\\.]+)\\[([a-zA-Z-_\\d\\.]+)\\]$");
static const std::regex nameRegex("^[a-zA-Z-_\\d\\.]+$");

dv::Module::Module(std::string_view name_, std::string_view library_) :
	name(name_),
	moduleConfigNode(dv::Cfg::GLOBAL.getNode("/mainloop/" + name + "/")),
	connectedInputs(0),
	threadAlive(false),
	statRateLimiter(1, 500),
	cycleStat(moduleConfigNode, "cycleTime") {
	// Switch to system logger.
	dv::LoggerInternal::Set(nullptr);

	if (!std::regex_match(name, nameRegex)) {
		const auto exMsg = fmt::format(FMT_STRING("{:s}: invalid name."), name);
		dv::Log(dv::logLevel::ERROR, exMsg);
		throw std::runtime_error(exMsg);
	}

	// Load library to get module functions.
	try {
		auto lib = dv::ModulesLoadLibrary(library_);
		library  = std::move(lib.library);
		info     = std::move(lib.info);
	}
	catch (const std::exception &ex) {
		const auto exMsg = fmt::format(FMT_STRING("{:s}: module library load failed, exception '{:s} :: {:s}'."), name,
			boost::core::demangle(typeid(ex).name()), ex.what());
		dv::Log(dv::logLevel::ERROR, exMsg);
		throw std::runtime_error(exMsg);
	}

	// moduleRun() cannot be NULL!
	if (info->functions->moduleRun == nullptr) {
		const auto exMsg = fmt::format(FMT_STRING("{:s}: moduleRun() function is not set, this is not allowed."), name);
		dv::Log(dv::logLevel::ERROR, exMsg);

		// Cleanup on constructor exception.
		dv::ModulesUnloadLibrary(library);

		throw std::runtime_error(exMsg);
	}

	// Set configuration node (so it's user accessible).
	moduleNode = static_cast<dvConfigNode>(moduleConfigNode);

	// State allocated later by init().
	moduleState = nullptr;

	// Ensure the library is stored for successive startups.
	moduleConfigNode.create<dv::CfgType::STRING>(
		"moduleLibrary", std::string(library_), {1, PATH_MAX}, dv::CfgFlags::READ_ONLY, "Module library.");

	// Initialize logging related functionality.
	LoggingInit();

	// Initialize running related functionality.
	RunningInit();

	// Ensure static configuration is created on each module initialization.
	StaticInit();

	dv::Log(dv::logLevel::DEBUG, "Module initialized.");
}

void dv::Module::startThread() {
	// Start module thread.
	threadAlive = true;
	thread      = std::thread(&dv::Module::runThread, this);
}

void dv::Module::stopThread() {
	// Stop module thread and wait for it to exit.
	threadAlive = false;
	run.cond.notify_all();
	thread.join();
}

dv::Module::~Module() {
	// Switch to system logger.
	dv::LoggerInternal::Set(nullptr);

	// Check module is properly shut down, which takes care of
	// cleaning up all input connections. This should always be
	// the case as it's a requirement for calling removeModule().
	if (run.isRunning || run.running) {
		dv::Log(dv::logLevel::ERROR, "Destroying a running module. This should never happen!");
	}

	// Cleanup configuration and types.
	registerInput("ALL", "REMOVE");
	registerOutput("ALL", "REMOVE");

	if (connectedInputs != 0) {
		dv::Log(dv::logLevel::ERROR, "Connected inputs remain. This should never happen!");
	}

	moduleConfigNode.removeNode();

	MainData::getGlobal().typeSystem.unregisterModuleTypes(this);

	dv::Log(dv::logLevel::DEBUG, "Module destroyed.");

	// Last, unload the shared library plugin.
	dv::ModulesUnloadLibrary(library);
}

void dv::Module::LoggingInit() {
	// Per-module custom log string prefix.
	logger.logPrefix = name;

	// Per-module log level support. Initialize with global log level value.
	moduleConfigNode.create<dv::CfgType::STRING>("logLevel", dv::LoggerInternal::logLevelEnumToName(dv::logLevel::INFO),
		{0, INT32_MAX}, dv::CfgFlags::NORMAL, "Module-specific log-level.");
	moduleConfigNode.attributeModifierListOptions("logLevel", dv::LoggerInternal::logLevelNamesCommaList(), false);

	// Update log-level to new names.
	const auto &oldLogLevel = moduleConfigNode.getString("logLevel");

	if ((oldLogLevel == "EMERGENCY") || (oldLogLevel == "ALERT") || (oldLogLevel == "CRITICAL")) {
		moduleConfigNode.putString("logLevel", "ERROR");
	}
	else if (oldLogLevel == "NOTICE") {
		moduleConfigNode.putString("logLevel", "INFO");
	}

	moduleConfigNode.addAttributeListener(&logger.logLevel, &moduleLogLevelListener);
	logger.logLevel = dv::LoggerInternal::logLevelNameToEnum(moduleConfigNode.get<dv::CfgType::STRING>("logLevel"));
}

void dv::Module::RunningInit() {
	// Initialize shutdown controls. By default modules are OFF.
	moduleConfigNode.create<dv::CfgType::BOOL>("running", false, {}, dv::CfgFlags::NORMAL, "Module start/stop.");

	moduleConfigNode.create<dv::CfgType::BOOL>(
		"isRunning", false, {}, dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Module running state.");

	moduleConfigNode.addAttributeListener(&run, &moduleRunningListener);
	run.running = moduleConfigNode.get<dv::CfgType::BOOL>("running");

	run.forcedShutdown = false;

	run.isRunning = false;
	moduleConfigNode.updateReadOnly<dv::CfgType::BOOL>("isRunning", false);
}

void dv::Module::StaticInit() {
	moduleConfigNode.addAttributeListener(&run.configUpdate, &moduleConfigUpdateListener);

	// Call module's staticInit function to create default static config.
	if (info->functions->moduleStaticInit != nullptr) {
		try {
			if (!info->functions->moduleStaticInit(this)) {
				throw std::runtime_error("Failed static module initialization");
			}
		}
		catch (const std::exception &ex) {
			const auto exMsg = fmt::format(FMT_STRING("moduleStaticInit(): failed, exception '{:s} :: {:s}'."),
				boost::core::demangle(typeid(ex).name()), ex.what());
			dv::Log(dv::logLevel::ERROR, exMsg);

			// Cleanup on constructor exception.
			registerInput("ALL", "REMOVE");
			registerOutput("ALL", "REMOVE");

			MainData::getGlobal().typeSystem.unregisterModuleTypes(this);

			dv::ModulesUnloadLibrary(library);

			throw std::runtime_error(exMsg);
		}
	}

	// Each module has a GUI support attribute which allows GUIs to dump their per-module
	// options in a string in the config tree.
	moduleConfigNode.attributeModifierGUISupport();

	// Each module can set priority attributes for UI display. By default let's show 'running'.
	// Called last to allow for configInit() function to create a different default first.
	moduleConfigNode.attributeModifierPriorityAttributes("running");

	// display the CPU usage and CPU time per thread
	moduleConfigNode.create<dv::CfgType::FLOAT>(
		"cpuUsage", 0.0, {0.0, 100.0}, dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "CPU thread usage");
	moduleConfigNode.create<dv::CfgType::LONG>(
		"cpuTime", 0, {0, INT64_MAX}, dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "CPU Time elapsed");

	cpuUsageStat.oldThreadTime = cpuUsageStat.oldCpuTime = 0;
	cpuTimeStat.startTime                                = getThreadCPUTime();
}

void dv::Module::registerType(const dv::Types::Type type) {
	MainData::getGlobal().typeSystem.registerModuleType(this, type);
}

void dv::Module::registerInput(std::string_view inputName, std::string_view typeName, bool optional) {
	std::string inputNameString(inputName);

	if (!std::regex_match(inputNameString, nameRegex)) {
		throw std::invalid_argument(
			fmt::format(FMT_STRING("Input name '{:s}' contains invalid characters."), inputNameString));
	}

	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	if (typeName == "REMOVE") {
		if (inputNameString == "ALL") {
			for (auto &in : inputs) {
				moduleConfigNode.getRelativeNode("inputs/" + in.first + "/").removeNode();
			}

			inputs.clear();
		}
		else {
			if (inputs.count(inputNameString) == 0) {
				throw std::invalid_argument(
					fmt::format(FMT_STRING("Input with name '{:s}' doesn't exists."), inputNameString));
			}

			moduleConfigNode.getRelativeNode("inputs/" + inputNameString + "/").removeNode();

			inputs.erase(inputNameString);
		}

		return;
	}

	if (inputs.count(inputNameString)) {
		throw std::invalid_argument(fmt::format(FMT_STRING("Input with name '{:s}' already exists."), inputNameString));
	}

	auto typeInfo = MainData::getGlobal().typeSystem.getTypeInfo(typeName, this);

	// Add info to internal data structure.
	auto inputNode = moduleConfigNode.getRelativeNode("inputs/" + inputNameString + "/");

	inputs.try_emplace(inputNameString, typeInfo, optional, this, inputNode);

	// Add info to ConfigTree.
	inputNode.create<dv::CfgType::BOOL>("optional", optional, {}, dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT,
		"Module can run without this input being connected.");
	inputNode.create<dv::CfgType::STRING>("typeIdentifier", typeInfo.identifier, {4, 4},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Type identifier.");
	inputNode.create<dv::CfgType::STRING>("typeDescription", typeInfo.description, {1, 200},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Type description.");

	// Add input statistics.
	inputNode.create<dv::CfgType::INT>("queueCapacity", INTER_MODULE_TRANSFER_QUEUE_SIZE, {0, INT32_MAX},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Maximum capacity of this input's data queue.");
	inputNode.create<dv::CfgType::INT>("queueLength", 0, {0, INTER_MODULE_TRANSFER_QUEUE_SIZE},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Current length of this input's data queue.");
	inputNode.create<dv::CfgType::LONG>("readPacketsNumber", 0, {0, INT64_MAX},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Number of packets read from input.");
	inputNode.create<dv::CfgType::LONG>("readPacketsElements", 0, {0, INT64_MAX},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Number of elements in packets read from input.");

	// Add connectivity configuration attribute.
	inputNode.addAttributeListener(this, &moduleFromChangeListener);
	inputNode.create<dv::CfgType::STRING>(
		"from", "", {0, 256}, dv::CfgFlags::NORMAL, "From which 'moduleName[outputName]' to get data.");

	dv::Log(dv::logLevel::DEBUG, "Input '{:s}' registered with type '{:s}' (optional={}).", inputNameString,
		typeInfo.identifier, optional);
}

std::pair<std::string, std::string> dv::Module::parseFromString(const std::string &from) {
	// Not empty, so check syntax and then components.
	std::smatch inputConnComponents;
	if (!std::regex_match(from, inputConnComponents, inputConnRegex)) {
		throw std::invalid_argument(fmt::format(FMT_STRING("Invalid format of connectivity attribute '{:s}'."), from));
	}

	auto moduleName = inputConnComponents.str(1);
	auto outputName = inputConnComponents.str(2);

	return (std::make_pair(moduleName, outputName));
}

void dv::Module::inputConnect(const std::string &input, const std::string &from) {
	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	if (inputs.count(input) == 0) {
		throw std::invalid_argument(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exists."), input));
	}

	auto in = &inputs.at(input);

	// Check basic syntax: either empty or 'x[y]'.
	if (from.empty()) {
		// Nothing to connect if empty.
		return;
	}

	// Not empty, so check syntax and then components.
	std::string moduleName, outputName;

	try {
		std::tie(moduleName, outputName) = parseFromString(from);
	}
	catch (const std::exception &ex) {
		dv::Log(dv::logLevel::ERROR, "Input '{:s}': {:s}", input, ex.what());

		return;
	}

	// Does the referenced module exist?
	auto otherModule = getModule(moduleName);
	if (otherModule == nullptr) {
		dv::Log(dv::logLevel::DEBUG, "Input '{:s}': invalid connectivity attribute, module '{:s}' does not exist.",
			input, moduleName);

		return;
	}

	// Does it have the specified output?
	auto moduleOutput = otherModule->getModuleOutput(outputName);
	if (moduleOutput == nullptr) {
		dv::Log(dv::logLevel::DEBUG,
			"Input '{:s}': invalid connectivity attribute, output '{:s}' does not exist in module '{:s}'.", input,
			outputName, moduleName);

		return;
	}

	// Then, check the type.
	// If the expected type is ANYT, we accept anything.
	if ((in->type.id != dv::Types::anyId) && (in->type.id != moduleOutput->type.id)) {
		dv::Log(dv::logLevel::ERROR,
			"Input '{:s}': invalid connectivity attribute, output '{:s}' in module '{:s}' has type "
			"'{:s}', but this input requires type '{:s}'.",
			input, outputName, moduleName, moduleOutput->type.identifier, in->type.identifier);

		return;
	}

	// All is well, let's connect to that output.
	moduleOutput->destinations.emplace_back(in, &dataAvailable);

	// And we're done.
	in->linkedOutput = moduleOutput;

	connectedInputs++;

	dv::Log(dv::logLevel::DEBUG, "Connected input {:s} to module {:s}, output {:s}, connected {:d}.", input, moduleName,
		outputName, connectedInputs.load());
}

void dv::Module::inputDisconnect(const std::string &input) {
	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	if (inputs.count(input) == 0) {
		throw std::invalid_argument(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exists."), input));
	}

	auto in = &inputs.at(input);

	if (in->linkedOutput == nullptr) {
		// Input not connected. Nothing to do.
		return;
	}

	dv::Log(dv::logLevel::DEBUG, "Disconnected input {:s} to module {:s}, output {:s}, connected {:d}.", input,
		in->linkedOutput->parentModule->name, in->linkedOutput->node.getName(), connectedInputs.load());

	// Disconnect input from output.
	OutConnection dataConn{in, nullptr};

	vectorRemove(in->linkedOutput->destinations, dataConn);

	in->linkedOutput = nullptr;

	connectedInputs--;
}

void dv::Module::registerOutput(std::string_view outputName, std::string_view typeName) {
	std::string outputNameString(outputName);

	if (!std::regex_match(outputNameString, nameRegex)) {
		throw std::invalid_argument(
			fmt::format(FMT_STRING("Input name '{:s}' contains invalid characters."), outputNameString));
	}

	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	if (typeName == "REMOVE") {
		if (outputNameString == "ALL") {
			for (auto &out : outputs) {
				outputDisconnect(out.first);
				moduleConfigNode.getRelativeNode("outputs/" + out.first + "/").removeNode();
			}

			outputs.clear();
		}
		else {
			if (outputs.count(outputNameString) == 0) {
				throw std::invalid_argument(
					fmt::format(FMT_STRING("Output with name '{:s}' doesn't exists."), outputNameString));
			}

			outputDisconnect(outputNameString);
			moduleConfigNode.getRelativeNode("outputs/" + outputNameString + "/").removeNode();

			outputs.erase(outputNameString);
		}

		return;
	}

	if (outputs.count(outputNameString)) {
		throw std::invalid_argument(
			fmt::format(FMT_STRING("Output with name '{:s}' already exists."), outputNameString));
	}

	dv::Types::Type typeInfo;

	if (typeName.substr(0, 6) == "INPUT_") {
		typeInfo = MainData::getGlobal().typeSystem.getTypeInfo(typeName.substr(6, 4), nullptr);
	}
	else {
		typeInfo = MainData::getGlobal().typeSystem.getTypeInfo(typeName, this);
	}

	// Add info to internal data structure.
	auto outputNode = moduleConfigNode.getRelativeNode("outputs/" + outputNameString + "/");

	outputs.try_emplace(outputNameString, typeInfo, this, outputNode);

	// Add info to ConfigTree.
	outputNode.create<dv::CfgType::STRING>("typeIdentifier", typeInfo.identifier, {4, 4},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Type identifier.");
	outputNode.create<dv::CfgType::STRING>("typeDescription", typeInfo.description, {1, 200},
		dv::CfgFlags::READ_ONLY | dv::CfgFlags::NO_EXPORT, "Type description.");

	// Link any inputs that now are valid.
	outputConnect(outputNameString);

	dv::Log(dv::logLevel::DEBUG, "Output '{:s}' registered with type '{:s}'.", outputNameString, typeInfo.identifier);
}

void dv::Module::inputsRegenerate(const std::string &moduleName, const std::string &outputName) {
	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	// Go thorugh all unconnected inputs and stimulate them to try a connection.
	for (auto &mod : dv::MainData::getGlobal().modules) {
		for (auto &in : mod.second->inputs) {
			if (in.second.linkedOutput != nullptr) {
				// Connected already to something, skip.
				continue;
			}

			// Try connecting ...
			auto from = in.second.node.getString("from");
			std::string module, output;

			try {
				std::tie(module, output) = parseFromString(from);
			}
			catch (const std::exception &) {
				continue;
			}

			if ((moduleName == "ALL") || (moduleName == module && outputName == output)) {
				mod.second->inputConnect(in.first, from);
			}
		}
	}
}

void dv::Module::outputConnect(const std::string &output) {
	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	if (outputs.count(output) == 0) {
		throw std::invalid_argument(fmt::format(FMT_STRING("Output with name '{:s}' doesn't exists."), output));
	}

	// Go thorugh all unconnected inputs and stimulate them to try a connection.
	inputsRegenerate(name, output);
}

void dv::Module::outputDisconnect(const std::string &output) {
	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	if (outputs.count(output) == 0) {
		throw std::invalid_argument(fmt::format(FMT_STRING("Output with name '{:s}' doesn't exists."), output));
	}

	auto out = &outputs.at(output);

	for (const auto &dest : out->destinations) {
		dest.linkedInput->linkedOutput = nullptr;

		dest.linkedInput->parentModule->connectedInputs--;
	}

	out->destinations.clear();
}

dv::Module *dv::Module::getModule(const std::string &moduleName) {
	try {
		return (MainData::getGlobal().modules.at(moduleName).get());
	}
	catch (const std::out_of_range &) {
		// Fine, not found.
		return (nullptr);
	}
}

dv::ModuleOutput *dv::Module::getModuleOutput(const std::string &outputName) {
	try {
		return (&outputs.at(outputName));
	}
	catch (const std::out_of_range &) {
		// Fine, not found.
		return (nullptr);
	}
}

dv::ModuleInput *dv::Module::getModuleInput(const std::string &outputName) {
	try {
		return (&inputs.at(outputName));
	}
	catch (const std::out_of_range &) {
		// Fine, not found.
		return (nullptr);
	}
}

void dv::Module::inputConnectivityInitialize() {
	for (auto &input : inputs) {
		// 'from' change listener already connected what can be connected.
		// So all non-optional inputs must be connected already.
		if (input.second.linkedOutput == nullptr) {
			// Try to stimulate connectivity between all I/Os.
			// May fix error on next retry.
			inputsRegenerate("ALL", "");

			// Check again.
			if (input.second.linkedOutput == nullptr) {
				if (input.second.optional) {
					// Optional can be unconnected.
					input.second.enabled = false;
					continue;
				}
				else {
					// Not optional, must be connected!
					throw std::runtime_error(fmt::format(
						FMT_STRING("Input '{:s}': input is not optional, its connectivity attribute can not be "
								   "left empty or invalid."),
						input.first));
				}
			}
		}

		// Ensure the upstream module is running.
		if (!input.second.linkedOutput->parentModule->run.isRunning) {
			throw std::runtime_error(
				fmt::format(FMT_STRING("Input '{:s}': required module '{:s}' is not running. Please start it first!"),
					input.first, input.second.linkedOutput->parentModule->name));
		}

		// Enable input.
		auto &dest = input.second.linkedOutput->destinations;

		OutConnection dataConn{&input.second, nullptr};
		auto result = std::find(dest.begin(), dest.end(), dataConn);

		if (result == dest.end()) {
			throw std::domain_error(
				fmt::format(FMT_STRING("Input '{:s}': connection not found, this should never happen."), input.first));
		}

		result->enable(&input.second.queue);
		input.second.enabled = true;
	}
}

void dv::Module::inputConnectivityDisconnect() {
	// Disconnect from all inputs.
	for (auto &input : inputs) {
		if (input.second.linkedOutput != nullptr) {
			// Disable input.
			auto &dest = input.second.linkedOutput->destinations;

			OutConnection dataConn{&input.second, nullptr};
			auto result = std::find(dest.begin(), dest.end(), dataConn);

			if (result == dest.end()) {
				throw std::domain_error(fmt::format(
					FMT_STRING("Input '{:s}': connection not found, this should never happen."), input.first));
			}

			result->disable();
		}
	}
}

void dv::Module::inputConnectivityCleanup() {
	std::unique_lock lock(dataAvailable.lock);

	// Cleanup data left over in inputs (after disconnection).
	for (auto &input : inputs) {
		// Empty queue of any remaining data elements.
		while (input.second.queue.read_available() > 0) {
			input.second.queue.pop();
			dataAvailable.count--;
		}

		// Empty per-input tracker of live memory of remaining data.
		input.second.inUsePackets.clear();
		input.second.currentPublishedPacket.reset();

		// Reset statistics.
		input.second.statPacketsSize   = 0;
		input.second.statPacketsNumber = 0;

		input.second.node.updateReadOnly<dv::CfgType::INT>("queueLength", 0);
		input.second.node.updateReadOnly<dv::CfgType::LONG>("readPacketsNumber", 0);
		input.second.node.updateReadOnly<dv::CfgType::LONG>("readPacketsElements", 0);
	}

	if (dataAvailable.count != 0) {
		dv::Log(
			dv::logLevel::ERROR, "Inputs Cleanup: available data count out of sync ({:d} != 0).", dataAvailable.count);
	}
}

/**
 * Check that each output's info node has been populated with
 * at least one informative attribute, so that downstream modules
 * can find out relevant information about the output.
 *
 * Throws std::domain_error if any problem is detected.
 */
void dv::Module::verifyOutputInfoNodes() {
	for (const auto &out : outputs) {
		if (out.second.infoNode.getAttributeKeys().empty()) {
			throw std::domain_error(
				fmt::format(FMT_STRING("Output '{:s}' has no informative attributes in info node."), out.first));
		}
	}
}

/**
 * Remove all attributes and children of the informative
 * output nodes, leaving them empty.
 * This cleanup should happen on module initialization failure
 * and on module exit, to ensure no stale attributes are kept.
 */
void dv::Module::cleanupOutputInfoNodes() {
	for (auto &out : outputs) {
		out.second.infoNode.clearSubTree(true);
		out.second.infoNode.removeSubTree();
	}
}

void dv::Module::shutdownProcedure(bool doModuleExit, bool disableModule) {
	// Disconnect from other modules first.
	inputConnectivityDisconnect();

	// Run exit().
	if (doModuleExit && info->functions->moduleExit != nullptr) {
		try {
			dv::LoggerInternal::Set(&logger);
			info->functions->moduleExit(this);
			dv::LoggerInternal::Set(nullptr);
		}
		catch (const std::exception &ex) {
			dv::LoggerInternal::Set(nullptr);

			dv::Log(dv::logLevel::ERROR, "moduleExit(): '{:s} :: {:s}', disabling module.",
				boost::core::demangle(typeid(ex).name()), ex.what());
			disableModule = true;
		}
	}

	// Free state memory.
	if (info->memSize != 0) {
		// Only deallocate if we were the original allocator.
		free(moduleState);
	}
	moduleState = nullptr;

	// Remove left-over data elements.
	inputConnectivityCleanup();

	// Cleanup output info nodes, if any exist.
	cleanupOutputInfoNodes();

	// This module has shut down, thus all its direct downstream modules
	// should have shut down too, and no outputs should remain active.
	for (auto &output : outputs) {
		for (const auto &dest : output.second.destinations) {
			if (dest.isValid()) {
				dv::Log(dv::logLevel::ERROR,
					"Output '{:s}': active links still existing on shutdown. This should never happen!", output.first);
			}
		}

		// Remove last data object, might not have been committed yet.
		output.second.nextPacket.reset();
	}

	// Reset CPU usage and time.
	moduleConfigNode.updateReadOnly<dv::CfgType::FLOAT>("cpuUsage", 0.0f);

	// Update cpuTimeStat during shutdown
	if (!cpuTimeStat.blocking) {
		cpuTimeStat.stopTime = getThreadCPUTime();
		cpuTimeStat.diffTime = cpuTimeStat.stopTime - cpuTimeStat.startTime;
		cpuTimeStat.blocking = true;
	}
	moduleConfigNode.updateReadOnly<dv::CfgType::LONG>("cpuTime", static_cast<int64_t>(cpuTimeStat.diffTime));

	// If we cannot recover from whatever caused the shutdown,
	// we force-disable the module and let the user take action.
	if (disableModule) {
		moduleConfigNode.put<dv::CfgType::BOOL>("running", false);
	}
	else {
		run.runDelay = true;
	}
}

void dv::Module::forcedShutdown(bool shutdown) {
	{
		std::unique_lock lock(run.lock);

		run.forcedShutdown = shutdown;
	}

	run.cond.notify_all();
}

void dv::Module::runThread() {
	// Switch to system logger.
	dv::LoggerInternal::Set(nullptr);

	// Set thread name.
	portable_thread_set_name(logger.logPrefix.c_str());

	dv::Log(dv::logLevel::DEBUG, "Module thread running.");

	// Run state machine as long as module is running.
	while (threadAlive.load(std::memory_order_relaxed)) {
		runStateMachine();
	}

	dv::Log(dv::logLevel::DEBUG, "Module thread stopped.");
}

uint64_t dv::Module::getThreadCPUTime() {
#if defined(OS_LINUX)
	struct timespec currTime;

	// Get thread id and time
	clockid_t threadClockId;
	pthread_getcpuclockid(pthread_self(), &threadClockId);
	clock_gettime(threadClockId, &currTime);

	return static_cast<uint64_t>(currTime.tv_sec * 1000000000LL + currTime.tv_nsec);
#endif
#if defined(OS_WINDOWS)
	FILETIME lpCreationTime;
	FILETIME lpExitTime;
	FILETIME lpKernelTime;
	FILETIME lpUserTime;

	GetThreadTimes(GetCurrentThread(), &lpCreationTime, &lpExitTime, &lpKernelTime, &lpUserTime);

	uint64_t tThreadKernel = (static_cast<uint64_t>(lpKernelTime.dwHighDateTime) << 32) + lpKernelTime.dwLowDateTime;
	uint64_t tThreadUser   = (static_cast<uint64_t>(lpUserTime.dwHighDateTime) << 32) + lpUserTime.dwLowDateTime;

	return tThreadKernel + tThreadUser;

#endif

#if defined(OS_MACOS)
	mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
	thread_basic_info_data_t machThreadInfo;

	if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, reinterpret_cast<thread_info_t>(&machThreadInfo), &count)
		== KERN_SUCCESS) {
		return static_cast<uint64_t>(machThreadInfo.user_time.seconds) * static_cast<uint64_t>(1e6)
			 + static_cast<uint64_t>(machThreadInfo.user_time.microseconds)
			 + static_cast<uint64_t>(machThreadInfo.system_time.seconds) * static_cast<uint64_t>(1e6)
			 + static_cast<uint64_t>(machThreadInfo.system_time.microseconds);
	}
#endif
}

float dv::Module::cpuUsageThread() {
	float cpuUsagePercent = 0.0f;

	// Get Thread Time
	cpuTimeStat.stopTime = getThreadCPUTime();
	cpuTimeStat.diffTime = cpuTimeStat.stopTime - cpuTimeStat.startTime;
	uint64_t threadTime  = cpuTimeStat.stopTime;

#if defined(OS_LINUX)
	struct timespec currTime;

	// get current processor time
	clock_gettime(CLOCK_MONOTONIC, &currTime);

	uint64_t cpuTime = static_cast<uint64_t>(currTime.tv_sec * 1000000000LL + currTime.tv_nsec);

	uint64_t diffThreadTime = threadTime - cpuUsageStat.oldThreadTime;
	uint64_t diffCpuTime    = cpuTime - cpuUsageStat.oldCpuTime;

	// save the current value for the next cycle
	cpuUsageStat.oldThreadTime = threadTime;
	cpuUsageStat.oldCpuTime    = cpuTime;

	cpuUsagePercent = ((100.0f * static_cast<float>(diffThreadTime)) / static_cast<float>(diffCpuTime));
#endif

#if defined(OS_WINDOWS)
	FILETIME lpIdleTimeSystem;
	FILETIME lpKernelTimeSystem;
	FILETIME lpUserTimeSystem;

	// this is the current system time, it is needed to calculate time system time
	// at start up and then make the difference
	GetSystemTimes(&lpIdleTimeSystem, &lpKernelTimeSystem, &lpUserTimeSystem);

	uint64_t tSysKernel
		= (static_cast<uint64_t>(lpKernelTimeSystem.dwHighDateTime) << 32) + lpKernelTimeSystem.dwLowDateTime;
	uint64_t tSysUser = (static_cast<uint64_t>(lpUserTimeSystem.dwHighDateTime) << 32) + lpUserTimeSystem.dwLowDateTime;
	uint64_t cpuTime  = tSysKernel + tSysUser;

	uint64_t diffThreadTime = threadTime - cpuUsageStat.oldThreadTime;
	uint64_t diffCpuTime    = cpuTime - cpuUsageStat.oldCpuTime;

	cpuUsageStat.oldThreadTime = threadTime;
	cpuUsageStat.oldCpuTime    = cpuTime;

	cpuUsagePercent = ((100.0f * static_cast<float>(diffThreadTime)) / static_cast<float>(diffCpuTime));
#endif

#if defined(OS_MACOS)
	mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
	thread_basic_info_data_t machThreadInfo;

	if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, reinterpret_cast<thread_info_t>(&machThreadInfo), &count)
		== KERN_SUCCESS) {
		cpuUsagePercent
			= ((100.0f * static_cast<float>(machThreadInfo.cpu_usage)) / static_cast<float>(TH_USAGE_SCALE));
	}
#endif

	return (cpuUsagePercent);
}

void dv::Module::runStateMachine() {
	// Rate-limit retries to once per 50ms.
	if (run.runDelay) {
		std::this_thread::sleep_for(std::chrono::milliseconds(50));

		run.runDelay = false;
	}

	bool shouldRun = false;

	{
		std::unique_lock lock(run.lock);

		run.cond.wait(lock, [this]() {
			if (!threadAlive.load(std::memory_order_relaxed)) {
				return (true); // Stop waiting on thread exit.
			}

			return (run.running || run.isRunning.load(std::memory_order_relaxed));
		});

		shouldRun = (run.running && !run.forcedShutdown);
	}

	if (run.isRunning.load(std::memory_order_relaxed) && shouldRun) {
		// CPU usage percentage and timing calculation
		if (statRateLimiter.pass()) {
			moduleConfigNode.updateReadOnly<dv::CfgType::FLOAT>("cpuUsage", cpuUsageThread());
			moduleConfigNode.updateReadOnly<dv::CfgType::LONG>("cpuTime", static_cast<int64_t>(cpuTimeStat.diffTime));
		}

		if (run.configUpdate.load(std::memory_order_relaxed)) {
			run.configUpdate = false;

			if (info->functions->moduleConfig != nullptr) {
				// Call config function. 'configUpdate' variable reset is done above.
				try {
					dv::LoggerInternal::Set(&logger);
					info->functions->moduleConfig(this);
					dv::LoggerInternal::Set(nullptr);
				}
				catch (const std::exception &ex) {
					dv::LoggerInternal::Set(nullptr);

					dv::Log(dv::logLevel::ERROR, "moduleConfig(): '{:s} :: {:s}', disabling module.",
						boost::core::demangle(typeid(ex).name()), ex.what());

					moduleConfigNode.put<dv::CfgType::BOOL>("running", false);
					return;
				}
			}
		}

		// Only run if there is data. On timeout with no data, do nothing.
		// If is an input generation module (no inputs defined at all), always run.
		if (connectedInputs > 0) {
			std::unique_lock lock(dataAvailable.lock);

			if (!dataAvailable.cond.wait_for(lock, std::chrono::seconds(1), [this]() {
					return (dataAvailable.count > 0);
				})) {
				return;
			}
		}

		try {
			dv::LoggerInternal::Set(&logger);
			cycleStat.start();
			info->functions->moduleRun(this);
			cycleStat.finish();

			dv::LoggerInternal::Set(nullptr);
		}
		catch (const std::exception &ex) {
			dv::LoggerInternal::Set(nullptr);

			dv::Log(dv::logLevel::ERROR, "moduleRun(): '{:s} :: {:s}', disabling module.",
				boost::core::demangle(typeid(ex).name()), ex.what());

			moduleConfigNode.put<dv::CfgType::BOOL>("running", false);
			return;
		}
	}
	else if (!run.isRunning.load(std::memory_order_relaxed) && shouldRun) {
		std::vector<std::string> modulesToWaitFor;

		{
			// Serialize module start/stop globally.
			std::unique_lock lock(MainData::getGlobal().modulesLock, std::try_to_lock);

			if (!lock.owns_lock()) {
				// Avoid possible deadlock if external client is trying
				// to shut down this module at the same time.
				return;
			}

			// Verify that we still want to run after acquiring the lock.
			if (!run.running || run.forcedShutdown) {
				return;
			}

			// Allocate memory for module state.
			if (info->memSize != 0) {
				moduleState = calloc(1, info->memSize);
				if (moduleState == nullptr) {
					dv::Log(dv::logLevel::ERROR, "moduleInit(): 'memory allocation failure', disabling module.");

					shutdownProcedure(false, true);
					return;
				}
			}
			else {
				// memSize is zero, so moduleState must be nullptr.
				moduleState = nullptr;
			}

			// At module startup, check that input connectivity is
			// satisfied and hook up the input queues.
			try {
				inputConnectivityInitialize();
			}
			catch (const std::runtime_error &ex) {
				dv::Log(dv::logLevel::DEBUG, "moduleInit(): '{:s}', trying again automatically.", ex.what());

				// Runtime errors indicate problems the system can
				// maybe recover from with no user intervention,
				// so we allow the module to restart after one second.
				shutdownProcedure(false, false);
				return;
			}
			catch (const std::exception &ex) {
				dv::Log(dv::logLevel::ERROR, "moduleInit(): '{:s}', disabling module.", ex.what());

				shutdownProcedure(false, true);
				return;
			}

			// init startTime once
			cpuTimeStat.startTime = getThreadCPUTime();
			cpuTimeStat.blocking  = false;

			// Reset variables, as the following Init() is stronger than a reset
			// and implies a full configuration update. This avoids stale state
			// forcing an update and/or reset right away in the first run of
			// the module, which is unneeded and wasteful.
			run.configUpdate = false;

			if (info->functions->moduleInit != nullptr) {
				try {
					dv::LoggerInternal::Set(&logger);
					errno = 0; // Reset errno for boost ASIO bug: https://github.com/chriskohlhoff/asio/issues/1587
					if (!info->functions->moduleInit(this)) {
						throw std::runtime_error("Failed runtime module initialization");
					}
					dv::LoggerInternal::Set(nullptr);
				}
				catch (const std::exception &ex) {
					dv::LoggerInternal::Set(nullptr);

					dv::Log(dv::logLevel::DEBUG, "moduleInit(): '{:s} :: {:s}', disabling module.",
						boost::core::demangle(typeid(ex).name()), ex.what());

					shutdownProcedure(false, true);
					return;
				}
			}

			// Check that all info nodes for the outputs have been created and populated.
			try {
				verifyOutputInfoNodes();
			}
			catch (const std::exception &ex) {
				dv::Log(dv::logLevel::ERROR, "moduleInit(): '{:s}', disabling module.", ex.what());

				shutdownProcedure(true, true);
				return;
			}

			run.isRunning = true;
			moduleConfigNode.updateReadOnly<dv::CfgType::BOOL>("isRunning", true);

			// Getting child modules that need to start before sending data.
			for (auto &output : outputs) {
				auto &childs = output.second.destinations;

				for (auto &child : childs) {
					if (dv::Cfg::GLOBAL.getNode("/mainloop/" + child.linkedInput->parentModule->name + "/")
							.getBool("running")) {
						modulesToWaitFor.push_back(child.linkedInput->parentModule->name);
					}
				}
			}

			vectorSortUnique(modulesToWaitFor);
		}

		// Waiting that all children are running before sending data.
		for (auto moduleName : modulesToWaitFor) {
			std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

			while (dv::Cfg::GLOBAL.existsNode("/mainloop/" + moduleName + "/")
				   && !dv::Cfg::GLOBAL.getNode("/mainloop/" + moduleName + "/").getBool("isRunning")
				   && dv::Cfg::GLOBAL.getNode("/mainloop/" + moduleName + "/").getBool("running")) {
				lock.unlock();

				std::this_thread::sleep_for(std::chrono::milliseconds(1));

				lock.lock();
			}
		}
	}
	else if (run.isRunning.load(std::memory_order_relaxed) && !shouldRun) {
		{
			// Serialize module start/stop globally.
			std::unique_lock lock(MainData::getGlobal().modulesLock);

			// Disconnect from upstream, to ensure no new data gets put on our queues.
			inputConnectivityDisconnect();
		}

		// Run through all data stuck in the input queues.
		{
			std::unique_lock lock(dataAvailable.lock);

			while (dataAvailable.count > 0) {
				lock.unlock();

				try {
					dv::LoggerInternal::Set(&logger);
					cycleStat.start();
					info->functions->moduleRun(this);
					cycleStat.finish();
					dv::LoggerInternal::Set(nullptr);
				}
				catch (const std::exception &ex) {
					dv::LoggerInternal::Set(nullptr);

					dv::Log(dv::logLevel::ERROR, "moduleRun(): '{:s} :: {:s}', shutting down.",
						boost::core::demangle(typeid(ex).name()), ex.what());
				}

				lock.lock();
			}
		}

		std::vector<std::string> forcedShutdownModuleNames;

		{
			// Serialize module start/stop globally.
			std::unique_lock lock(MainData::getGlobal().modulesLock);

			// Shutdown downstream modules first. This happens recursively.
			// First prevent any module from connecting to us. Running is a pre-condition
			// checked with this isRunning variable, so we set it false early
			// to provent any later module from establishing a new connection.
			run.isRunning = false;

			// Gather all outputs that must be shutdown.
			for (auto &out : outputs) {
				for (auto &dest : out.second.destinations) {
					forcedShutdownModuleNames.push_back(dest.linkedInput->parentModule->name);
				}
			}

			// Remove any duplicates.
			vectorSortUnique(forcedShutdownModuleNames);

			// Now force all those modules to shut down and remain
			// in shutdown until allowed to run again, after this
			// module has also turned itself off. Here we can just
			// getModule() directly, as we still hold the global modules
			// lock and nothing can have removed a module in the meantime.
			for (auto &mName : forcedShutdownModuleNames) {
				getModule(mName)->forcedShutdown(true);
			}
		}

	checkDownstreamShutdown:
		// Wait until all downstream modules have really quit. We check
		// moduleNode.isRunning here because that is set _after_ a module
		// has fully shut down (while the isRunning variable is set early).
		// We hold the global modules lock during the check and check again
		// that the module actually exists, to handle the case where modules
		// could have been removed in the mean-time while we didn't hold
		// the global lock. The global lock must be released between checks
		// to allow the other modules to run their shutdown code.
		std::this_thread::sleep_for(std::chrono::milliseconds(1));

		{
			// Serialize module start/stop globally.
			std::unique_lock lock(MainData::getGlobal().modulesLock);

			for (auto &mName : forcedShutdownModuleNames) {
				auto mod = getModule(mName);
				if (mod == nullptr) {
					continue;
				}

				if (dv::Cfg::GLOBAL.getNode("/mainloop/" + mName + "/").get<dv::CfgType::BOOL>("isRunning")) {
					goto checkDownstreamShutdown;
				}
			}
		}

		{
			// Serialize module start/stop globally.
			std::unique_lock lock(MainData::getGlobal().modulesLock);

			// Full shutdown.
			shutdownProcedure(true, false);

			moduleConfigNode.updateReadOnly<dv::CfgType::BOOL>("isRunning", false);

			// Allow downstream modules to start again, depending on user config.
			// First check for the existence of the corresponding module to verify
			// the module still exists, since we didn't hold the global modules lock
			// before all the time, it could have been removed by now.
			for (auto &mName : forcedShutdownModuleNames) {
				auto mod = getModule(mName);
				if (mod == nullptr) {
					continue;
				}

				mod->forcedShutdown(false);
			}

			// Release memory to system after shutdown.
#if defined(ENABLE_HEAP_PROFILER)
			MallocExtension::instance()->ReleaseFreeMemory();
#elif defined(OS_LINUX)
			malloc_trim(0);
#endif
		}
	}
}

dv::Types::TypedObject *dv::Module::outputAllocate(std::string_view outputName) {
	auto output = getModuleOutput(std::string(outputName));
	if (output == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Output with name '{:s}' doesn't exist."), outputName));
	}

	if (!output->nextPacket) {
		// Allocate new and store.
		output->nextPacket = std::make_shared<dv::Types::TypedObject>(output->type);
	}

	// Return current value.
	return (output->nextPacket.get());
}

void dv::Module::outputCommit(std::string_view outputName) {
	auto output = getModuleOutput(std::string(outputName));
	if (output == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Output with name '{:s}' doesn't exist."), outputName));
	}

	if (!output->nextPacket) {
		// Not previously allocated, ignore.
		return;
	}

	if (!run.isRunning.load(std::memory_order_relaxed)) {
		// We're shutting down and yet commit has been called somewhere:
		// return directly to avoid dead-lock scenarios with modules that
		// have multiple extra threads and try to commit in them.
		return;
	}

	{
		std::shared_lock lock(dv::MainData::getGlobal().modulesLock);

		std::vector<OutConnection> copy(output->destinations);

	resendToOutputs:
		for (auto &dest : output->destinations) {
			// If not in the copy, data already sent to this output.
			if (!dv::vectorContains(copy, dest)) {
				continue;
			}

			// Disabled output.
			if (!dest.isValid()) {
				continue;
			}

			// Send new data to downstream module, increasing its reference
			// count to share ownership amongst the downstream modules.
			if (!dest.queue->push(output->nextPacket)) {
				// Notify full queue load to input.
				dest.linkedInput->node.updateReadOnly<dv::CfgType::INT>(
					"queueLength", INTER_MODULE_TRANSFER_QUEUE_SIZE);

				lock.unlock();

				// Retry after 1 ms to send data to this output. We release the lock
				// here so that downstream modules have the chance to shutdown and
				// remove themselves from 'output->destinations'. We then resume
				// iterating over the up-to-date vector and don't resend data to
				// outputs that have already got it (using the 'copy' vector).
				std::this_thread::sleep_for(std::chrono::milliseconds(1));

				lock.lock();

				goto resendToOutputs;
			}

			// Success, remove ourselves from list of destinations for this data block.
			vectorRemove(copy, dest);

			// Notify downstream module about new data being available.
			{
				std::unique_lock lock2(dest.dataAvailable->lock);
				dest.dataAvailable->count++;
			}

			dest.dataAvailable->cond.notify_all();
		}
	}

	output->nextPacket.reset();
}

const dv::Types::TypedObject *dv::Module::inputGet(std::string_view inputName) {
	auto input = getModuleInput(std::string(inputName));
	if (input == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exist."), inputName));
	}

	// Remember each real packet given out to the user, so that an inputDismiss()
	// will remove the corresponding entry.
	if (input->currentPublishedPacket) {
		input->inUsePackets.push_back(input->currentPublishedPacket);
	}

	return input->currentPublishedPacket.get();
}

/**
 * Advances the provided input. Reads the lates data packet on the specified output and
 * sets it to the currentPublishedPacket variable, if the name of the input is not defined,
 * all inputs are advanced.
 *
 * @param inputName The name of the input to be advanced (empty for all).
 */
void dv::Module::inputAdvance(std::string_view inputName) {
	// advance all inputs when an empty input name is given, otherwise only advance the given input
	if (inputName.empty()) {
		for (const auto &in : inputs) {
			_singleInputAdvance(in.first);
		}
	}
	else {
		_singleInputAdvance(inputName);
	}
}

/**
 * Private implementation of the advance function for a single input.
 * Gets called by inputAdvance().
 *
 * @param inputName The name of the input to be advanced.
 */
void dv::Module::_singleInputAdvance(std::string_view inputName) {
	auto input = getModuleInput(std::string(inputName));
	if (input == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exist."), inputName));
	}

	TypedObjectSharedPtr nextPacket;

	if (!input->queue.pop(nextPacket)) {
		// Empty queue, no data to be taken off queue.
		// Reset pointer to empty, ensuring nullptr is contained.
		input->currentPublishedPacket.reset();

		input->node.updateReadOnly<dv::CfgType::INT>("queueLength", 0);
		return;
	}

	{
		input->statPacketsNumber++;

		// ANYT/NULL types have no information.
		if (input->type.timeElementExtractor != nullptr) {
			const auto timeElementInfo  = (*input->type.timeElementExtractor)(nextPacket->obj);
			input->statPacketsSize     += timeElementInfo.numElements;
			input->statThroughput.add(static_cast<uint64_t>(timeElementInfo.numElements));
		}
	}

	if (input->statRateLimiter.pass()) {
		input->node.updateReadOnly<dv::CfgType::INT>(
			"queueLength", static_cast<int32_t>(input->queue.read_available()));

		input->node.updateReadOnly<dv::CfgType::LONG>("readPacketsNumber", input->statPacketsNumber);
		input->node.updateReadOnly<dv::CfgType::LONG>("readPacketsElements", input->statPacketsSize);
	}

	{
		std::unique_lock lock(dataAvailable.lock);
		dataAvailable.count--;
	}

	input->currentPublishedPacket = nextPacket;
}

void dv::Module::inputDismiss(std::string_view inputName, const dv::Types::TypedObject *data) {
	auto input = getModuleInput(std::string(inputName));
	if (input == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exist."), inputName));
	}

	auto pkt = std::find_if(input->inUsePackets.begin(), input->inUsePackets.end(), [data](const auto &p) {
		return (p.get() == data);
	});
	if (pkt == input->inUsePackets.end()) {
		// Not found.
		throw std::out_of_range(fmt::format(
			FMT_STRING("Input '{:s}': could not find packet in list, inputDismiss() called too many times."),
			inputName));
	}

	input->inUsePackets.erase(pkt);
}

/**
 * Get informative node for an output of this module.
 *
 * @param outputName name of output.
 * @return informative node for that output.
 */
dv::Config::Node dv::Module::outputGetInfoNode(std::string_view outputName) {
	auto output = getModuleOutput(std::string(outputName));
	if (output == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Output with name '{:s}' doesn't exist."), outputName));
	}

	return (output->infoNode);
}

/**
 * Get informative node for an input from that input's upstream module.
 *
 * @param inputName name of input.
 * @return informative node for that input.
 */
const dv::Config::Node dv::Module::inputGetInfoNode(std::string_view inputName) {
	auto input = getModuleInput(std::string(inputName));
	if (input == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exist."), inputName));
	}

	std::shared_lock lock(dv::MainData::getGlobal().modulesLock);

	if (!input->enabled || (input->linkedOutput == nullptr)) {
		// Input can be unconnected.
		throw std::runtime_error(fmt::format(FMT_STRING("Input '{:s}' is unconnected."), inputName));
	}

	auto infoNode = input->linkedOutput->infoNode;
	if (infoNode.getAttributeKeys().empty()) {
		throw std::runtime_error(
			fmt::format(FMT_STRING("No informative content present for input '{:s}'."), inputName));
	}

	return (infoNode);
}

/**
 * Check if an input is connected properly and can get data at runtime.
 * Most useful in moduleInit() to verify status of optional inputs.
 *
 * @param inputName name of input.
 * @return true if input is connected and valid, false otherwise.
 */
bool dv::Module::inputIsConnected(std::string_view inputName) {
	auto input = getModuleInput(std::string(inputName));
	if (input == nullptr) {
		// Not found.
		throw std::out_of_range(fmt::format(FMT_STRING("Input with name '{:s}' doesn't exist."), inputName));
	}

	std::shared_lock lock(dv::MainData::getGlobal().modulesLock);

	return (input->enabled);
}

void dv::Module::moduleRunningListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
	const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
	UNUSED_ARGUMENT(node);

	auto run = static_cast<dv::RunControl *>(userData);

	if ((event == DVCFG_ATTRIBUTE_MODIFIED) && (changeType == DVCFG_TYPE_BOOL)
		&& (std::string{changeKey} == "running")) {
		{
			std::unique_lock lock(run->lock);

			run->running = changeValue.boolean;
		}

		run->cond.notify_all();
	}
}

void dv::Module::moduleLogLevelListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
	const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
	UNUSED_ARGUMENT(node);

	auto logLevel = static_cast<std::atomic<dv::logLevel> *>(userData);

	if ((event == DVCFG_ATTRIBUTE_MODIFIED) && (changeType == DVCFG_TYPE_STRING)
		&& (std::string(changeKey) == "logLevel")) {
		logLevel->store(dv::LoggerInternal::logLevelNameToEnum(changeValue.string));
	}
}

void dv::Module::moduleConfigUpdateListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
	const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
	UNUSED_ARGUMENT(node);
	UNUSED_ARGUMENT(changeKey);
	UNUSED_ARGUMENT(changeType);
	UNUSED_ARGUMENT(changeValue);

	auto configUpdate = static_cast<std::atomic_bool *>(userData);
	auto changeKeyStr = std::string(changeKey);

	// Simply set the config update flag to 1 on any attribute change.
	if ((event == DVCFG_ATTRIBUTE_MODIFIED) && (changeKeyStr != "cpuUsage") && (changeKeyStr != "cpuTime")
		&& (changeKeyStr.find("cycleTime") == std::string::npos)) {
		configUpdate->store(true);
	}
}

void dv::Module::moduleFromChangeListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
	const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue) {
	if ((changeType != DVCFG_TYPE_STRING) || (std::string{changeKey} != "from")) {
		return;
	}

	auto inputNode  = static_cast<dv::Cfg::Node>(node);
	auto moduleNode = inputNode.getParent().getParent();
	auto module     = static_cast<dv::Module *>(userData);

	std::unique_lock lock(dv::MainData::getGlobal().modulesLock);

	switch (event) {
		case DVCFG_ATTRIBUTE_MODIFIED:
			module->inputDisconnect(inputNode.getName());
			if (moduleNode.getBool("isRunning")) {
				moduleNode.putBool("running", false);
			}
			module->inputConnect(inputNode.getName(), changeValue.string);
			break;

		case DVCFG_ATTRIBUTE_ADDED:
		case DVCFG_ATTRIBUTE_MODIFIED_CREATE:
			module->inputConnect(inputNode.getName(), changeValue.string);
			break;

		case DVCFG_ATTRIBUTE_REMOVED:
			module->inputDisconnect(inputNode.getName());
			break;

		default:
			break;
	}
}
