/*
 * Copyright 2001-2010 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Ingo Weinhold, ingo_weinhold@gmx.de
 */


#include <MessageRunner.h>

#include <Application.h>
#include <AppMisc.h>
#include <RegistrarDefs.h>
#include <Roster.h>
#ifndef HoG_GENODE  //  ----------------------------

#include <RosterPrivate.h>
using namespace BPrivate;

#else  // HoG_GENODE ----------------------------

///ToDo: move Genode-capability-using code to a "_" prefixed file, e.g. "_GeTimer.cpp" (this will be my policy from now on)
#include <timer_session/connection.h>
#include <stdio.h>  // printf()

static int debug = 0;

class BMessageRunner::Impl
{
public:
		Impl(
			const BMessenger target,
			const BMessage & message,
			const bigtime_t interval,
			const int32 count,
			const BMessenger replyTo
			);
		~Impl();
		
		void SetInterval( bigtime_t interval );
		void SetCount( int32 count );
		
		void _handle_progress_timeout( Genode::Duration );


private:  // Code

	static int worker_entry( void * runner_impl_uncast );


private:  // Data

		///Optim: should use only *one* Timer/Capability/Connection for all runners
		Genode::Constructible< Timer::Connection >
			_timer { };
		Genode::Constructible< Timer::Periodic_timeout<Impl> >
			_timeout { };
		
		const BMessage msgContents;
		const BMessenger targetMger;
		const BMessenger replyMger;
		
#if 1  // delegate BMessage sending to 'haiku' thread
		thread_id threadHelper;
		Genode::Blockade jobWaiter;
		bool tearingDown;
#endif
};

BMessageRunner::Impl::Impl(
			const BMessenger target,
			const BMessage & message,
			const bigtime_t interval,
			const int32 count,
			const BMessenger reply_to
			)
:	_timer()
	,_timeout()
	,msgContents( message )
	,targetMger( target )
	,replyMger( reply_to )
	,threadHelper( -1 )
	,jobWaiter()
	,tearingDown( false )
{
	if( debug )
		printf("  bmessagerunner.ctor (msg.what: 0x%x)\n", msgContents.what );
	
	if( count != -1 )
	{
		Genode::log( "BMessageRunner with count=", count, ": n/i, aborting so as not to break Tracker" );
		
		// We only implement 'count' with value -1 (infinite count) so far. If a non-infinite
		// count is specified, bail out (before even creating the thread) otherwise e.g. Tracker
		// will misbehave, its double-clicking paralyzed by LongAndDragTrackingFilter::Filter() turning it into an RMB-emul "click":
		//
		return;
		//
	}
#if 1  // delegate BMessage sending to 'haiku' thread
	status_t res = -1;
	
	threadHelper = spawn_thread( worker_entry, "messagerunner_thread_companion", 0, this );
	if( threadHelper >= 0 )
		res = resume_thread( threadHelper );
	
	if( res != B_OK )
	{
		Genode::error("error: cannot Spawn/Resume BMessageRunner-companion thread\n");
		
		//
		return;
	}
#endif
	
	_timer.construct( be_app->Env() );
	SetInterval( interval );
	SetCount( count );
}

BMessageRunner::Impl::~Impl()
{
	if( debug )
		printf("  ~bmessagerunner.DTor (msg.what: 0x%x)\n", msgContents.what );
	
	_timeout.destruct();
	_timer.destruct();
	
#if 1
	tearingDown = true;
	jobWaiter.wakeup();
	if( threadHelper >= 0 )
		wait_for_thread( threadHelper, NULL );
#endif
}

void BMessageRunner::Impl::SetInterval( bigtime_t interval )
{
	_timeout.destruct();
	_timeout.construct(
		*_timer,
		*this,
		&Impl::_handle_progress_timeout,
		Genode::Microseconds( interval )
		);
}

void BMessageRunner::Impl::SetCount( int32 count )
{
	if( count != -1 )
		Genode::warning( "BMessageRunner with count=", count, " : not implemented" );
///FIXME-2: For Tracker/LongAndDragTrackingFilter (RMB emulated from prolonged-LMB): BMessageRunner.SetCount: support it! VERY annoying to get the "blue selection rect" all the time... Easy enough to implement, just keep the count in Impl and decrement at each call
///later: work-around Mandelbrot problem, about first msg occuring too soon: maybe use a "OneShot" repetitively (instead of a "Periodic"), as described in the below genodians article?
}

void BMessageRunner::Impl::_handle_progress_timeout( Genode::Duration duration )
{
	if( debug >= 3 )
	{
		//printf( "  ..bmessagerunner.handle-progress\n" );
		//don't use libc/printf() from within a handler, otherwise we get "libc suspend() called from non-user [i.e. KERNEL] context (0x1326e5a) - aborting"
		// (also see: http://genodians.org/m-stein/2021-05-07-short-guide-to-the-timeout-framework )
		Genode::log("  ..bmessagerunner.handle-progress for duration: ", duration.trunc_to_plain_us().value );
		
		//Genode::Duration time { _timer.curr_time() };
		//Genode::Milliseconds time_us { time.trunc_to_plain_us() };
		//Genode::Microseconds time_ms { time.trunc_to_plain_ms() };
		// Genode::log( " time elapsed since object instantiated: ", time_ms, " ms, or ", time_us, " µs" );
	}
	
	// dont call Haiku code directly from this Genode sig-handler, find_thread(NULL) et al will not work ! Causes trouble in BWindow etc
	// instead, let a separate 'haiku' thread handle each tick:
	jobWaiter.wakeup();
}

// static
int BMessageRunner::Impl::worker_entry( void * runner_impl_uncast )
{
	BMessageRunner::Impl * provider = static_cast<BMessageRunner::Impl*>( runner_impl_uncast );
	
	for( ;; )
	{
		provider->jobWaiter.block();
		
		if( provider->tearingDown )
			//
			break;
			//
		
		//Genode::log( "............. worker: tick !" );
		
		// we're in a 'haiku' thread, it's safe to call BMessenger.SendMessage():
		const bigtime_t no_timeout = 0;
		BMessage m( provider->msgContents );///later: race condition..?
		provider->targetMger
			.SendMessage( &m, provider->replyMger, no_timeout );
	}
	
	//Genode::log( "BMessageRunner: worker thread tear-down *****************" );
	
	return 0;
}


//#pragma mark -

#endif  // HoG_GENODE ----------------------------




BMessageRunner::BMessageRunner(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count)
	:
	fToken(-1)
{
	_InitData(target, message, interval, count, be_app_messenger);
}


BMessageRunner::BMessageRunner(BMessenger target, const BMessage& message,
	bigtime_t interval, int32 count)
	:
	fToken(-1)
{
	_InitData(target, &message, interval, count, be_app_messenger);
}


BMessageRunner::BMessageRunner(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count, BMessenger replyTo)
	:
	fToken(-1)
{
	_InitData(target, message, interval, count, replyTo);
}


BMessageRunner::BMessageRunner(BMessenger target, const BMessage& message,
	bigtime_t interval, int32 count, BMessenger replyTo)
	:
	fToken(-1)
{
	_InitData(target, &message, interval, count, replyTo);
}


BMessageRunner::~BMessageRunner()
{
	if (fToken < B_OK)
		return;

#ifndef HoG_GENODE
	// compose the request message
	BMessage request(B_REG_UNREGISTER_MESSAGE_RUNNER);
	status_t result = request.AddInt32("token", fToken);

	// send the request
	BMessage reply;
	if (result == B_OK)
		result = BRoster::Private().SendTo(&request, &reply, false);

	// ignore the reply, we can't do anything anyway
#else
	delete genodeImpl;
#endif
}


status_t
BMessageRunner::InitCheck() const
{
	return fToken >= 0 ? B_OK : fToken;
}


status_t
BMessageRunner::SetInterval(bigtime_t interval)
{
	return _SetParams(true, interval, false, 0);
}


#ifndef HoG_GENODE
status_t
BMessageRunner::SetCount(int32 count)
{
	return _SetParams(false, 0, true, count);
}


status_t
BMessageRunner::GetInfo(bigtime_t* interval, int32* count) const
{
	status_t result =  fToken >= 0 ? B_OK : B_BAD_VALUE;

	// compose the request message
	BMessage request(B_REG_GET_MESSAGE_RUNNER_INFO);
	if (result == B_OK)
		result = request.AddInt32("token", fToken);

	// send the request
	BMessage reply;
	if (result == B_OK)
		result = BRoster::Private().SendTo(&request, &reply, false);

	// evaluate the reply
	if (result == B_OK) {
		if (reply.what == B_REG_SUCCESS) {
			// count
			int32 _count;
			if (reply.FindInt32("count", &_count) == B_OK) {
				if (count != 0)
					*count = _count;
			} else
				result = B_ERROR;

			// interval
			bigtime_t _interval;
			if (reply.FindInt64("interval", &_interval) == B_OK) {
				if (interval != 0)
					*interval = _interval;
			} else
				result = B_ERROR;
		} else {
			if (reply.FindInt32("error", &result) != B_OK)
				result = B_ERROR;
		}
	}

	return result;
}
#endif


#if defined( HoG_GENODE )

///ToDo: for BToolTipManager et al: currently "delete genodeImpl;" is called only in BMessageRunner dtor ; make it so it we can also (also? only?) calle "delete this;" in Impl::_handle_progress_timeout(), once countdown reaches zero
/*static*/ status_t
BMessageRunner::StartSending(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count)
{
	printf( "BMessageRunner::StartSending(): not implemented\n" );
	
	return -1;
}

#else

/*static*/ status_t
BMessageRunner::StartSending(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count)
{
	int32 token = _RegisterRunner(target, message, interval, count, true,
		be_app_messenger);

	return token >= B_OK ? B_OK : token;
}


/*static*/ status_t
BMessageRunner::StartSending(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count, BMessenger replyTo)
{
	int32 token = _RegisterRunner(target, message, interval, count, true,
		replyTo);

	return token >= B_OK ? B_OK : token;
}
#endif


// FBC
void BMessageRunner::_ReservedMessageRunner1() {}
void BMessageRunner::_ReservedMessageRunner2() {}
void BMessageRunner::_ReservedMessageRunner3() {}
void BMessageRunner::_ReservedMessageRunner4() {}
void BMessageRunner::_ReservedMessageRunner5() {}
void BMessageRunner::_ReservedMessageRunner6() {}


//! Privatized copy constructor to prevent usage.
BMessageRunner::BMessageRunner(const BMessageRunner &)
	:
	fToken(-1)
{
}


//! Privatized assignment operator to prevent usage.
BMessageRunner&
BMessageRunner::operator=(const BMessageRunner&)
{
	return* this;
}


/*!	Initializes the BMessageRunner.

	The success of the initialization can (and should) be asked for via
	InitCheck().

	\note As soon as the last message has been sent, the message runner
	      becomes unusable. InitCheck() will still return \c B_OK, but
	      SetInterval(), SetCount() and GetInfo() will fail.

	\param target Target of the message(s).
	\param message The message to be sent to the target.
	\param interval Period of time before the first message is sent and
	       between messages (if more than one shall be sent) in microseconds.
	\param count Specifies how many times the message shall be sent.
	       A value less than \c 0 for an unlimited number of repetitions.
	\param replyTo Target replies to the delivered message(s) shall be sent to.
*/
void
BMessageRunner::_InitData(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count, BMessenger replyTo)
{
#ifndef HoG_GENODE
	fToken = _RegisterRunner(target, message, interval, count, false, replyTo);
#else
	genodeImpl = new Impl( target, *message, interval, count, replyTo );
	fToken = B_OK;  // good enough
#endif
}


/*!	Registers the BMessageRunner in the registrar.

	\param target Target of the message(s).
	\param message The message to be sent to the target.
	\param interval Period of time before the first message is sent and
	       between messages (if more than one shall be sent) in microseconds.
	\param count Specifies how many times the message shall be sent.
	       A value less than \c 0 for an unlimited number of repetitions.
	\param replyTo Target replies to the delivered message(s) shall be sent to.

	\return The token the message runner is registered with, or the error code
	        while trying to register it.
*/
#if 0  // HoG_GENODE
/*static*/ int32
BMessageRunner::_RegisterRunner(BMessenger target, const BMessage* message,
	bigtime_t interval, int32 count, bool detach, BMessenger replyTo)
{
	status_t result = B_OK;
	if (message == NULL || count == 0 || (count < 0 && detach))
		result = B_BAD_VALUE;

	// compose the request message
	BMessage request(B_REG_REGISTER_MESSAGE_RUNNER);
	if (result == B_OK)
		result = request.AddInt32("team", BPrivate::current_team());

	if (result == B_OK)
		result = request.AddMessenger("target", target);

	if (result == B_OK)
		result = request.AddMessage("message", message);

	if (result == B_OK)
		result = request.AddInt64("interval", interval);

	if (result == B_OK)
		result = request.AddInt32("count", count);

	if (result == B_OK)
		result = request.AddMessenger("reply_target", replyTo);

	// send the request
	BMessage reply;
	if (result == B_OK)
		result = BRoster::Private().SendTo(&request, &reply, false);

	int32 token;

	// evaluate the reply
	if (result == B_OK) {
		if (reply.what == B_REG_SUCCESS) {
			if (reply.FindInt32("token", &token) != B_OK)
				result = B_ERROR;
		} else {
			if (reply.FindInt32("error", &result) != B_OK)
				result = B_ERROR;
		}
	}

	if (result == B_OK)
		return token;

	return result;
}
#endif


/*!	Sets the message runner's interval and count parameters.

	The parameters \a resetInterval and \a resetCount specify whether
	the interval or the count parameter respectively shall be reset.

	At least one parameter must be set, otherwise the methods returns
	\c B_BAD_VALUE.

	\param resetInterval \c true, if the interval shall be reset, \c false
	       otherwise -- then \a interval is ignored.
	\param interval The new interval in microseconds.
	\param resetCount \c true, if the count shall be reset, \c false
	       otherwise -- then \a count is ignored.
	\param count Specifies how many times the message shall be sent.
	       A value less than \c 0 for an unlimited number of repetitions.

	\return A status code.
	\retval B_OK Everything went fine.
	\retval B_BAD_VALUE The message runner is not longer valid. All the
	        messages that had to be sent have already been sent. Or both
	        \a resetInterval and \a resetCount are \c false.
*/
status_t
BMessageRunner::_SetParams(bool resetInterval, bigtime_t interval,
	bool resetCount, int32 count)
{
	if ((!resetInterval && !resetCount) || fToken < 0)
		return B_BAD_VALUE;

#ifndef HoG_GENODE
	// compose the request message
	BMessage request(B_REG_SET_MESSAGE_RUNNER_PARAMS);
	status_t result = request.AddInt32("token", fToken);
	if (result == B_OK && resetInterval)
		result = request.AddInt64("interval", interval);

	if (result == B_OK && resetCount)
		result = request.AddInt32("count", count);

	// send the request
	BMessage reply;
	if (result == B_OK)
		result = BRoster::Private().SendTo(&request, &reply, false);

	// evaluate the reply
	if (result == B_OK) {
		if (reply.what != B_REG_SUCCESS) {
			if (reply.FindInt32("error", &result) != B_OK)
				result = B_ERROR;
		}
	}

	return result;

#else  // HoG_GENODE

	if( resetInterval )
		genodeImpl->SetInterval( interval );
	if( resetCount )
		genodeImpl->SetCount( count );
	
	return B_OK;
#endif  // ~HoG_GENODE
}

