#include <tqfile.h>
#include <tdeglobal.h>
#include <tqdir.h>
#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <knotifyclient.h>
#include <tdelocale.h>
#include <kurl.h>
#include <pluginloader.h>
#include <plugin.h>
#include <kdebug.h>

bool operator ==(const NoatunLibraryInfo &a, const NoatunLibraryInfo &b)
{
	// Feels like cheating, doesn't it?
	return a.specfile == b.specfile;
}

LibraryLoader::LibraryLoader() : mPlaylist(0)
{
}

LibraryLoader::~LibraryLoader()
{
	TQValueList<NoatunLibraryInfo> l;

	l = loaded();
	for(TQValueList<NoatunLibraryInfo>::Iterator i = l.begin(); i != l.end(); ++i)
	{
		if((*i).type != "userinterface" && (*i).type != "playlist" && (*i).type != "systray")
		{
			removeNow((*i).specfile);
		}
	}

	l = loaded();
	for(TQValueList<NoatunLibraryInfo>::Iterator i = l.begin(); i != l.end(); ++i)
	{
		if((*i).type == "userinterface")
		{
			removeNow((*i).specfile);
		}
	}

	l = loaded();
	for(TQValueList<NoatunLibraryInfo>::Iterator i = l.begin(); i != l.end(); ++i)
	{
		removeNow((*i).specfile);
	}
}

TQValueList<NoatunLibraryInfo> LibraryLoader::available() const
{
	TQValueList<NoatunLibraryInfo> items;
	TQStringList files=TDEGlobal::dirs()->findAllResources("appdata", "*.plugin", false, true);
	for (TQStringList::Iterator i=files.begin(); i!=files.end(); ++i)
		items.append(getInfo(*i));

	return items;
}

TQPtrList<Plugin> LibraryLoader::plugins() const
{
	TQPtrList<Plugin> list;
	for (TQDictIterator<LibraryLoader::PluginLibrary> i(mLibHash); i.current(); ++i)
		list.append(i.current()->plugin);
	return list;
}

bool LibraryLoader::loadAll()
{
	TDEConfig *config=TDEGlobal::config();
	config->setGroup("");
	TQStringList modules = config->readListEntry("Modules");
	return loadAll(modules);
}

bool LibraryLoader::loadAll(const TQStringList &modules)
{
	// Session management...
	for(TQStringList::ConstIterator i=modules.begin(); i!=modules.end(); ++i)
	{
		NoatunLibraryInfo info=getInfo(*i);
		if (!info.type.contains("sm"))
			continue;
		loadSO(*i);
	}

	// load all the playlists in the first
	for(TQStringList::ConstIterator i=modules.begin(); i!=modules.end(); ++i)
	{
		NoatunLibraryInfo info=getInfo(*i);
		if (!info.type.contains("playlist"))
			continue;
		loadSO(*i);
	}

	if (!mPlaylist)
	{
		kdWarning(66666) << "No playlist plugin loaded, defaulting to splitplaylist" << endl;
		if (!loadSO("splitplaylist.plugin"))
			return false;
	}

	// load all the user interfaces now
	for(TQStringList::ConstIterator i=modules.begin(); i!=modules.end(); ++i)
	{
		NoatunLibraryInfo info=getInfo(*i);
		if (!info.type.contains("userinterface"))
			continue;
		loadSO(*i);
	}

	if (!loadedByType("userinterface").count())
	{
		kdWarning(66666) << "No userinterface plugin loaded, defaulting to excellent" << endl;
		if (!loadSO("excellent.plugin"))
			return false;
	}

	for(TQStringList::ConstIterator i=modules.begin(); i!=modules.end(); ++i)
	{
		NoatunLibraryInfo info=getInfo(*i);
		if((!info.type.contains("playlist"))
		&& (!info.type.contains("userinterface"))
		&& (!info.type.contains("sm")))
		{
			loadSO(*i);
		}
	}

	return true;
}

NoatunLibraryInfo LibraryLoader::getInfo(const TQString &spec) const
{
	NoatunLibraryInfo info;
	TQString specPath = (spec[0]=='/') ? spec : TDEGlobal::dirs()->findResource("appdata", spec);
	if (!TQFile::exists(specPath))
		return info;
	KSimpleConfig file(specPath);
	if (spec.find('/')>=0)
		info.specfile=KURL(spec).fileName();
	else
		info.specfile=spec;
	info.filename=file.readPathEntry("Filename");
	info.author=file.readEntry("Author");
	info.site=file.readEntry("Site");
	info.email=file.readEntry("Email");
	info.type=file.readEntry("Type");
	info.name=file.readEntry("Name");
	info.comment=file.readEntry("Comment");
	info.require=file.readListEntry("Require");
	info.license=file.readEntry("License");
	return info;
}

bool LibraryLoader::isLoaded(const TQString &spec) const
{
	PluginLibrary *lib=mLibHash[spec];
	if (!lib) return false;
	return lib->plugin;
}

bool LibraryLoader::loadSO(const TQString &spec)
{
	if(!isLoaded(spec))
	{
		NoatunLibraryInfo info = getInfo(spec);
		if (info.specfile != spec)
			return false;

		for (TQStringList::ConstIterator it = info.require.begin(); it != info.require.end(); ++it)
			loadSO(*it);

		// get the library loader instance
		KLibLoader *loader = KLibLoader::self();

		PluginLibrary *listitem=mLibHash[spec];

		if (!listitem)
		{
			TQString filename = TDEGlobal::dirs()->findResource("module", info.filename);
			KLibrary *lib = loader->library(TQFile::encodeName(filename));
			if (!lib)
				return false;
			listitem=new PluginLibrary;
			listitem->library=lib;
			mLibHash.insert(spec, listitem);
		}

		void *create=listitem->library->symbol("create_plugin");
		if (!create)
			return false;

		Plugin* (*plugInStart)();
		plugInStart=(Plugin* (*)()) create;
		listitem->plugin=plugInStart();

		if (info.type.contains("playlist"))
		{
			//kdDebug(66666) << k_funcinfo << "Assigning mPlaylist to " << info.name << endl;
			mPlaylist=listitem->plugin->playlist();
		}
		listitem->plugin->init();

		return true;
	}
	else
		return false;
}

void LibraryLoader::add(const TQString &spec)
{
	PluginLibrary *lib=mLibHash[spec];
	if (lib)
		if (lib->plugin) return;

	loadSO(spec);
}

void LibraryLoader::setModules(const TQStringList &mods)
{
	TDEConfig *config=TDEGlobal::config();
	config->setGroup(0);
	config->writeEntry("Modules", mods);
	config->sync();
}

bool LibraryLoader::remove(const TQString& spec, bool terminateOnLastUI)
{
	bool SystrayPluginEnabled=false;

	NoatunLibraryInfo info = getInfo(spec);
	// exit if this is the last UI
	if (info.type=="userinterface" && terminateOnLastUI)
	{
		TQValueList<NoatunLibraryInfo> l=loaded();

		// Iterate over other plugins
		for (TQValueList<NoatunLibraryInfo>::Iterator i=l.begin(); i!=l.end(); ++i)
		{
			// Is this a UI plugin?
			if ((*i).specfile!=spec && (*i).type=="userinterface")
			{
				// Good, we don't have to exit
				removeNow(spec);
				return true;
			}
			else if((*i).type=="systray") // Is there a Systray plugin?
			{
				SystrayPluginEnabled=true;
			}
		}

		// Don't terminate, when there is a systray plugin.
		if(SystrayPluginEnabled)
		{
			napp->toggleInterfaces();
			return true;
		}
		else
		{
			// No other UIs, terminate
			kapp->exit();
		}
	}
	else if (info.type=="playlist")
	{
		//kdWarning(66666) << "Unloading playlist, this might be dangerous" << endl;
		mPlaylist=0;
	}

	removeNow(spec);
	return true;
}

bool LibraryLoader::remove(const TQString &spec)
{
	remove(spec, true);
	return true;
}

bool LibraryLoader::remove(const PluginLibrary *pl)
{
	for (TQDictIterator<PluginLibrary> i(mLibHash); i.current(); ++i)
	{
		if (i.current()==pl)
			return remove(i.currentKey());
	}
	return false;
}

bool LibraryLoader::remove(const Plugin *plugin)
{
	for (TQDictIterator<PluginLibrary> i(mLibHash); i.current(); ++i)
	{
		if (i.current()->plugin==plugin)
			return remove(i.currentKey());
	}
	return false;

}

Playlist *LibraryLoader::playlist() const
{
	// playlist should never be NULL when this method is called
	Q_ASSERT(mPlaylist);
	return mPlaylist;
}

TQValueList<NoatunLibraryInfo> LibraryLoader::loaded() const
{
	TQValueList<NoatunLibraryInfo> items;

	for (TQDictIterator<PluginLibrary> i(mLibHash); i.current(); ++i)
		if (isLoaded(i.currentKey()))
			items.append(getInfo(i.currentKey()));

	return items;
}

TQValueList<NoatunLibraryInfo> LibraryLoader::loadedByType(const TQString &type) const
{
	TQValueList<NoatunLibraryInfo> items;

	for (TQDictIterator<PluginLibrary> i(mLibHash); i.current(); ++i)
	{
		if (isLoaded(i.currentKey()))
		{
			NoatunLibraryInfo info = getInfo(i.currentKey());
			if (info.type.contains(type))
				items.append(info);
		}
	}

	return items;
}

void LibraryLoader::removeNow(const TQString &spec)
{
	NoatunLibraryInfo info = getInfo(spec);
	if (info.specfile == spec)
	{
		TQValueList<NoatunLibraryInfo> l = loaded();
		for (TQValueList<NoatunLibraryInfo>::Iterator i = l.begin(); i != l.end(); ++i)
		{
			for (TQStringList::ConstIterator it = (*i).require.begin(); it != (*i).require.end(); ++it)
			{
				if (*it == spec)
					removeNow((*i).specfile);
			}
		}
	}

	PluginLibrary *lib=mLibHash[spec];

	if (!lib) return;

	delete lib->plugin;
	lib->plugin=0;
	mLibHash.remove(spec);
	delete lib;
}


