/*
 KMix -- KDE's full featured mini mixer
 Copyright (C) 2012  Christian Esken <esken@kde.org>

 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.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "ControlManager.h"
#include "core/GlobalConfig.h"

#include <QMutableListIterator>

#include <KDebug>


ControlManager ControlManager::instanceSingleton;

ControlManager& ControlManager::instance()
{
	return instanceSingleton;
}

ControlManager::ControlManager()
{
	listenersChanged = false;
}

/**
 * Announce a change for one or all mixers.
 *
 * @param mixerId The mixerId. Use an empty QString() to announce a change for all mixers
 * @param changeType A bit array of ControlChangeType flags
 * @param sourceId Only for logging
 *
 */
void ControlManager::announce(QString mixerId, ControlChangeType::Type changeType, QString sourceId)
{

	bool listenersModified = false;
	QSet<Listener*> processedListeners;
	do
	{
		listenersModified = false;
		QList<Listener>::iterator it;
		for (it = listeners.begin(); it != listeners.end(); ++it)
		{
			Listener& listener = *it;
			if ( &listener == 0 )
			{
				kWarning() << "null Listener detected ... skipping";
				continue;
			}

			bool mixerIsOfInterest = listener.getMixerId().isEmpty() || mixerId.isEmpty()
				|| listener.getMixerId() == mixerId;

			bool listenerAlreadyProcesed = processedListeners.contains(&listener);
			if ( listenerAlreadyProcesed )
			{
				if (GlobalConfig::instance().data.debugControlManager)
					kDebug() << "Skipping already processed listener";
				continue;
			}
			if (mixerIsOfInterest && listener.getChangeType() == changeType)
			{
				bool success = QMetaObject::invokeMethod(listener.getTarget(), "controlsChange", Qt::DirectConnection,
				Q_ARG(int, changeType));
				if (GlobalConfig::instance().data.debugControlManager)
				{
					kDebug() << "Listener " << listener.getSourceId() <<" is interested in " << mixerId
				<< ", " << ControlChangeType::toString(changeType);
				}

				if (!success)
				{
					kError() << "Listener Failed to send to " << listener.getTarget()->metaObject()->className();
				}
				processedListeners.insert(&listener);
				if (listenersChanged)
				{
					// The invokeMethod() above has changed the listeners => my Iterator is invalid => restart loop
					if (GlobalConfig::instance().data.debugControlManager)
						kDebug() << "Listeners modified => restart loop";
					listenersChanged = false;
					listenersModified = true;
					break; // break inner loop => restart via outer loop
				}

			}
		}
	}
	while ( listenersModified);

	if (GlobalConfig::instance().data.debugControlManager)
	{
		kDebug()
		<< "Announcing " << ControlChangeType::toString(changeType) << " for "
		<< (mixerId.isEmpty() ? "all cards" : mixerId) << " by " << sourceId;
	}
}

/**
 * Adds a listener for the given mixerId and changeType.
 * Listeners are informed about all corresponding changes via a signal.
 * Listeners are not informed about changes that originate from oneself (according to sourceId).
 *
 * @param mixerId The id of the Mixer you are interested in
 * @param changetType The changeType of interest
 * @param target The QObject, where the notification signal is sent to. It must implement the SLOT controlChanged(QString mixerId, ControlChangeType::Type changeType).
 * @param sourceId Only for logging
 */
void ControlManager::addListener(QString mixerId, ControlChangeType::Type changeType, QObject* target, QString sourceId)
{
	if (GlobalConfig::instance().data.debugControlManager)
	{
		kDebug()
		<< "Listening to " << ControlChangeType::toString(changeType) << " for "
		<< (mixerId.isEmpty() ? "all cards" : mixerId) << " by " << sourceId << ". Announcements are sent to "
		<< target;
	}

	for ( ControlChangeType::Type ct = ControlChangeType::TypeFirst; ct != ControlChangeType::TypeLast;  ct = (ControlChangeType::Type)(ct << 1))
	{
		if ( changeType & ct )
		{
			// Add all listeners.
			Listener listener = Listener(mixerId, ct, target, sourceId);
			listeners.append(listener);
			listenersChanged = true;
		}
	}
	if (GlobalConfig::instance().data.debugControlManager)
	{
		kDebug()
		<< "We now have" << listeners.size() << "listeners";
	}
}

/**
 * Removes all listeners of the given target.
 * @param target The QObject that was used to register via addListener()
 */
void ControlManager::removeListener(QObject* target)
{
	ControlManager::instance().removeListener(target, target->metaObject()->className());
}

/**
 * Removes all listeners of the given target.
 * @param target The QObject that was used to register via addListener()
 * @param sourceId Optional: Only for logging
 */
void ControlManager::removeListener(QObject* target, QString sourceId)
{
	QMutableListIterator<Listener> it(listeners);
	while ( it.hasNext())
	{
		Listener& listener = it.next();
		if (listener.getTarget() == target)
		{
			if (GlobalConfig::instance().data.debugControlManager)
				kDebug()
				<< "Stop Listening of " << listener.getSourceId() << " requested by " << sourceId << " from " << target;
			it.remove();
			// Hint: As we have actual objects no explicit delete is needed
			listenersChanged = true;
		}
	}
}

void ControlManager::warnUnexpectedChangeType(ControlChangeType::Type type, QObject *obj)
{
	kWarning() << "Unexpected type " << type << " received by " << obj->metaObject()->className();
}

void ControlManager::shutdownNow()
{
	if (GlobalConfig::instance().data.debugControlManager)
		kDebug() << "Shutting down ControlManager";
	QList<Listener>::iterator it;
	for (it = listeners.begin(); it != listeners.end(); ++it)
	{
		Listener& listener = *it;
		if (GlobalConfig::instance().data.debugControlManager)
			kDebug()
			<< "Listener still connected. Closing it. source=" << listener.getSourceId() << "listener="
			<< listener.getTarget()->metaObject()->className();
	}
}

