/*
 * ComponentManager.cpp
 * --------------------
 * Purpose: Manages loading of optional components.
 * Notes  : (currently none)
 * Authors: Joern Heusipp
 *          OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */

#include "stdafx.h"
#include "ComponentManager.h"

#include "mpt/mutex/mutex.hpp"

#include "Logging.h"

OPENMPT_NAMESPACE_BEGIN


ComponentBase::ComponentBase(ComponentType type)
	: m_Type(type)
	, m_Initialized(false)
	, m_Available(false)
{
	return;
}


ComponentBase::~ComponentBase()
{
	return;
}


void ComponentBase::SetInitialized()
{
	m_Initialized = true;
}


void ComponentBase::SetAvailable()
{
	m_Available = true;
}


ComponentType ComponentBase::GetType() const
{
	return m_Type;
}


bool ComponentBase::IsInitialized() const
{
	return m_Initialized;
}


bool ComponentBase::IsAvailable() const
{
	return m_Initialized && m_Available;
}


mpt::ustring ComponentBase::GetVersion() const
{
	return mpt::ustring();
}


void ComponentBase::Initialize()
{
	if(IsInitialized())
	{
		return;
	}
	if(DoInitialize())
	{
		SetAvailable();
	}
	SetInitialized();
}


#if defined(MODPLUG_TRACKER)


ComponentLibrary::ComponentLibrary(ComponentType type)
	: ComponentBase(type)
	, m_BindFailed(false)
{
	return;
}


ComponentLibrary::~ComponentLibrary()
{
	return;
}


bool ComponentLibrary::AddLibrary(const std::string &libName, const mpt::LibraryPath &libPath)
{
	if(m_Libraries[libName].IsValid())
	{
		// prefer previous
		return true;
	}
	mpt::Library lib(libPath);
	if(!lib.IsValid())
	{
		return false;
	}
	m_Libraries[libName] = lib;
	return true;
}


void ComponentLibrary::ClearLibraries()
{
	m_Libraries.clear();
}


void ComponentLibrary::SetBindFailed()
{
	m_BindFailed = true;
}


void ComponentLibrary::ClearBindFailed()
{
	m_BindFailed = false;
}


bool ComponentLibrary::HasBindFailed() const
{
	return m_BindFailed;
}


mpt::Library ComponentLibrary::GetLibrary(const std::string &libName) const
{
	const auto it = m_Libraries.find(libName);
	if(it == m_Libraries.end())
	{
		return mpt::Library();
	}
	return it->second;
}


#endif // MODPLUG_TRACKER


#if MPT_COMPONENT_MANAGER


ComponentFactoryBase::ComponentFactoryBase(const std::string &id, const std::string &settingsKey)
	: m_ID(id)
	, m_SettingsKey(settingsKey)
{
	return;
}


ComponentFactoryBase::~ComponentFactoryBase()
{
	return;
}


std::string ComponentFactoryBase::GetID() const
{
	return m_ID;
}


std::string ComponentFactoryBase::GetSettingsKey() const
{
	return m_SettingsKey;
}


void ComponentFactoryBase::PreConstruct() const
{
	MPT_LOG_GLOBAL(LogInformation, "Components", 
		MPT_UFORMAT("Constructing Component {}")
			( mpt::ToUnicode(mpt::Charset::ASCII, m_ID)
			)
		);
}


void ComponentFactoryBase::Initialize(ComponentManager &componentManager, std::shared_ptr<IComponent> component) const
{
	if(componentManager.IsComponentBlocked(GetSettingsKey()))
	{
		return;
	}
	componentManager.InitializeComponent(component);
}


// Global list of component register functions.
// We do not use a global scope static list head because the corresponding
//  mutex would be no POD type and would thus not be safe to be usable in
//  zero-initialized state.
// Function scope static initialization is guaranteed to be thread safe
//  in C++11.
// We use this implementation to be future-proof.
// MSVC currently does not exploit the possibility of using multiple threads
//  for global lifetime object's initialization.
// An implementation with a simple global list head and no mutex at all would
//  thus work fine for MSVC (currently).

static mpt::mutex & ComponentListMutex()
{
	static mpt::mutex g_ComponentListMutex;
	return g_ComponentListMutex;
}

static ComponentListEntry * & ComponentListHead()
{
	static ComponentListEntry g_ComponentListHeadEmpty = {nullptr, nullptr};
	static ComponentListEntry *g_ComponentListHead = &g_ComponentListHeadEmpty;
	return g_ComponentListHead;
}

bool ComponentListPush(ComponentListEntry *entry)
{
	mpt::lock_guard<mpt::mutex> guard(ComponentListMutex());
#if MPT_MSVC_BEFORE(2019,0)
	// Guard against VS2017 compiler bug causing repeated initialization of inline variables.
	// See <https://developercommunity.visualstudio.com/t/static-inline-variable-gets-destroyed-multiple-tim/297876>.
	if(entry->next)
	{
		return false;
	}
#endif
	entry->next = ComponentListHead();
	ComponentListHead() = entry;
	return true;
}


static std::shared_ptr<ComponentManager> g_ComponentManager;


void ComponentManager::Init(const IComponentManagerSettings &settings)
{
	MPT_LOG_GLOBAL(LogInformation, "Components", U_("Init"));
	// cannot use make_shared because the constructor is private
	g_ComponentManager = std::shared_ptr<ComponentManager>(new ComponentManager(settings));
}


void ComponentManager::Release()
{
	MPT_LOG_GLOBAL(LogInformation, "Components", U_("Release"));
	g_ComponentManager = nullptr;
}


std::shared_ptr<ComponentManager> ComponentManager::Instance()
{
	return g_ComponentManager;
}


ComponentManager::ComponentManager(const IComponentManagerSettings &settings)
	: m_Settings(settings)
{
	mpt::lock_guard<mpt::mutex> guard(ComponentListMutex());
	for(ComponentListEntry *entry = ComponentListHead(); entry; entry = entry->next)
	{
		if(entry->reg)
		{
			entry->reg(*this);
		}
	}
}


void ComponentManager::Register(const IComponentFactory &componentFactory)
{
	if(m_Components.find(componentFactory.GetID()) != m_Components.end())
	{
		return;
	}
	RegisteredComponent registeredComponent;
	registeredComponent.settingsKey = componentFactory.GetSettingsKey();
	registeredComponent.factoryMethod = componentFactory.GetStaticConstructor();
	registeredComponent.instance = nullptr;
	registeredComponent.weakInstance = std::weak_ptr<IComponent>();
	m_Components.insert(std::make_pair(componentFactory.GetID(), registeredComponent));
}


void ComponentManager::Startup()
{
	MPT_LOG_GLOBAL(LogDebug, "Components", U_("Startup"));
	if(m_Settings.LoadOnStartup())
	{
		for(auto &it : m_Components)
		{
			it.second.instance = it.second.factoryMethod(*this);
			it.second.weakInstance = it.second.instance;
		}
	}
	if(!m_Settings.KeepLoaded())
	{
		for(auto &it : m_Components)
		{
			it.second.instance = nullptr;
		}
	}
}


bool ComponentManager::IsComponentBlocked(const std::string &settingsKey) const
{
	if(settingsKey.empty())
	{
		return false;
	}
	return m_Settings.IsBlocked(settingsKey);
}


void ComponentManager::InitializeComponent(std::shared_ptr<IComponent> component) const
{
	if(!component)
	{
		return;
	}
	if(component->IsInitialized())
	{
		return;
	}
	component->Initialize();
}


std::shared_ptr<const IComponent> ComponentManager::GetComponent(const IComponentFactory &componentFactory)
{
	std::shared_ptr<IComponent> component = nullptr;
	auto it = m_Components.find(componentFactory.GetID());
	if(it != m_Components.end())
	{ // registered component
		if((*it).second.instance)
		{ // loaded
			component = (*it).second.instance;
		} else
		{ // not loaded
			component = (*it).second.weakInstance.lock();
			if(!component)
			{
				component = (*it).second.factoryMethod(*this);
			}
			if(m_Settings.KeepLoaded())
			{ // keep the component loaded
				(*it).second.instance = component;
			}
			(*it).second.weakInstance = component;
		}
	} else
	{ // unregistered component
		component = componentFactory.Construct(*this);
	}
	MPT_ASSERT(component);
	return component;
}


std::shared_ptr<const IComponent> ComponentManager::ReloadComponent(const IComponentFactory &componentFactory)
{
	std::shared_ptr<IComponent> component = nullptr;
	auto it = m_Components.find(componentFactory.GetID());
	if(it != m_Components.end())
	{ // registered component
		if((*it).second.instance)
		{ // loaded
			(*it).second.instance = nullptr;
			if(!(*it).second.weakInstance.expired())
			{
				throw std::runtime_error("Component not completely unloaded. Cannot reload.");
			}
			(*it).second.weakInstance = std::weak_ptr<IComponent>();
		}
		// not loaded
		component = (*it).second.factoryMethod(*this);
		if(m_Settings.KeepLoaded())
		{ // keep the component loaded
			(*it).second.instance = component;
		}
		(*it).second.weakInstance = component;
	} else
	{ // unregistered component
		component = componentFactory.Construct(*this);
	}
	MPT_ASSERT(component);
	return component;
}


std::vector<std::string> ComponentManager::GetRegisteredComponents() const
{
	std::vector<std::string> result;
	result.reserve(m_Components.size());
	for(const auto &it : m_Components)
	{
		result.push_back(it.first);
	}
	return result;
}


ComponentInfo ComponentManager::GetComponentInfo(std::string name) const
{
	ComponentInfo result;
	result.name = name;
	result.state = ComponentStateUnregistered;
	result.settingsKey = "";
	result.type = ComponentTypeUnknown;
	const auto it = m_Components.find(name);
	if(it == m_Components.end())
	{
		result.state = ComponentStateUnregistered;
		return result;
	}
	result.settingsKey = it->second.settingsKey;
	if(IsComponentBlocked(it->second.settingsKey))
	{
		result.state = ComponentStateBlocked;
		return result;
	}
	std::shared_ptr<IComponent> component = it->second.instance;
	if(!component)
	{
		component = it->second.weakInstance.lock();
	}
	if(!component)
	{
		result.state = ComponentStateUnintialized;
		return result;
	}
	result.type = component->GetType();
	if(!component->IsInitialized())
	{
		result.state = ComponentStateUnintialized;
		return result;
	}
	if(!component->IsAvailable())
	{
		result.state = ComponentStateUnavailable;
		return result;
	}
	result.state = ComponentStateAvailable;
	return result;
}


mpt::PathString ComponentManager::GetComponentPath() const
{
	return m_Settings.Path();
}


#endif // MPT_COMPONENT_MANAGER


OPENMPT_NAMESPACE_END
