#ifndef MODULE_HPP_
#define MODULE_HPP_

#include "../include/dv-sdk/config.hpp"
#include "../include/dv-sdk/module.h"
#include "../include/dv-sdk/stats.hpp"
#include "../include/dv-sdk/utils.h"

#include "log.hpp"
#include "modules_discovery.hpp"

#include <boost/lockfree/spsc_queue.hpp>

#include <atomic>
#include <condition_variable>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <utility>

#define INTER_MODULE_TRANSFER_QUEUE_SIZE 256

namespace dv {

class Module;
class ModuleInput;
class ModuleOutput;

using TypedObjectSharedPtr = std::shared_ptr<dv::Types::TypedObject>;
using TypedObjectsSPSCQueue
	= boost::lockfree::spsc_queue<TypedObjectSharedPtr, boost::lockfree::capacity<INTER_MODULE_TRANSFER_QUEUE_SIZE>>;

class ModuleInput {
public:
	dv::Types::Type type;
	bool optional;
	Module *parentModule;
	dv::Cfg::Node node;
	ModuleOutput *linkedOutput;
	TypedObjectsSPSCQueue queue;
	bool enabled; // Queue getting data?
	// GLOBAL LOCK NOT NEEDED, 'inUsePackets' IS LOCAL.
	std::vector<TypedObjectSharedPtr> inUsePackets;
	// GLOBAL LOCK NOT NEEDED, 'currentPublishedPacket' IS LOCAL.
	TypedObjectSharedPtr currentPublishedPacket;
	// Input data statistics.
	dv::_RateLimiter statRateLimiter;
	int64_t statPacketsNumber;
	int64_t statPacketsSize;
	dv::statistics::Throughput statThroughput;

	ModuleInput(const dv::Types::Type &t, bool opt, Module *parent, dv::Cfg::Node n) :
		type(t),
		optional(opt),
		parentModule(parent),
		node(n),
		linkedOutput(nullptr),
		enabled(false),
		statRateLimiter(1, 500),
		statPacketsNumber(0),
		statPacketsSize(0),
		statThroughput(std::chrono::milliseconds(10), node, "throughput") {
	}
};

struct InputDataAvailable {
	// Input data availability.
	std::mutex lock;
	std::condition_variable cond;
	int32_t count;

	InputDataAvailable() : count(0) {
	}
};

class OutConnection {
public:
	ModuleInput *linkedInput;
	TypedObjectsSPSCQueue *queue;
	InputDataAvailable *dataAvailable;

	OutConnection(ModuleInput *linkedInput_, InputDataAvailable *dataAvailable_) :
		linkedInput(linkedInput_),
		queue(nullptr),
		dataAvailable(dataAvailable_) {
	}

	void enable(TypedObjectsSPSCQueue *queue_) noexcept {
		queue = queue_;
	}

	void disable() noexcept {
		queue = nullptr;
	}

	[[nodiscard]] bool isValid() const noexcept {
		return (queue != nullptr);
	}

	[[nodiscard]] bool operator==(const OutConnection &rhs) const noexcept {
		// The linked input pointer is unique, so enough to establish equality.
		return (linkedInput == rhs.linkedInput);
	}

	[[nodiscard]] bool operator!=(const OutConnection &rhs) const noexcept {
		return (!operator==(rhs));
	}
};

class ModuleOutput {
public:
	dv::Types::Type type;
	Module *parentModule;
	dv::Config::Node node;
	dv::Config::Node infoNode;
	std::vector<OutConnection> destinations;
	// GLOBAL LOCK NOT NEEDED, 'nextPacket' IS LOCAL.
	TypedObjectSharedPtr nextPacket;

	ModuleOutput(const dv::Types::Type &t, Module *parent, dv::Config::Node n) :
		type(t),
		parentModule(parent),
		node(n),
		infoNode(n.getRelativeNode("info/")) {
	}
};

struct RunControl {
	// Run status.
	std::mutex lock;
	std::condition_variable cond;
	bool forcedShutdown;
	bool running;
	std::atomic_bool isRunning;
	std::atomic_bool configUpdate;
	bool runDelay;

	RunControl() : forcedShutdown(false), running(false), isRunning(false), configUpdate(false), runDelay(false) {
	}
};

class Module : public dvModuleDataS {
private:
	// Module info.
	std::string name;
	dvModuleInfo info;
	dv::ModuleLibrary library;
	dv::Config::Node moduleConfigNode;
	// Run status.
	struct RunControl run;
	// Logging.
	dv::LoggerInternal::LogBlock logger;
	// I/O connectivity.
	std::unordered_map<std::string, ModuleInput> inputs;
	std::unordered_map<std::string, ModuleOutput> outputs;
	// Input data availability.
	struct InputDataAvailable dataAvailable;
	std::atomic_size_t connectedInputs;
	// Module thread management.
	std::thread thread;
	std::atomic_bool threadAlive;
	// Module resource info.
	dv::_RateLimiter statRateLimiter;

	struct {
		uint64_t oldCpuTime;
		uint64_t oldThreadTime;
	} cpuUsageStat;

	struct {
		uint64_t startTime;
		uint64_t stopTime;
		uint64_t diffTime;
		bool blocking{false};
	} cpuTimeStat;

	dv::statistics::CycleTime cycleStat;

public:
	Module(std::string_view _name, std::string_view _library);
	~Module();

	void startThread();
	void stopThread();

	void registerType(const dv::Types::Type type);
	void registerOutput(std::string_view name, std::string_view typeName);
	void registerInput(std::string_view name, std::string_view typeName, bool optional = false);

	dv::Types::TypedObject *outputAllocate(std::string_view outputName);
	void outputCommit(std::string_view outputName);

	const dv::Types::TypedObject *inputGet(std::string_view inputName);
	void inputAdvance(std::string_view inputName);
	void inputDismiss(std::string_view inputName, const dv::Types::TypedObject *data);

	dv::Config::Node outputGetInfoNode(std::string_view outputName);
	const dv::Config::Node inputGetInfoNode(std::string_view inputName);
	bool inputIsConnected(std::string_view inputName);

private:
	void LoggingInit();
	void RunningInit();
	void StaticInit();

	Module *getModule(const std::string &moduleName);
	ModuleOutput *getModuleOutput(const std::string &outputName);
	ModuleInput *getModuleInput(const std::string &outputName);
	void _singleInputAdvance(std::string_view inputName);

	static std::pair<std::string, std::string> parseFromString(const std::string &from);
	static void inputsRegenerate(const std::string &moduleName, const std::string &outputName);

	void inputConnect(const std::string &input, const std::string &from);
	void inputDisconnect(const std::string &input);

	void outputConnect(const std::string &output);
	void outputDisconnect(const std::string &output);

	void inputConnectivityInitialize();
	void inputConnectivityDisconnect();
	void inputConnectivityCleanup();

	void verifyOutputInfoNodes();
	void cleanupOutputInfoNodes();

	void runThread();
	void runStateMachine();
	void shutdownProcedure(bool doModuleExit, bool disableModule);
	void forcedShutdown(bool shutdown);

	// return the cpu usage for the thread that execute the run function
	float cpuUsageThread();
	uint64_t getThreadCPUTime();

	static void moduleRunningListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue);
	static void moduleLogLevelListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue);
	static void moduleConfigUpdateListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue);
	static void moduleFromChangeListener(dvConfigNode node, void *userData, enum dvConfigAttributeEvents event,
		const char *changeKey, enum dvConfigAttributeType changeType, union dvConfigAttributeValue changeValue);
};

} // namespace dv

#endif /* MODULE_HPP_ */
