#pragma once

/*
	PluginFramework
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Plugin Manager

#include "Plugin.h"
#include <memory>
#include <string>
#include <cstring>
#include <unordered_map>
#include <atomic>
#include <functional>
#include <vector>

// Forward declare file watcher
namespace filewatch {
	enum class Event;
	template <typename T>
	class FileWatch;
}
using FileWatcher = filewatch::FileWatch<std::string>;

namespace PluginFramework {

// Definition of ToString template
template <typename T>
std::string ToString(T value);

// Specializations for ToString from different types
template <> inline std::string ToString(const char* value) { return std::string(value);    }
template <> inline std::string ToString(std::string value) { return value;                 }
template <> inline std::string ToString(bool value)        { return std::to_string(value); }
template <> inline std::string ToString(uint8_t value)     { return std::to_string(value); }
template <> inline std::string ToString(int8_t value)      { return std::to_string(value); }
template <> inline std::string ToString(uint16_t value)    { return std::to_string(value); }
template <> inline std::string ToString(int16_t value)     { return std::to_string(value); }
template <> inline std::string ToString(uint32_t value)    { return std::to_string(value); }
template <> inline std::string ToString(int32_t value)     { return std::to_string(value); }
template <> inline std::string ToString(uint64_t value)    { return std::to_string(value); }
template <> inline std::string ToString(int64_t value)     { return std::to_string(value); }
template <> inline std::string ToString(float value)       { return std::to_string(value); }
template <> inline std::string ToString(double value)      { return std::to_string(value); }

// Definition of FromString template
template <typename T>
void FromString(T& value, std::string str);

// Specializations for FromString to different types
template <> inline void FromString(const char*& value, std::string str) { value = strdup(str.c_str());          } // potential leak
template <> inline void FromString(std::string& value, std::string str) { value = str;                          }
template <> inline void FromString(bool&        value, std::string str) { value = std::stoul (str, nullptr, 0); }
template <> inline void FromString(uint8_t&     value, std::string str) { value = std::stoul (str, nullptr, 0); }
template <> inline void FromString(int8_t&      value, std::string str) { value = std::stol  (str, nullptr, 0); }
template <> inline void FromString(uint16_t&    value, std::string str) { value = std::stoul (str, nullptr, 0); }
template <> inline void FromString(int16_t&     value, std::string str) { value = std::stol  (str, nullptr, 0); }
template <> inline void FromString(uint32_t&    value, std::string str) { value = std::stoul (str, nullptr, 0); }
template <> inline void FromString(int32_t&     value, std::string str) { value = std::stol  (str, nullptr, 0); }
template <> inline void FromString(uint64_t&    value, std::string str) { value = std::stoull(str, nullptr, 0); }
template <> inline void FromString(int64_t&     value, std::string str) { value = std::stoll (str, nullptr, 0); }
template <> inline void FromString(float&       value, std::string str) { value = std::stof  (str, nullptr);    }
template <> inline void FromString(double&      value, std::string str) { value = std::stod  (str, nullptr);    }

class State
{
public:
	State() {}
	~State() {}

	template <typename T>
	void AddValue(std::string name, T value)
	{
		mValues[name] = ToString(value);
	}

	template <typename T>
	bool GetValue(std::string name, T& value) const
	{
		if (mValues.count(name))
		 	FromString(value, mValues.at(name));
		return mValues.count(name) != 0;
	}

	void Reset()
	{
		mValues.clear();
	}

private:
	std::unordered_map<std::string, std::string> mValues;
};

class IExtension
{
public:
	IExtension() {}
	virtual ~IExtension() {}

	static std::string Type() { return "IExtension"; }
	virtual	bool SupportsInterface(std::string interfaceName) const { return (interfaceName == IExtension::Type()); }

	virtual	void Initialize() = 0;
	virtual	void Shutdown() = 0;
	virtual	void Update() = 0;

	// For hot-reloading, need to be able to serialize and deserialize
	virtual void SerializeState(State& state) = 0;
	virtual void DeserializeState(const State& state) = 0;
};

struct ExtensionWrapper
{
	IExtension*      mExtensionPtr;
	State            mSavedState;
};

class PluginManager
{
public:
	PluginManager();
	~PluginManager();

	void SetPluginDirectory(const char* pluginDirectoryName);

	void Initialize();
	void Shutdown();

	void Update();

	void LoadPlugins();
	void RegisterPlugins();

	void Reload();
	void UnloadPlugin(Plugin& plugin);

	// Call by the plugin to register the functionality it can provide
	template <typename T>
	void RegisterExtension()
	{
		RegisterExtension(T::Type(), new T);
	}

	// Call by the plugin to deregister when the plugin is unloaded
	template <typename T>
	void DeregisterExtension()
	{
		DeregisterExtension(T::Type());
	}

	IExtension* FindExtension(std::string typeName)
	{
		if (mRegisteredExtensions.count(typeName))
			return mRegisteredExtensions.at(typeName).mExtensionPtr;
		return nullptr;
	}

	template <typename T>
	T* FindExtension()
	{
		return (T*)FindExtension(T::Type());
	}

private:
	void RegisterExtension(std::string extensionName, IExtension* extensionPtr)
	{
		if (mRegisteredExtensions.count(extensionName))
		{
			ExtensionWrapper& wrapper = mRegisteredExtensions.at(extensionName);
			if (wrapper.mExtensionPtr != nullptr)
			{
				printf("Unexpected, there is already an extension instance with same type %s\n", extensionName.c_str());
				delete extensionPtr;
				return;
			}
			wrapper.mExtensionPtr = extensionPtr;
			wrapper.mExtensionPtr->DeserializeState(wrapper.mSavedState);
			wrapper.mSavedState.Reset();
		}
		else
		{
			mRegisteredExtensions[extensionName] = ExtensionWrapper{ extensionPtr };
		}
	}

	// Call by the plugin to deregister when the plugin is unloaded
	void DeregisterExtension(std::string extensionName)
	{
		if (mRegisteredExtensions.count(extensionName))
		{
			ExtensionWrapper& wrapper = mRegisteredExtensions.at(extensionName);
			if (wrapper.mExtensionPtr == nullptr)
			{
				printf("Unexpected, there isn't already an extension instance when deregistering %s\n", extensionName.c_str());
				return;
			}
			wrapper.mExtensionPtr->SerializeState(wrapper.mSavedState);
			delete wrapper.mExtensionPtr;
			wrapper.mExtensionPtr = nullptr;
		}
		else
		{
			printf("Unexpectedly deregistering an extension that isn't registered %s\n", extensionName.c_str());
		}
	}

	using FileWatcherPtr = std::unique_ptr<FileWatcher>;

	std::unordered_map<std::string, ExtensionWrapper>  mRegisteredExtensions;

	std::string                              mPluginDirectoryName;
	std::unordered_map<std::string, Plugin>  mLoadedPlugins;

	FileWatcherPtr                           mFileWatcher;
	std::atomic<bool>                        mNeedReload;
	std::atomic<bool>                        mPendingAdd;
};

} // PluginFramework namespace
