/* This file is part of Ingen.
 * Copyright (C) 2007-2009 Dave Robillard <http://drobilla.net>
 *
 * Ingen 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.
 *
 * Ingen 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 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.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "ingen-config.h"
#include <cstdlib>
#include <pthread.h>
#include <dirent.h>
#include <float.h>
#include <cmath>
#include "redlandmm/World.hpp"
#include "raul/Atom.hpp"
#include "module/World.hpp"
#include "NodeFactory.hpp"
#include "ThreadManager.hpp"
#include "InternalNote.hpp"
#include "InternalTrigger.hpp"
#include "InternalController.hpp"
#include "InternalTransport.hpp"
#include "PatchImpl.hpp"
#include "InternalPlugin.hpp"
#ifdef HAVE_LADSPA_H
#include "LADSPANode.hpp"
#include "LADSPAPlugin.hpp"
#endif
#ifdef HAVE_SLV2
#include "slv2/slv2.h"
#include "LV2Plugin.hpp"
#include "LV2Node.hpp"
#endif

using namespace std;

namespace Ingen {


NodeFactory::NodeFactory(Ingen::Shared::World* world)
	: _world(world)
	, _has_loaded(false)
#ifdef HAVE_SLV2
	, _lv2_info(new LV2Info(world))
#endif
{
}


NodeFactory::~NodeFactory()
{
	for (Plugins::iterator i = _plugins.begin(); i != _plugins.end(); ++i)
		if (i->second->type() != Shared::Plugin::Internal)
			delete i->second;

	_plugins.clear();
}


PluginImpl*
NodeFactory::plugin(const Raul::URI& uri)
{
	const Plugins::const_iterator i = _plugins.find(uri);
	return ((i != _plugins.end()) ? i->second : NULL);
}


/** DEPRECATED: Find a plugin by type, lib, label.
 *
 * Slow.  Evil.  Do not use.
 */
PluginImpl*
NodeFactory::plugin(const string& type, const string& lib, const string& label)
{
	if (type != "LADSPA" || lib == "" || label == "")
		return NULL;

#ifdef HAVE_LADSPA_H
	for (Plugins::const_iterator i = _plugins.begin(); i != _plugins.end(); ++i) {
		LADSPAPlugin* lp = dynamic_cast<LADSPAPlugin*>(i->second);
		if (lp && lp->library_name() == lib
				&& lp->label() == label)
			return lp;
	}
#endif

	cerr << "ERROR: Failed to find " << type << " plugin " << lib << " / " << label << endl;

	return NULL;
}


void
NodeFactory::load_plugins()
{
	assert(ThreadManager::current_thread_id() == THREAD_PRE_PROCESS);

	_world->rdf_world->mutex().lock();

	// Only load if we havn't already, so every client connecting doesn't cause
	// this (expensive!) stuff to happen.  Not the best solution - would be nice
	// if clients could refresh plugins list for whatever reason :/
	if (!_has_loaded) {
		_plugins.clear(); // FIXME: assert empty?

		load_internal_plugins();

#ifdef HAVE_SLV2
		load_lv2_plugins();
#endif

#ifdef HAVE_LADSPA_H
		load_ladspa_plugins();
#endif

		_has_loaded = true;
	}

	_world->rdf_world->mutex().unlock();

	//cerr << "[NodeFactory] # Plugins: " << _plugins.size() << endl;
}


void
NodeFactory::load_internal_plugins()
{
	// This is a touch gross...

	PatchImpl* parent = new PatchImpl(*_world->local_engine, "dummy", 1, NULL, 1, 1, 1);

	NodeImpl* n = NULL;
	n = new NoteNode("foo", 1, parent, 1, 1);
	_plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
	delete n;
	n = new TriggerNode("foo", 1, parent, 1, 1);
	_plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
	delete n;
	n = new ControllerNode("foo", 1, parent, 1, 1);
	_plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
	delete n;
	n = new TransportNode("foo", 1, parent, 1, 1);
	_plugins.insert(make_pair(n->plugin_impl()->uri(), n->plugin_impl()));
	delete n;

	delete parent;
}


#ifdef HAVE_SLV2
/** Loads information about all LV2 plugins into internal plugin database.
 */
void
NodeFactory::load_lv2_plugins()
{
	SLV2Plugins plugins = slv2_world_get_all_plugins(_world->slv2_world);

	//cerr << "[NodeFactory] Found " << slv2_plugins_size(plugins) << " LV2 plugins:" << endl;

	for (unsigned i=0; i < slv2_plugins_size(plugins); ++i) {

		SLV2Plugin lv2_plug = slv2_plugins_get_at(plugins, i);

		const string uri(slv2_value_as_uri(slv2_plugin_get_uri(lv2_plug)));

#ifndef NDEBUG
		assert(_plugins.find(uri) == _plugins.end());
#endif

		LV2Plugin* const plugin = new LV2Plugin(_lv2_info, uri);

		plugin->slv2_plugin(lv2_plug);
		_plugins.insert(make_pair(uri, plugin));
	}

	slv2_plugins_free(_world->slv2_world, plugins);
}
#endif // HAVE_SLV2


#ifdef HAVE_LADSPA_H
/** Loads information about all LADSPA plugins into internal plugin database.
 */
void
NodeFactory::load_ladspa_plugins()
{
	char* env_ladspa_path = getenv("LADSPA_PATH");
	string ladspa_path;
	if (!env_ladspa_path) {
	 	cerr << "[NodeFactory] LADSPA_PATH is empty.  Assuming /usr/lib/ladspa:/usr/local/lib/ladspa:~/.ladspa" << endl;
		ladspa_path = string("/usr/lib/ladspa:/usr/local/lib/ladspa:").append(
			getenv("HOME")).append("/.ladspa");
	} else {
		ladspa_path = env_ladspa_path;
	}

	// Yep, this should use an sstream alright..
	while (ladspa_path != "") {
		const string dir = ladspa_path.substr(0, ladspa_path.find(':'));
		if (ladspa_path.find(':') != string::npos)
			ladspa_path = ladspa_path.substr(ladspa_path.find(':')+1);
		else
			ladspa_path = "";

		DIR* pdir = opendir(dir.c_str());
		if (pdir == NULL) {
			//cerr << "[NodeFactory] Unreadable directory in LADSPA_PATH: " << dir.c_str() << endl;
			continue;
		}

		struct dirent* pfile;
		while ((pfile = readdir(pdir))) {
			union {
				void*                      dp;
				LADSPA_Descriptor_Function fp;
			} df;
			df.dp = NULL;
			df.fp = NULL;

			LADSPA_Descriptor* descriptor = NULL;

			if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, ".."))
				continue;

			const string lib_path = dir +"/"+ pfile->d_name;

			// Ignore stupid libtool files.  Kludge alert.
			if (lib_path.substr(lib_path.length()-3) == ".la") {
				//cerr << "WARNING: Skipping stupid libtool file " << pfile->d_name << endl;
				continue;
			}

			Glib::Module* plugin_library = new Glib::Module(lib_path, Glib::MODULE_BIND_LOCAL);
			if (!plugin_library || !(*plugin_library))
				continue;

			bool found = plugin_library->get_symbol("ladspa_descriptor", df.dp);
			if (!found || !df.dp) {
				cerr << "WARNING: Non-LADSPA library found in LADSPA path: " <<
					lib_path << endl;
				// Not a LADSPA plugin library
				delete plugin_library;
				continue;
			}

			for (unsigned long i=0; (descriptor = (LADSPA_Descriptor*)df.fp(i)) != NULL; ++i) {
				char id_str[11];
				snprintf(id_str, 11, "%lu", descriptor->UniqueID);
				const string uri = string("ladspa:").append(id_str);

				const Plugins::const_iterator i = _plugins.find(uri);

				if (i == _plugins.end()) {
					LADSPAPlugin* plugin = new LADSPAPlugin(lib_path, uri,
						descriptor->UniqueID,
						descriptor->Label,
						descriptor->Name);

					_plugins.insert(make_pair(uri, plugin));

				} else {
					cerr << "Warning: Duplicate " << uri
					     << " - Using " << i->second->library_path()
					     << " over " << lib_path << endl;
				}
			}

			delete plugin_library;
		}
		closedir(pdir);
	}
}
#endif // HAVE_LADSPA_H


} // namespace Ingen