//------------------------------------------------------------------------------
//	Copyright (c) 2001-2002, OpenBeOS
//
//	Permission is hereby granted, free of charge, to any person obtaining a
//	copy of this software and associated documentation files (the "Software"),
//	to deal in the Software without restriction, including without limitation
//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
//	and/or sell copies of the Software, and to permit persons to whom the
//	Software is furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//	DEALINGS IN THE SOFTWARE.
//
//	File Name:		MessageRunnerManager.cpp
//	Author:			Ingo Weinhold (bonefish@users.sf.net)
//	Description:	Manages the registrar side "shadows" of BMessageRunners.
//------------------------------------------------------------------------------

#include <algobase.h>
#include <new.h>

#include <Autolock.h>
#include <Message.h>
#include <Messenger.h>
#include <OS.h>
#include <RegistrarDefs.h>

#include "Debug.h"
#include "Event.h"
#include "EventQueue.h"
#include "MessageRunnerManager.h"

/*!	\class MessageRunnerManager
	\brief Manages the registrar side "shadows" of BMessageRunners.

	The class features four methods to which the registrar application
	dispatches the message runner specific request messages.

	Each active message runner (i.e. one that still has messages to be sent)
	is represented by a RunnerInfo that comprises all necessary information,
	among these a RunnerEvent added to the event queue. When the event is
	executed, it calls the _DoEvent() method, which in turn sends the message
	runner message to the respective target and schedules the event for the
	next time the message has to be sent (_ScheduleEvent()).

	A couple of helper methods provide convenient access to the RunnerInfo
	list (\a fRunnerInfos). A BLocker (\a fLock) and respective locking
	methods are used to serialize the access to the member variables.
*/

/*! \var BList MessageRunnerManager::fRunnerInfos
	\brief The list of RunnerInfos.
*/

/*! \var BLocker MessageRunnerManager::fLock
	\brief A locker used to serialize the access to the object's variable
		   members.
*/

/*! \var EventQueue *MessageRunnerManager::fEventQueue
	\brief Event queue used by the manager.
*/

/*! \var int32 MessageRunnerManager::fNextToken
	\brief Next unused token for message runners.
*/


//! The minimal time interval for message runners (50 ms).
static const bigtime_t kMininalTimeInterval = 50000LL;

// RunnerEvent
/*!	\brief Event class used to by the message runner manager.

	For each active message runner such an event is used. It invokes
	MessageRunnerManager::_DoEvent() on execution.
*/
class MessageRunnerManager::RunnerEvent : public Event {
public:
	/*!	\brief Creates a new RunnerEvent.
		\param manager The message runner manager.
		\param info The RunnerInfo for the message runner.
	*/
	RunnerEvent(MessageRunnerManager *manager, RunnerInfo *info)
		: Event(false),
		  fManager(manager),
		  fInfo(info)
	{
	}

	/*!	\brief Hook method invoked when the event is executed.

		Implements Event. Calls MessageRunnerManager::_DoEvent().
		
		\param queue The event queue executing the event.
		\return \c true, if the object shall be deleted, \c false otherwise.
	*/
	virtual bool Do(EventQueue *queue)
	{
		return fManager->_DoEvent(fInfo);
	}

private:
	MessageRunnerManager	*fManager;	//!< The message runner manager.
	RunnerInfo				*fInfo;		//!< The message runner info.
};


// RunnerInfo
/*!	\brief Contains all needed information about an active message runner.
*/
struct MessageRunnerManager::RunnerInfo {
	/*!	\brief Creates a new RunnerInfo.
		\param team The team owning the message runner.
		\param token The unique token associated with the message runner.
		\param target The target the message shall be sent to.
		\param message The message to be sent to the target.
		\param interval The message runner's time interval.
		\param count The number of times the message shall be sent.
		\param replyTarget The reply target for the delivered message.
	*/
	RunnerInfo(team_id team, int32 token, BMessenger target, BMessage *message,
			   bigtime_t interval, int32 count, BMessenger replyTarget)
		: team(team),
		  token(token),
		  target(target),
		  message(message),
		  interval(interval),
		  count(count),
		  replyTarget(replyTarget),
		  time(0),
		  event(NULL),
		  rescheduled(false)
	{
	}

	/*!	\brief Frees all resources associated with the object.

		The message and the event are delete.
	*/
	~RunnerInfo()
	{
		delete message;
		delete event;
	}

	/*!	\brief Delivers the message to the respective target.
		\return \c B_OK, if the message has successfully been delivered or
				the target does still exist and its message port is full,
				an error code otherwise.
	*/
	status_t DeliverMessage()
	{
		if (count > 0)
			count--;
		status_t error = target.SendMessage(message, replyTarget, 0);
		// B_WOULD_BLOCK is as good as B_OK. We return an error only, if
		// there are serious problems with the target, i.e. if it doesn't
		// exist anymore for instance. A full message port is harmless.
		if (error == B_WOULD_BLOCK)
			error = B_OK;
		return error;
	}

	team_id		team;			//!< The team owning the message runner.
	int32		token;			/*!< The unique token associated with the
									 message runner. */
	BMessenger	target;			//!< The target the message shall be sent to.
	BMessage	*message;		//!< The message to be sent to the target.
	bigtime_t	interval;		//!< The message runner's time interval.
	int32		count;			/*!< The number of times the message shall be
									 sent. */
	BMessenger	replyTarget;	/*!< The reply target for the delivered
									 message. */
	bigtime_t	time;			/*!< Time at which the next message will be
									 sent. */
	RunnerEvent	*event;			//!< Runner event for the message runner.
	bool		rescheduled;	/*!< Set to \c true when the event has been
									 started to be executed while it was
									 rescheduled. */
};


// constructor
/*!	\brief Creates a new MessageRunnerManager.
	\param eventQueue The EventQueue the manager shall use.
*/
MessageRunnerManager::MessageRunnerManager(EventQueue *eventQueue)
	: fRunnerInfos(),
	  fLock(),
	  fEventQueue(eventQueue),
	  fNextToken(0)
{
}

// destructor
/*!	\brief Frees all resources associated with the object.

	The manager's event queue must already have been stopped
	(EventQueue::Die()).
*/
MessageRunnerManager::~MessageRunnerManager()
{
	// The event queue should already be stopped, but must still exist.
	// If it is still running and an event gets executed after we've locked
	// ourselves, then it will access an already deleted manager.
	BAutolock _lock(fLock);
	for (int32 i = 0; RunnerInfo *info = _InfoAt(i); i++) {
		if (!fEventQueue->RemoveEvent(info->event))
			info->event = NULL;
		delete info;
	}
	fRunnerInfos.MakeEmpty();
}

// HandleRegisterRunner
/*!	\brief Handles a registration request (BMessageRunner::InitData()).
	\param request The request message.
*/
void
MessageRunnerManager::HandleRegisterRunner(BMessage *request)
{
	FUNCTION_START();

	BAutolock _lock(fLock);
	status_t error = B_OK;
	// get the parameters
	team_id team;
	BMessenger target;
	// TODO: This should be a "new (nothrow)", but R5's BMessage doesn't
	// define that version.
	BMessage *message = new BMessage;
	bigtime_t interval;
	int32 count;
	BMessenger replyTarget;
	if (error == B_OK && message == NULL)
		error = B_NO_MEMORY;
	if (error == B_OK && request->FindInt32("team", &team) != B_OK)
		error = B_BAD_VALUE;
	if (error == B_OK && request->FindMessenger("target", &target) != B_OK)
		error = B_BAD_VALUE;
	if (error == B_OK && request->FindMessage("message", message) != B_OK)
		error = B_BAD_VALUE;
	if (error == B_OK && request->FindInt64("interval", &interval) != B_OK)
		error = B_BAD_VALUE;
	if (error == B_OK && request->FindInt32("count", &count) != B_OK)
		error = B_BAD_VALUE;
	if (error == B_OK
		&& request->FindMessenger("reply_target", &replyTarget) != B_OK) {
		error = B_BAD_VALUE;
	}
	// check the parameters
	if (error == B_OK && count == 0)
		error = B_BAD_VALUE;
	// add a new runner info
	RunnerInfo *info = NULL;
	if (error == B_OK) {
		interval = max(interval, kMininalTimeInterval);
		info = new(nothrow) RunnerInfo(team, _NextToken(), target, message,
									   interval, count, replyTarget);
		if (info) {
			info->time = system_time();
			if (!_AddInfo(info))
				error = B_NO_MEMORY;
		} else
			error = B_NO_MEMORY;
	}
	// create a new event
	RunnerEvent *event = NULL;
	if (error == B_OK) {
		event = new(nothrow) RunnerEvent(this, info);
		if (event) {
			info->event = event;
			if (!_ScheduleEvent(info))
				error = B_NO_MEMORY;	// TODO: The only possible reason?
		} else
			error = B_NO_MEMORY;
	}
	// cleanup on error
	if (error != B_OK) {
		if (info) {
			_RemoveInfo(info);
			delete info;
		}
		delete message;
	}
	// reply to the request
	if (error == B_OK) {
		BMessage reply(B_REG_SUCCESS);
		reply.AddInt32("token", info->token);
		request->SendReply(&reply);
	} else {
		BMessage reply(B_REG_ERROR);
		reply.AddInt32("error", error);
		request->SendReply(&reply);
	}

	FUNCTION_END();
}

// HandleUnregisterRunner
/*!	\brief Handles an unregistration request (BMessageRunner destructor).
	\param request The request message.
*/
void
MessageRunnerManager::HandleUnregisterRunner(BMessage *request)
{
	FUNCTION_START();

	BAutolock _lock(fLock);
	status_t error = B_OK;
	// get the parameters
	int32 token;
	if (error == B_OK && request->FindInt32("token", &token) != B_OK)
		error = B_BAD_VALUE;
	// find and delete the runner info
	if (error == B_OK) {
		if (RunnerInfo *info = _InfoForToken(token))
			_DeleteInfo(info, false);
		else
			error = B_BAD_VALUE;
	}
	// reply to the request
	if (error == B_OK) {
		BMessage reply(B_REG_SUCCESS);
		request->SendReply(&reply);
	} else {
		BMessage reply(B_REG_ERROR);
		reply.AddInt32("error", error);
		request->SendReply(&reply);
	}

	FUNCTION_END();
}

// HandleSetRunnerParams
/*!	\brief Handles an parameter change request (BMessageRunner::SetParams()).
	\param request The request message.
*/
void
MessageRunnerManager::HandleSetRunnerParams(BMessage *request)
{
	FUNCTION_START();

	BAutolock _lock(fLock);
	status_t error = B_OK;
	// get the parameters
	int32 token;
	bigtime_t interval;
	int32 count;
	bool setInterval = false;
	bool setCount = false;
	if (error == B_OK && request->FindInt32("token", &token) != B_OK)
		error = B_BAD_VALUE;
	if (error == B_OK && request->FindInt64("interval", &interval) == B_OK)
		setInterval = true;
	if (error == B_OK && request->FindInt32("count", &count) == B_OK)
		setCount = true;
	// find the runner info
	RunnerInfo *info = NULL;
	if (error == B_OK) {
		info = _InfoForToken(token);
		if (!info)
			error = B_BAD_VALUE;
	}
	// set the new values
	if (error == B_OK) {
		bool eventRemoved = false;
		bool deleteInfo = false;
		// count
		if (setCount) {
			if (count == 0)
				deleteInfo = true;
			else
				info->count = count;
		}
		// interval
		if (setInterval) {
			eventRemoved = fEventQueue->RemoveEvent(info->event);
			if (!eventRemoved)
				info->rescheduled = true;
			interval = max(interval, kMininalTimeInterval);
			info->interval = interval;
			info->time = system_time();
			if (!_ScheduleEvent(info))
				error = B_NO_MEMORY;	// TODO: The only possible reason?
		}
		// remove and delete the info on error
		if (error != B_OK || deleteInfo)
			_DeleteInfo(info, eventRemoved);
	}
	// reply to the request
	if (error == B_OK) {
		BMessage reply(B_REG_SUCCESS);
		request->SendReply(&reply);
	} else {
		BMessage reply(B_REG_ERROR);
		reply.AddInt32("error", error);
		request->SendReply(&reply);
	}

	FUNCTION_END();
}

// HandleGetRunnerInfo
/*!	\brief Handles an get info request (BMessageRunner::GetInfo()).
	\param request The request message.
*/
void
MessageRunnerManager::HandleGetRunnerInfo(BMessage *request)
{
	FUNCTION_START();

	BAutolock _lock(fLock);
	status_t error = B_OK;
	// get the parameters
	int32 token;
	if (error == B_OK && request->FindInt32("token", &token) != B_OK)
		error = B_BAD_VALUE;
	// find the runner info
	RunnerInfo *info = NULL;
	if (error == B_OK) {
		info = _InfoForToken(token);
		if (!info)
			error = B_BAD_VALUE;
	}
	// reply to the request
	if (error == B_OK) {
		BMessage reply(B_REG_SUCCESS);
		reply.AddInt64("interval", info->interval);
		reply.AddInt32("count", info->count);
		request->SendReply(&reply);
	} else {
		BMessage reply(B_REG_ERROR);
		reply.AddInt32("error", error);
		request->SendReply(&reply);
	}

	FUNCTION_END();
}

// Lock
/*!	\brief Locks the manager.
	\return \c true, if locked successfully, \c false otherwise.
*/
bool
MessageRunnerManager::Lock()
{
	return fLock.Lock();
}

// Unlock
/*!	\brief Unlocks the manager.
*/
void
MessageRunnerManager::Unlock()
{
	fLock.Unlock();
}

// _AddInfo
/*!	\brief Adds a RunnerInfo to the list of RunnerInfos.

	\note The manager must be locked.

	\param info The RunnerInfo to be added.
	\return \c true, if added successfully, \c false otherwise.
*/
bool
MessageRunnerManager::_AddInfo(RunnerInfo *info)
{
	return fRunnerInfos.AddItem(info);
}

// _RemoveInfo
/*!	\brief Removes a RunnerInfo from the list of RunnerInfos.

	\note The manager must be locked.

	\param info The RunnerInfo to be removed.
	\return \c true, if removed successfully, \c false, if the list doesn't
			contain the supplied info.
*/
bool
MessageRunnerManager::_RemoveInfo(RunnerInfo *info)
{
	return fRunnerInfos.RemoveItem(info);
}

// _RemoveInfo
/*!	\brief Removes a RunnerInfo at a given index from the list of RunnerInfos.

	\note The manager must be locked.

	\param index The index of the RunnerInfo to be removed.
	\return \c true, if removed successfully, \c false, if the supplied index
			is out of range.
*/
MessageRunnerManager::RunnerInfo*
MessageRunnerManager::_RemoveInfo(int32 index)
{
	return (RunnerInfo*)fRunnerInfos.RemoveItem(index);
}

// _RemoveInfoWithToken
/*!	\brief Removes a RunnerInfo with a specified token from the list of
		   RunnerInfos.

	\note The manager must be locked.

	\param token The token identifying the RunnerInfo to be removed.
	\return \c true, if removed successfully, \c false, if the list doesn't
			contain an info with the supplied token.
*/
MessageRunnerManager::RunnerInfo*
MessageRunnerManager::_RemoveInfoWithToken(int32 token)
{
	RunnerInfo *info = NULL;
	int32 index = _IndexOfToken(token);
	if (index >= 0)
		info = _RemoveInfo(index);
	return info;
}

// _DeleteInfo
/*!	\brief Removes a RunnerInfo from the list of RunnerInfos and deletes it.

	\note The manager must be locked.

	\param index The index of the RunnerInfo to be deleted.
	\return \c true, if removed and deleted successfully, \c false, if the
			list doesn't contain the supplied info.
*/
bool
MessageRunnerManager::_DeleteInfo(RunnerInfo *info, bool eventRemoved)
{
	bool result = _RemoveInfo(info);
	if (result) {
		// If the event is not in the event queue and has not been removed
		// just before, then it is in progress. It will delete itself.
		if (!eventRemoved && !fEventQueue->RemoveEvent(info->event))
			info->event = NULL;
		delete info;
	}
	return result;
}

// _CountInfos
/*!	\brief Returns the number of RunnerInfos in the list of RunnerInfos.

	\note The manager must be locked.

	\return Returns the number of RunnerInfos in the list of RunnerInfos.
*/
int32
MessageRunnerManager::_CountInfos() const
{
	return fRunnerInfos.CountItems();
}

// _InfoAt
/*!	\brief Returns the RunnerInfo at the specified index in the list of
		   RunnerInfos.

	\note The manager must be locked.

	\param index The index of the RunnerInfo to be returned.
	\return The runner info at the specified index, or \c NULL, if the index
			is out of range.
*/
MessageRunnerManager::RunnerInfo*
MessageRunnerManager::_InfoAt(int32 index) const
{
	return (RunnerInfo*)fRunnerInfos.ItemAt(index);
}

// _InfoForToken
/*!	\brief Returns the RunnerInfo with the specified index.

	\note The manager must be locked.

	\param token The token identifying the RunnerInfo to be returned.
	\return The runner info at the specified index, or \c NULL, if the list
			doesn't contain an info with the specified token.
*/
MessageRunnerManager::RunnerInfo*
MessageRunnerManager::_InfoForToken(int32 token) const
{
	return _InfoAt(_IndexOfToken(token));
}

// _IndexOf
/*!	\brief Returns the index of the supplied RunnerInfo in the list of
		   RunnerInfos.

	\note The manager must be locked.

	\param info The RunnerInfo whose index shall be returned.
	\return The index of the supplied RunnerInfo, or -1, if the list doesn't
			contain the supplied info.
*/
int32
MessageRunnerManager::_IndexOf(RunnerInfo *info) const
{
	return fRunnerInfos.IndexOf(info);
}

// _IndexOfToken
/*!	\brief Returns the index of the RunnerInfo identified by the supplied
		   token in the list of RunnerInfos.

	\note The manager must be locked.

	\param token The token identifying the RunnerInfo whose index shall be
		   returned.
	\return The index of the requested RunnerInfo, or -1, if the list doesn't
			contain an info with the supplied token.
*/
int32
MessageRunnerManager::_IndexOfToken(int32 token) const
{
	for (int32 i = 0; RunnerInfo *info = _InfoAt(i); i++) {
		if (info->token == token)
			return i;
	}
	return -1;
}

// _DoEvent
/*!	\brief Invoked when a message runner's event is executed.

	If the message runner info is still valid and the event was not just
	rescheduled, the message is delivered to the message runner's target
	and the event is rescheduled.

	\param info The message runner's info.
	\return \c true, if the event object shall be deleted, \c false otherwise.
*/
bool
MessageRunnerManager::_DoEvent(RunnerInfo *info)
{
	FUNCTION_START();

	BAutolock _lock(fLock);
	bool deleteEvent = false;
	// first check whether the info does still exist
	if (_lock.IsLocked() && _IndexOf(info) >= 0) {
		// If the event has been rescheduled after being removed from the
		// queue for execution, it needs to be ignored. This may happen, when
		// the interval is modified.
		if (info->rescheduled)
			info->rescheduled = false;
		else {
			// send the message
			bool success = (info->DeliverMessage() == B_OK);
			// reschedule the event
			if (success)
				success = _ScheduleEvent(info);
			// clean up, if the message delivery of the rescheduling failed
			if (!success) {
				deleteEvent = true;
				info->event = NULL;
				_RemoveInfo(info);
				delete info;
			}
		}
	} else {
		// The info is no more. That means it had been removed after the
		// event was removed from the event queue, but before we could acquire
		// the lock. Simply delete the event.
		deleteEvent = true;
	}

	FUNCTION_END();

	return deleteEvent;
}

// _ScheduleEvent
/*!	\brief Schedules the event for a message runner for the next time a
		   message has to be sent.

	\note The manager must be locked.

	\param info The message runner's info.
	\return \c true, if the event successfully been rescheduled, \c false,
			if either all messages have already been sent or the event queue
			doesn't allow adding the event (e.g. due to insufficient memory).
*/
bool
MessageRunnerManager::_ScheduleEvent(RunnerInfo *info)
{
	bool scheduled = false;
	// calculate next event time
	if (info->count != 0) {
		// avoid a bigtime_t overflow
		if (LONGLONG_MAX - info->interval < info->time)
			info->time = LONGLONG_MAX;
		else
			info->time += info->interval;
		info->event->SetTime(info->time);
		scheduled = fEventQueue->AddEvent(info->event);
PRINT(("runner %ld (%lld, %ld) rescheduled: %d, time: %lld, now: %lld\n",
info->token, info->interval, info->count, scheduled, info->time, system_time()));
	}
	return scheduled;
}

// _NextToken
/*!	\brief Returns a new unused message runner token.

	\note The manager must be locked.

	\return A new unused message runner token.
*/
int32
MessageRunnerManager::_NextToken()
{
	return fNextToken++;
}

