#ifndef LOADABLE_LIBRARY_MANAGER_H
#define LOADABLE_LIBRARY_MANAGER_H

#include <dlfcn.h>
#include <atomic>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include <spinscale/co/coQutex.h>
#include <spinscale/sharedResourceGroup.h>

namespace smo {
namespace loadable_lib {

class LoadableLibraryManager
{
public:
	struct SharedLibraryDeleter
	{
		void operator()(void* handle) const;
	};

	struct LoadedSharedLibrary
	{
		LoadedSharedLibrary(
			std::string libraryPathIn,
			std::string resolvedPathIn,
			std::unique_ptr<void, SharedLibraryDeleter> dlopenHandleIn)
		:	libraryPath(std::move(libraryPathIn)),
			resolvedPath(std::move(resolvedPathIn)),
			dlopenHandle(std::move(dlopenHandleIn))
		{}

		std::string libraryPath;
		std::string resolvedPath;
		std::unique_ptr<void, SharedLibraryDeleter> dlopenHandle;
		std::atomic<bool> isBeingDestroyed{false};

		void* getDlopenHandle() const
		{
			return dlopenHandle.get();
		}

		std::string stringify() const
		{
			std::string result = "Library Path: " + libraryPath + "\n";
			result += "Resolved Path: " + resolvedPath + "\n";
			result += "Is Being Destroyed: ";
			result += (isBeingDestroyed.load() ? "true" : "false");
			result += "\n";
			return result;
		}
	};

	struct Resources
	{
		std::vector<std::shared_ptr<LoadedSharedLibrary>> loadedSharedLibraries;
	};

	static LoadableLibraryManager& getInstance()
	{
		static LoadableLibraryManager instance;
		return instance;
	}

	std::optional<std::string> searchForLibInSmoSearchPaths(
		const std::string& libraryPath) const;

	std::shared_ptr<LoadedSharedLibrary> loadSharedLibrary(
		const std::string& libraryPath);

	void unloadSharedLibrary(
		const std::shared_ptr<LoadedSharedLibrary>& loadedLibrary);

	template<typename FnPtr>
	static FnPtr resolveSymbol(void* handle, const char* symbolName)
	{
		dlerror();
		auto symbol = reinterpret_cast<FnPtr>(dlsym(handle, symbolName));
		const char* dlerr = dlerror();
		if (dlerr != nullptr)
		{
			throw std::runtime_error(
				std::string("dlsym('") + symbolName + "') failed: " + dlerr);
		}

		if (symbol == nullptr)
		{
			throw std::runtime_error(
				std::string("dlsym('") + symbolName + "') returned null");
		}

		return symbol;
	}

public:
	sscl::SharedResourceGroup<sscl::co::CoQutex, Resources> s;

private:
	LoadableLibraryManager()
	:	s("LoadableLibraryManager")
	{}

	~LoadableLibraryManager();

	LoadableLibraryManager(const LoadableLibraryManager&) = delete;
	LoadableLibraryManager& operator=(const LoadableLibraryManager&) = delete;

	void appendLoadedSharedLibrary(
		const std::shared_ptr<LoadedSharedLibrary>& loadedLibrary);

	void removeLoadedSharedLibrary(
		const std::shared_ptr<LoadedSharedLibrary>& loadedLibrary);
};

} // namespace loadable_lib
} // namespace smo

#endif // LOADABLE_LIBRARY_MANAGER_H
