    /*

    Copyright (C) 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., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    */


#include "giomanager.h"
#include "notification.h"
#include "debug.h"
#include "dispatcher.h"
#include <glib.h>

using namespace Arts;
using namespace std;

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

namespace Arts {
	struct GIOManagerSource
	{
		GSource source;
		GIOManager *ioManager;
	};
	struct GIOWatch
	{
		GPollFD gpollfd;	 /* <- must be the first data member */

	/* GPollFD =
  		gint          fd;
  		gushort       events;
  		gushort       revents;
  	*/
		GIOWatch(GIOManagerSource *source, int fd, int types, IONotify *notify)
			: types(types), registered(false), notify(notify), source(source)
		{
			gpollfd.fd = fd;
			setTypes(types);
		}
		void setTypes(int types)
		{
			this->types = types;
			gpollfd.events = gpollfd.revents = 0;

			if(types & IOType::read)
				gpollfd.events |= G_IO_IN | G_IO_HUP;
			if(types & IOType::write)
				gpollfd.events |= G_IO_OUT;
			if(types & IOType::except)
				gpollfd.events |= G_IO_ERR;

		}
		/* prepares source for running in event loop level "level"
		 * removes source unconditionally if level == -1
		 */
		void prepare(int level)
		{
			gpollfd.revents = 0;

			bool shouldRegister = true;
			if(level != 1 && (types & IOType::reentrant) == 0)
				shouldRegister = false;
			if(level == -1)
				shouldRegister = false;

			if(shouldRegister == registered)
				return;

			if(shouldRegister)
			{
				g_source_add_poll(&source->source, &this->gpollfd);
			}
			else
			{
				g_source_remove_poll(&source->source, &this->gpollfd);
			}

			registered = shouldRegister;
		}
		int check()
		{
			int result = 0;

			if(gpollfd.revents & (G_IO_IN | G_IO_HUP))
				result |= IOType::read;
			if(gpollfd.revents & G_IO_OUT)
				result |= IOType::write;
			if(gpollfd.revents & G_IO_ERR)
				result |= IOType::except;

			return result;
		}
		void destroy()
		{
			/* TODO: if active do this, else do that */
			delete this;
		}
		~GIOWatch()
		{
			prepare(-1);
		}

		int types;
		bool registered;
		IONotify *notify;
		GIOManagerSource *source;
	};

class GIOTimeWatch {
	int milliseconds;
	TimeNotify *_notify;
	timeval nextNotify;
	bool active, destroyed;
public:
	GIOTimeWatch(int milliseconds, TimeNotify *notify)
		: milliseconds(milliseconds), _notify(notify),
		  active(false),destroyed(false)
	{
		gettimeofday(&nextNotify,0);

		nextNotify.tv_usec += (milliseconds%1000)*1000;
		nextNotify.tv_sec += (milliseconds/1000)+(nextNotify.tv_usec/1000000);
		nextNotify.tv_usec %= 1000000;
	}
	int msUntilNextNotify(const timeval& now)
	{
		int result = (nextNotify.tv_sec - now.tv_sec)*1000
				   + (nextNotify.tv_usec - now.tv_usec)/1000;

		if(result < 0) result = 0;
		return result;
	}
	void advance(const timeval& currentTime)
	{
		active = true;
		while(msUntilNextNotify(currentTime) == 0)
		{
			nextNotify.tv_usec += (milliseconds%1000)*1000;
			nextNotify.tv_sec += (milliseconds/1000)
			                  +(nextNotify.tv_usec/1000000);
			nextNotify.tv_usec %= 1000000;

			_notify->notifyTime();

			if(destroyed)
			{
				delete this;
				return;
			}
		}
		active = false;
	}
	void destroy()
	{
		if(active)
		{
			destroyed = true;
		}
		else
		{
			delete this;
		}
	}
	TimeNotify *notify()
	{
		return _notify;
	}
};

static gboolean GIOManager_prepare(GSource *source, gint *timeout)
{
	return((GIOManagerSource *)source)->ioManager->prepare(timeout);
}

static gboolean GIOManager_check(GSource *source)
{
	return((GIOManagerSource *)source)->ioManager->check();
}

static gboolean GIOManager_dispatch(GSource *source, GSourceFunc callback,
											gpointer user_data)
{
	return((GIOManagerSource *)source)->ioManager->dispatch(callback,user_data);
}


}

GIOManager::GIOManager(GMainContext *context)
	: level(0), context(context)
{
	static GSourceFuncs funcs =
	{
		GIOManager_prepare,
		GIOManager_check,
		GIOManager_dispatch,
		0
	};

	source = (GIOManagerSource *)g_source_new(&funcs, sizeof(GIOManagerSource));
	source->ioManager = this;
	g_source_set_can_recurse(&source->source, true);
	g_source_attach(&source->source, context);

	gioManagerBlocking = new GIOManagerBlocking();
	_blocking = true;
	fileDescriptorsNeedRecheck = false;
}

GIOManager::~GIOManager()
{
	g_source_unref(&source->source);
	delete gioManagerBlocking;
}

void GIOManager::processOneEvent(bool blocking)
{
	if(_blocking)
	{
		level++;
		if(level == 1)
			Dispatcher::lock();

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

		if(level == 1)
			Dispatcher::unlock();
		level--;
	}
	else
	{
		g_main_context_iteration(context, blocking);
	}
}

void GIOManager::setBlocking(bool blocking)
{
	_blocking = blocking;
}

void GIOManager::run()
{
	arts_warning("GIOManager::run not implemented yet");
}

void GIOManager::terminate()
{
	arts_warning("GIOManager::terminate not implemented yet");
}

void GIOManager::watchFD(int fd, int types, IONotify * notify)
{
	fdList.push_back(new GIOWatch(source, fd, types, notify));

	if(types & IOType::reentrant)
		gioManagerBlocking->watchFD(fd, types, notify);
}

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

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

		if(w->notify == notify)
		{
			int newTypes = w->types & (~types);

			if(newTypes)
			{
				w->setTypes(newTypes);
			}
			else
			{
				w->destroy();
				fdList.erase(i);
				i = fdList.begin();
			}
		}
		else i++;
	}
	gioManagerBlocking->remove(notify, types);
}

void GIOManager::addTimer(int milliseconds, TimeNotify *notify)
{
	timeList.push_back(new GIOTimeWatch(milliseconds,notify));
}

void GIOManager::removeTimer(TimeNotify *notify)
{
	list<GIOTimeWatch *>::iterator i;

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

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

gboolean GIOManager::prepare(gint *timeout)
{
	*timeout = 10000;

	level++;

	if(level == 1)
		Dispatcher::lock();

	/* handle timers - only at level 1 */
	if(level == 1 && timeList.size())
	{
		struct timeval currenttime;
		gettimeofday(&currenttime,0);

		list<GIOTimeWatch *>::iterator ti;

		ti = timeList.begin();
		while(ti != timeList.end())
		{
			GIOTimeWatch *w = *ti++;
			int ms = w->msUntilNextNotify(currenttime);

			if(ms < *timeout) *timeout = ms;
		}
	}

	list<GIOWatch *>::iterator i;
	for(i = fdList.begin(); i != fdList.end(); i++)
	{
		GIOWatch *w = *i;
		w->prepare(level);
	}
	fileDescriptorsNeedRecheck = false;

	if(level == 1 && NotificationManager::the()->pending())
		*timeout = 0;

	if(level == 1)
		Dispatcher::unlock();

	level--;

	return (*timeout == 0);
}

gboolean GIOManager::check()
{
	gboolean result = false;
	level++;

	if(level == 1)
		Dispatcher::lock();

	/*
	 * handle timers - only at level 1
	 */
	if(level == 1 && timeList.size())
	{
		struct timeval currenttime;
		gettimeofday(&currenttime,0);

		list<GIOTimeWatch *>::iterator ti;

		ti = timeList.begin();
		while(ti != timeList.end() && !result)
		{
			GIOTimeWatch *w = *ti++;
			if(w->msUntilNextNotify(currenttime) == 0)
				result = true;
		}
	}

	/*
	 * handle filedescriptors
	 */
	list<GIOWatch *>::iterator i;
	for(i = fdList.begin(); i != fdList.end(); i++) {
		GIOWatch *w = *i;
		int match = w->check();

		if((w->types & IOType::reentrant) == 0 && level != 1)
		{
			arts_assert(match == 0);
		}
		if(match)
		{
			result = true;
		}
	}
	fileDescriptorsNeedRecheck = false;

	/*
	 * check for notifications
	 */
	if(level == 1 && NotificationManager::the()->pending())
		result = true;

	if(level == 1)
		Dispatcher::unlock();

	level--;

	return result;
}

gboolean GIOManager::dispatch(GSourceFunc /* func */, gpointer /* user_data */)
{
	bool done = false;

	level++;

	if(level == 1)
		Dispatcher::lock();

	// notifications not carried out reentrant
	if(!done && level == 1 && NotificationManager::the()->pending())
	{
		NotificationManager::the()->run();
		done = true;
	}
	
	// handle filedescriptor things
	if(!done && !fileDescriptorsNeedRecheck)
	{
		list<GIOWatch *>::iterator i;
		for(i = fdList.begin(); i != fdList.end(); i++) {
			GIOWatch *w = *i;
			int match = w->check();

			if((w->types & IOType::reentrant) == 0 && level != 1)
			{
				arts_assert(match == 0);
			}
			if(match)
			{
				w->notify->notifyIO(w->gpollfd.fd,match);
				done = true;
				break;
			}
		}
	}

	// handle timers - only at level 1
	if(!done && level == 1 && timeList.size())
	{
		struct timeval currenttime;
		gettimeofday(&currenttime,0);

		list<GIOTimeWatch *>::iterator ti;

		ti = timeList.begin();
		while(ti != timeList.end())
		{
			GIOTimeWatch *w = *ti++;
			w->advance(currenttime);
		}
	}

	if(level == 1)
		Dispatcher::unlock();
	level--;

	return true;
}
