    /*

    Copyright (C) 1999-2001 Stefan Westerfeld
                            stefan@space.twc.de

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
   
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

    */

#include "qiomanager.h"
#include "qiomanager_p.h"
#include <qsocketnotifier.h>
#include <qapplication.h>
#include "debug.h"
#include "dispatcher.h"
#include "thread.h"

using namespace std;
using namespace Arts;

/* 
 * Collected incompatibilities of QIOManager (compared against StdIOManager):
 *
 * - StdIOManager catches up timers (i.e. if a 100ms timer hasn't been
 *   notified for half a second, it will get notified five times to
 *   catch up the lost time
 * - StdIOManager is able to watch the same filedescriptor twice for reading
 * - StdIOManager sends notifications soon after they have been produced,
 *   whereas we use a hackish 50ms timer to deliver them
 */

/*
 * Fallback for the case where we should perform blocking
 */
namespace Arts {
class QIOManagerBlocking : public StdIOManager {
public:
	void setLevel(int newLevel) { level = newLevel; }
};
}

/*
 * QIOManager is a singleton (or at least supposed to be used only once at
 * most, and the behaviour would pretty undefined if you violate this), so we
 * use static variables for private data here.
 */
static int qioLevel;
static QIOManager *qioManager = 0;
static QIOManagerBlocking *qioManagerBlocking = 0;
static bool qioBlocking;

/*
 * QIOWatch:
 */
QIOWatch::QIOWatch(int fd, int type, IONotify *notify,
	QSocketNotifier::Type qtype, bool reentrant)
	: _fd(fd), _type(type), _client(notify), _reentrant(reentrant)
{
	qsocketnotify = new QSocketNotifier(fd,qtype,this);
	connect(qsocketnotify,SIGNAL(activated(int)),this,SLOT(notify(int)));
}

void QIOWatch::notify(int socket)
{
	arts_assert(socket == _fd);
	qioManager->dispatch(this);
}

/*
 * QTimeWatch:
 */
QTimeWatch::QTimeWatch(int milliseconds, TimeNotify *notify)
{
	timer = new QTimer(this);
	connect( timer, SIGNAL(timeout()), this, SLOT(notify()) );
	timer->start(milliseconds);
	_client = notify;
}

void QTimeWatch::notify()
{
	qioManager->dispatch(this);
}

/*
 * Handle NotificationManager::the()->run() from time to time
 */
namespace Arts {

class HandleNotifications : public TimeNotify {
public:
	void notifyTime()
	{
		Arts::Dispatcher::the()->ioManager()->removeTimer(this);
		NotificationManager::the()->run();
		delete this;
	}
};
}

/*
 * QIOManager:
 */
QIOManager::QIOManager()
{
	assert(!qioManager);
	qioManager = this;
	qioLevel = 0;
	qioBlocking = true;
	qioManagerBlocking = new QIOManagerBlocking();
}

QIOManager::~QIOManager()
{
	assert(qioManager);
	qioManager = 0;

	delete qioManagerBlocking;
	qioManagerBlocking = 0;
}

void QIOManager::processOneEvent(bool blocking)
{
	assert(SystemThreads::the()->isMainThread());

	if(qioBlocking)
	{
		qioLevel++;
		if(qioLevel == 1)
			Dispatcher::lock();

		/*
		 * we explicitly take the level to qioManagerBlocking, so that it
		 * will process reentrant watchFDs only
		 */
		qioManagerBlocking->setLevel(qioLevel);
		qioManagerBlocking->processOneEvent(blocking);

		if(qioLevel == 1)
			Dispatcher::unlock();
		qioLevel--;
	}
	else
	{
		if(blocking)
			qApp->processOneEvent();
		else
			qApp->processEvents(0);
	}
}

void QIOManager::run()
{
	arts_warning("QIOManager::run() not implemented.");
}

void QIOManager::terminate()
{
	arts_warning("QIOManager::terminate() not implemented.");
}

void QIOManager::watchFD(int fd, int types, IONotify *notify)
{
	bool r = (types & IOType::reentrant) != 0;

	if(types & IOType::read)
	{
		fdList.push_back(
			new QIOWatch(fd, IOType::read, notify, QSocketNotifier::Read, r)
		);
	}
	if(types & IOType::write)
	{
		fdList.push_back(
			new QIOWatch(fd, IOType::write, notify, QSocketNotifier::Write, r)
		);
	}
	if(types & IOType::except)
	{
		fdList.push_back(
			new QIOWatch(fd, IOType::except, notify, QSocketNotifier::Exception,
						 r)
		);
	}
	if(r) qioManagerBlocking->watchFD(fd, types, notify);
}

void QIOManager::remove(IONotify *notify, int types)
{
	list<QIOWatch *>::iterator i;

	i = fdList.begin();
	while(i != fdList.end())
	{
		QIOWatch *w = *i;

		if(w->type() & types && w->client() == notify)
		{
			delete w;
			fdList.erase(i);
			i = fdList.begin();
		}
		else i++;
	}
	qioManagerBlocking->remove(notify, types);
}

void QIOManager::addTimer(int milliseconds, TimeNotify *notify)
{
	if (milliseconds == -1 && notify == 0)
	{
		// HACK: in order to not add a virtual function to IOManager we're calling addTimer with
		// magic values. This call tells the ioManager that notifications are pending and
		// NotificationManager::run() should get called soon.
		notify = new HandleNotifications();
		milliseconds = 0;
	}
	timeList.push_back(new QTimeWatch(milliseconds,notify));
}

void QIOManager::removeTimer(TimeNotify *notify)
{
	list<QTimeWatch *>::iterator i;

	i = timeList.begin();
	while(i != timeList.end())
	{
		QTimeWatch *w = *i;

		if(w->client() == notify)
		{
			delete w;
			timeList.erase(i);
			i = timeList.begin();
		}
		else i++;
	}
}

void QIOManager::dispatch(QIOWatch *ioWatch)
{
	qioLevel++;
	if(qioLevel == 1)
		Dispatcher::lock();

	/*
	 * FIXME: there is main loop pollution for (qioBlocking == false) here:
	 *
	 * As QIOManager will never disable the socket notifiers that are not
	 * to be carried out reentrant, these will (maybe) fire again and again,
	 * so that CPU is wasted.
	 */
	if(qioLevel == 1 || ioWatch->reentrant())
		ioWatch->client()->notifyIO(ioWatch->fd(),ioWatch->type());
	
	if(qioLevel == 1)
		Dispatcher::unlock();
	qioLevel--;
}

void QIOManager::dispatch(QTimeWatch *timeWatch)
{
	qioLevel++;
	if(qioLevel == 1)
		Dispatcher::lock();

	// timers are never done reentrant
	if(qioLevel == 1)
		timeWatch->client()->notifyTime();
	
	if(qioLevel == 1)
		Dispatcher::unlock();
	qioLevel--;
}

bool QIOManager::blocking()
{
	return qioBlocking;
}

void QIOManager::setBlocking(bool blocking)
{
	qioBlocking = blocking;
}

#include "qiomanager_p.moc"
