/* This file is part of Ingen.
 * Copyright (C) 2007-2009 David 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 <cstdlib>
#include <pthread.h>
#include <dirent.h>
#include <float.h>
#include <cmath>
#include <glibmm/miscutils.h>
#include "redlandmm/World.hpp"
#include "raul/log.hpp"
#include "raul/Atom.hpp"
#include "ingen-config.h"
#include "module/World.hpp"
#include "internals/Controller.hpp"
#include "internals/Delay.hpp"
#include "internals/Note.hpp"
#include "internals/Trigger.hpp"
#include "Engine.hpp"
#include "InternalPlugin.hpp"
#include "NodeFactory.hpp"
#include "PatchImpl.hpp"
#include "ThreadManager.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;
using namespace Raul;

namespace Ingen {

using namespace Internals;


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)
		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.empty() || label.empty())
		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

	error << "Failed to find " << type << " plugin " << lib << " / " << label << endl;

	return NULL;
}


void
NodeFactory::load_plugins()
{
	ThreadManager::assert_thread(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();
}


void
NodeFactory::load_internal_plugins()
{
	Shared::LV2URIMap& uris = *_world->uris().get();
	InternalPlugin* controller_plug = ControllerNode::internal_plugin(uris);
	_plugins.insert(make_pair(controller_plug->uri(), controller_plug));

	InternalPlugin* delay_plug = DelayNode::internal_plugin(uris);
	_plugins.insert(make_pair(delay_plug->uri(), delay_plug));

	InternalPlugin* note_plug = NoteNode::internal_plugin(uris);
	_plugins.insert(make_pair(note_plug->uri(), note_plug));

	InternalPlugin* trigger_plug = TriggerNode::internal_plugin(uris);
	_plugins.insert(make_pair(trigger_plug->uri(), trigger_plug));
}


#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());

	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) {
		info << "Using default LADSPA_PATH=/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.empty()) {
		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.clear();

		DIR* pdir = opendir(dir.c_str());
		if (pdir == NULL)
			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 = Glib::build_filename(dir, pfile->d_name);

			// Ignore stupid libtool files.  Kludge alert.
			if (lib_path.substr(lib_path.length()-3) == ".la")
				continue;

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

			if (!(*plugin_library)) {
				delete plugin_library;
				continue;
			}

			bool found = plugin_library->get_symbol("ladspa_descriptor", df.dp);
			if (!found || !df.dp) {
				warn << "Non-LADSPA library " << lib_path << " found in LADSPA path" << endl;
				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("urn:ladspa:").append(id_str);

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

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

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

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

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


} // namespace Ingen