// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements the plugin factory collection
		\author Tim Shead (tshead@k-3d.com)
*/

#include "plugin_factory_collection.h"

#include <k3dsdk/iplugin_factory.h>
#include <k3dsdk/iplugin_registry.h>
#include <k3dsdk/log.h>
#include <k3dsdk/module.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/system_functions.h>

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

#include <iostream>

#if defined K3D_PLATFORM_IS_POSIX

	#include <dlfcn.h>
	#define MODULE_EXTENSION "so"

#elif defined K3D_PLATFORM_IS_OSX

	#include <dlfcn.h>
	#define MODULE_EXTENSION "dylib"

#elif defined K3D_PLATFORM_IS_WIN32

	#include <windows.h>
	#define MODULE_EXTENSION "dll"

#endif // K3D_PLATFORM_IS_WIN32

namespace k3d
{

namespace detail
{

/////////////////////////////////////////////////////////////////////////////
// same_class_id

class same_class_id
{
public:
	same_class_id(const k3d::uuid& ClassID) :
		m_class_id(ClassID)
	{
	}

	bool operator()(k3d::iplugin_factory* Factory) const
	{
		return Factory->class_id() == m_class_id;
	}

private:
	const k3d::uuid m_class_id;
};

/////////////////////////////////////////////////////////////////////////////
// same_name

class same_name
{
public:
	same_name(const std::string& Name) :
		m_name(Name)
	{
	}

	bool operator()(k3d::iplugin_factory* Factory) const
	{
		return Factory->name() == m_name;
	}

private:
	const std::string m_name;
};

/////////////////////////////////////////////////////////////////////////////
// plugin_registry

/// Implements k3d::iplugin_registry, pushing registered plugin factory references into a vector
class plugin_registry :
	public k3d::iplugin_registry
{
public:
	plugin_registry(k3d::plugin_factory_collection::message_signal_t& MessageSignal, k3d::iplugin_factory_collection::factories_t& Factories) :
		m_message_signal(MessageSignal),
		m_factories(Factories)
	{
	}

	void register_factory(k3d::iplugin_factory& Factory)
	{
		m_message_signal.emit("Registering plugin " + Factory.name());

		// Ensure we don't have any duplicate class IDs ...
		if(std::find_if(m_factories.begin(), m_factories.end(), same_class_id(Factory.class_id())) != m_factories.end())
			{
				std::cerr << error << "Plugin " << Factory.name() << " with duplicate class ID " << Factory.class_id() << " will not be loaded" << std::endl;
				return;
			}

		// Warn if we have duplicate names ...
		if(std::find_if(m_factories.begin(), m_factories.end(), same_name(Factory.name())) != m_factories.end())
			std::cerr << warning << "Loading plugin with duplicate name " << Factory.name() << std::endl;

		// Store  a reference to the factory ...
		m_factories.insert(&Factory);
	}

private:
	k3d::plugin_factory_collection::message_signal_t& m_message_signal;
	k3d::iplugin_factory_collection::factories_t& m_factories;
};

/////////////////////////////////////////////////////////////////////////////
// load_module

/// For each candidate plugin module, looks for the required entry point, loads the module, and registers available module plugin factories
class load_module
{
public:
	load_module(k3d::plugin_factory_collection::message_signal_t& MessageSignal, k3d::iplugin_factory_collection::factories_t& Factories) :
		m_message_signal(MessageSignal),
		m_factories(Factories)
	{
	}

	void handle_file(const boost::filesystem::path& FilePath)
	{
		if(k3d::file_extension(FilePath) != MODULE_EXTENSION)
			return;

#if defined K3D_PLATFORM_IS_POSIX || defined K3D_PLATFORM_IS_OSX

		void* module = dlopen(FilePath.native_file_string().c_str(), RTLD_GLOBAL | RTLD_LAZY);
		if(!module)
			{
				std::cerr << error << "Library " << FilePath.leaf() << ": " << dlerror() << std::endl;
				return;
			}

		k3d::register_module_entry_point register_module = k3d::register_module_entry_point(dlsym(module, "register_k3d_module"));
		k3d::register_plugins_entry_point register_plugins = k3d::register_plugins_entry_point(dlsym(module, "register_k3d_plugins"));

#elif defined K3D_PLATFORM_IS_WIN32

		HINSTANCE module = LoadLibrary(FilePath.native_file_string().c_str());
		if(!module)
			{
				std::cerr << error << "Library " << FilePath.leaf() << ": could not be loaded" << std::endl;
				return;
			}

		k3d::register_module_entry_point register_module = k3d::register_module_entry_point(GetProcAddress(module, "register_k3d_module"));
		k3d::register_plugins_entry_point register_plugins = k3d::register_plugins_entry_point(GetProcAddress(module, "register_k3d_plugins"));

#endif // K3D_PLATFORM_IS_WIN32

		if(!register_module)
			return;

		if(!register_plugins)
			{
				std::cerr << error << "Module " << FilePath.leaf() << " does not contain required register_k3d_plugins() entry point" << std::endl;
				return;
			}

		// Ensure that we aren't loading the same module twice by another name ... 'twould smell as sweet
		const k3d::uuid module_id = register_module();
		if(m_modules.count(module_id))
			{
				std::cerr << info << "Skipping duplicate module " << FilePath.leaf() << std::endl;
				return;
			}
		m_modules.insert(module_id);

		// It's a K-3D module, all-right - give it a chance to register its plugins
		m_message_signal.emit("Loading plugin module " + FilePath.leaf());
		plugin_registry registry(m_message_signal, m_factories);
		register_plugins(registry);
	}

private:
	k3d::plugin_factory_collection::message_signal_t& m_message_signal;
	k3d::iplugin_factory_collection::factories_t& m_factories;
	std::set<k3d::uuid> m_modules;
};

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// plugin_factory_collection

plugin_factory_collection::message_signal_t& plugin_factory_collection::message_signal()
{
	return m_message_signal;
}

template<class Type>
void for_each_path(const boost::filesystem::path& StartDirectory, Type& Functor, const bool recursive)
{
	for(boost::filesystem::directory_iterator f(StartDirectory); f != boost::filesystem::directory_iterator(); ++f)
		{
			if(boost::filesystem::is_directory(*f) && recursive)
				for_each_path(*f, Functor, true);

			Functor.handle_file(*f);
		}
}

void plugin_factory_collection::load_modules(const std::string& Paths, bool Recursive)
{
	k3d::system::paths_t paths;
	k3d::system::decompose_path_list(Paths, paths);

	for(k3d::system::paths_t::const_iterator path = paths.begin(); path != paths.end(); ++path)
		{
			m_message_signal.emit("Searching for plugins in " + path->native_file_string());

			detail::load_module functor(m_message_signal, m_factories);
			for_each_path(*path, functor, Recursive);
		}
}

const iplugin_factory_collection::factories_t& plugin_factory_collection::factories()
{
	return m_factories;
}

} // namespace k3d


