/*	
 * Copyright (c) 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>,
 * Copyright (c) 2000-2008, Stephan Aßmus <superstippi@gmx.de>,
 * All Rights Reserved. Distributed under the terms of the MIT license.
 */
#include <new>
#include <stdio.h>

#include "Event.h"

#include "EventQueue.h"

using std::nothrow;


EventQueue::EventQueue()
	: fEvents(100),
	  fEventExecutor(-1),
	  fThreadControl(-1),
	  fNextEventTime(0),
	  fStatus(B_ERROR)
	  
{
	fThreadControl = create_sem(0, "event queue control");
	if (fThreadControl >= B_OK)
		fStatus = B_OK;
	else
		fStatus = fThreadControl;
	if (fStatus == B_OK) {
		fEventExecutor = spawn_thread(_execute_events_, "event queue runner",
									  B_NORMAL_PRIORITY, this);
		if (fEventExecutor >= B_OK) {
			fStatus = B_OK;
			resume_thread(fEventExecutor);
		} else
			fStatus = fEventExecutor;
	}
}


EventQueue::~EventQueue()
{
	if (delete_sem(fThreadControl) == B_OK)
		wait_for_thread(fEventExecutor, &fEventExecutor);
	while (Event *event = (Event*)fEvents.RemoveItem((int32)0)) {
		if (event->AutoDelete())
			delete event;
	}
}


status_t
EventQueue::InitCheck()
{
	return fStatus;
}


EventQueue*
EventQueue::CreateDefault()
{
	if (!fDefaultQueue) {
		fDefaultQueue = new(nothrow) EventQueue;
		if (fDefaultQueue && fDefaultQueue->InitCheck() != B_OK)
			DeleteDefault();
	}
	return fDefaultQueue;
}


void
EventQueue::DeleteDefault()
{
	if (fDefaultQueue) {
		delete fDefaultQueue;
		fDefaultQueue = NULL;
	}
}


EventQueue&
EventQueue::Default()
{
	return *fDefaultQueue;
}


void
EventQueue::AddEvent(Event* event)
{
	Lock();
	_AddEvent(event);
	_Reschedule();
	Unlock();
}


bool
EventQueue::RemoveEvent(Event* event)
{
	bool result = false;
	Lock();
	if ((result = fEvents.RemoveItem(event)))
		_Reschedule();
	Unlock();
	return result;
}


void
EventQueue::ChangeEvent(Event* event, bigtime_t newTime)
{
	Lock();
	if (fEvents.RemoveItem(event)) {
		event->SetTime(newTime);
		_AddEvent(event);
		_Reschedule();
	}
	Unlock();
}


// PRE: The object must be locked.
void
EventQueue::_AddEvent(Event* event)
{
	// find the insertion index
	int32 lower = 0;
	int32 upper = fEvents.CountItems();
	while (lower < upper) {
		int32 mid = (lower + upper) / 2;
		Event* midEvent = _EventAt(mid);
		if (event->Time() < midEvent->Time())
			upper = mid;
		else
			lower = mid + 1;
	}
	fEvents.AddItem(event, lower);
}


Event*
EventQueue::_EventAt(int32 index) const
{
	return (Event*)fEvents.ItemAtFast(index);
}


int32
EventQueue::_execute_events_(void* cookie)
{
	EventQueue *gc = (EventQueue*)cookie;
	return gc->_ExecuteEvents();
}


int32
EventQueue::_ExecuteEvents()
{
	bool running = true;
	while (running) {
		bigtime_t waitUntil = B_INFINITE_TIMEOUT;
		if (Lock()) {
			if (!fEvents.IsEmpty())
				waitUntil = _EventAt(0)->Time();
			fNextEventTime = waitUntil;
			Unlock();
		}
		status_t err = acquire_sem_etc(fThreadControl, 1, B_ABSOLUTE_TIMEOUT,
									   waitUntil);
		switch (err) {
			case B_TIMED_OUT:
				// execute events, that are supposed to go off
				if (Lock()) {
					while (!fEvents.IsEmpty()
						   && system_time() >= _EventAt(0)->Time()) {
						Event* event = (Event*)fEvents.RemoveItem((int32)0);
						bool deleteEvent = event->AutoDelete();
						event->Execute();
						if (deleteEvent)
							delete event;
					}
					Unlock();
				}
				break;
			case B_BAD_SEM_ID:
				running = false;
				break;
			case B_OK:
			default:
				break;
		}
	}
	return 0;
}


// PRE: The object must be locked.
void
EventQueue::_Reschedule()
{
	if (fStatus == B_OK) {
		if (!fEvents.IsEmpty() && _EventAt(0)->Time() < fNextEventTime)
			release_sem(fThreadControl);
	}
}


// static variables

EventQueue*	EventQueue::fDefaultQueue = NULL;

