#ifndef MAIN_HPP_
#define MAIN_HPP_

#include "types.hpp"

#include <atomic>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>

namespace dv {

class Module;

struct SDKLibFunctionPointers {
	// Type interface.
	std::function<const dv::Types::Type(const char *, const dv::Module *)> getTypeInfoCharString;
	std::function<const dv::Types::Type(uint32_t, const dv::Module *)> getTypeInfoIntegerID;
	// Module interface.
	std::function<void(dv::Module *, const dv::Types::Type)> registerType;
	std::function<void(dv::Module *, std::string_view, std::string_view)> registerOutput;
	std::function<void(dv::Module *, std::string_view, std::string_view, bool)> registerInput;
	std::function<dv::Types::TypedObject *(dv::Module *, std::string_view)> outputAllocate;
	std::function<void(dv::Module *, std::string_view)> outputCommit;
	std::function<const dv::Types::TypedObject *(dv::Module *, std::string_view)> inputGet;
	std::function<void(dv::Module *, std::string_view)> inputAdvance;
	std::function<void(dv::Module *, std::string_view, const dv::Types::TypedObject *)> inputDismiss;
	std::function<dv::Config::Node(dv::Module *, std::string_view)> outputGetInfoNode;
	std::function<const dv::Config::Node(dv::Module *, std::string_view)> inputGetInfoNode;
	std::function<bool(dv::Module *, std::string_view)> inputIsConnected;
	// Logging.
	std::function<void(dv::logLevel, std::string_view)> log;
};

class shared_recursive_mutex : public std::shared_mutex {
private:
	std::atomic<std::thread::id> owner = std::thread::id();
	size_t count                       = 0;

public:
	void lock() {
		std::thread::id this_id = std::this_thread::get_id();

		if (owner == this_id) {
			// recursive locking
			count++;
		}
		else {
			// normal locking
			std::shared_mutex::lock();
			owner = this_id;
			count = 1;
		}
	}

	bool try_lock() {
		std::thread::id this_id = std::this_thread::get_id();

		if (owner == this_id) {
			// recursive locking
			count++;

			return (true);
		}
		else {
			// normal locking
			bool locked = std::shared_mutex::try_lock();

			if (locked) {
				owner = this_id;
				count = 1;
			}

			return (locked);
		}
	}

	void unlock() {
		std::thread::id this_id = std::this_thread::get_id();

		if (owner == this_id) {
			if (count > 1) {
				// recursive unlocking
				count--;
			}
			else {
				// normal unlocking
				owner = std::thread::id();
				count = 0;

				std::shared_mutex::unlock();
			}
		}
		else {
			throw std::runtime_error("Call to shared_recursive_mutex::unlock() not from lock owner.");
		}
	}

	// Allow shared locking if already owning the unique lock.
	// We track that above anyway, so we can safely and easily
	// allow temporary downgrade of lock type.
	void lock_shared() {
		std::thread::id this_id = std::this_thread::get_id();

		if (owner == this_id) {
			// add to recursive unique locking
			count++;
		}
		else {
			// normal shared locking
			std::shared_mutex::lock_shared();
		}
	}

	bool try_lock_shared() {
		std::thread::id this_id = std::this_thread::get_id();

		if (owner == this_id) {
			// add to recursive unique locking
			count++;

			return (true);
		}
		else {
			// normal shared locking
			return (std::shared_mutex::try_lock_shared());
		}
	}

	void unlock_shared() {
		std::thread::id this_id = std::this_thread::get_id();

		if (owner == this_id) {
			if (count > 1) {
				// remove from recursive unique locking
				count--;
			}
			else {
				// error state
				throw std::runtime_error("Too many calls to shared_recursive_mutex::unlock_shared() from lock owner.");
			}
		}
		else {
			// normal shared unlocking
			std::shared_mutex::unlock_shared();
		}
	}
};

class MainData {
public:
	std::atomic_bool systemRunning;
	dv::SDKLibFunctionPointers libFunctionPointers;
	dv::Types::TypeSystem typeSystem;
	dv::shared_recursive_mutex modulesLock;
	std::unordered_map<std::string, std::shared_ptr<dv::Module>> modules;

	static MainData &getGlobal() {
		static MainData md;
		return (md);
	}

private:
	MainData() : systemRunning(true) {
	}
};

/**
 * Adds a new module to the system, initializing it and
 * its static configuration.
 *
 * @param name new module instance name.
 * @param library shared library plugin to load.
 */
void addModule(const std::string &name, const std::string &library, bool startModule);

/**
 * Removes a module from the system, fully erasing its configuration.
 * You must ensure the module was stopped first!
 *
 * @param name module instance name.
 */
void removeModule(const std::string &name);

/**
 * Only for internal usage! Do not reset the main data pointer!
 */
DVSDK_EXPORT void SDKLibInit(const SDKLibFunctionPointers *setLibFuncPtr);

extern const SDKLibFunctionPointers *glLibFuncPtr;

} // namespace dv

#endif /* MAIN_HPP_ */
