//----------------------------------------------------------------------------
//	Copyright (C) 2002, 2003, 2004 Humboldt-Universitaet zu Berlin
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Lesser General Public
//	License as published by the Free Software Foundation; either
//	version 2.1 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
//	Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public
//	License along with this library; if not, write to the Free Software
//	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//----------------------------------------------------------------------------
/**	\file Wait.cpp

	\author Ralf Gerstenberger

	\date created at 2003/06/06

	\brief Implementation of classes in Wait.h

	\sa Wait.h

	\since 1.0
*/

#ifdef ODEMX_USE_OBSERVATION

#include <odemx/synchronization/Wait.h>
#include <odemx/base/Simulation.h>

namespace odemx {
namespace synchronization {

Wait::Wait( base::Simulation& sim, const data::Label& label )
:	data::Producer( sim, label )
,	waitForAll_( true )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Wait) );
}

Wait::Wait( base::Simulation& sim, const data::Label& label, base::Process* p1 )
:	data::Producer( sim, label )
,	waitForAll_( true )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Wait) );

	base::Process* ppP[1] = { p1 };
	initObservedList( 1, ppP );
}

Wait::Wait( base::Simulation& sim, const data::Label& label, base::Process* p1,
		base::Process* p2, bool all )
:	data::Producer( sim, label )
,	waitForAll_( all )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Wait) );

	base::Process* ppP[2] = { p1, p2 };
	initObservedList( 2, ppP );
}

Wait::Wait( base::Simulation& sim, const data::Label& label, base::Process* p1,
		base::Process* p2, base::Process* p3, bool all )
:	data::Producer( sim, label )
,	waitForAll_( all )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Wait) );

	base::Process* ppP[3] = { p1, p2, p3 };
	initObservedList( 3, ppP );
}

Wait::Wait( base::Simulation& sim, const data::Label& label, int size,
		base::Process* p[], bool all )
:	data::Producer( sim, label )
,	waitForAll_( all )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Wait) );

	initObservedList( size, p );
}

Wait::~Wait()
{
	ODEMX_TRACE << log( "destroy" ).scope( typeid(Wait) );
}

void Wait::addProcess( base::Process* p )
{
	assert( p );

	ODEMX_TRACE << log( "add process" ).detail( "partner", p->getLabel() ).scope( typeid(Wait) );

	observedProcesses_.push_back( p );
}

void Wait::removeProcess( base::Process* p )
{
	assert( p );

	ODEMX_TRACE << log( "remove process" ).detail( "partner", p->getLabel() ).scope( typeid(Wait) );

	observedProcesses_.remove(p);
}

const base::ProcessList& Wait::getWaitingProcesses() const
{
	return observedProcesses_;
}

bool Wait::getCondition() const
{
	return waitForAll_;
}

void Wait::setCondition( bool all )
{
	waitForAll_ = all;
}

void Wait::onChangeProcessState( base::Process* sender,
		base::Process::ProcessState oldState,
		base::Process::ProcessState newState )
{
	if( newState == base::Process::TERMINATED )
	{
		// check condition
		waitCaller_->activate();

		// remove sender from observed list, no need to check it again
		observedProcesses_.remove( sender );

		// remove this as observer of the sender
		sender->data::Observable< base::ProcessObserver >::removeObserver( this );
	}
}

bool Wait::wait()
{
	// store the caller that wants to be awakened
	// upon termination of other processes
	waitCaller_ = getSimulation().getCurrentProcess();

	ODEMX_TRACE << log( "wait" )
			.detail( "partner", waitCaller_->getLabel() )
			.scope( typeid(Wait) );

	base::ProcessList::iterator i;

	// first check condition
	if( ! checkObserved() )
	{
		// add this as observer
		for( i = observedProcesses_.begin(); i != observedProcesses_.end(); ++i )
		{
			(*i)->data::Observable< base::ProcessObserver >::addObserver( this );
		}
	}
	else
	{
		ODEMX_TRACE << log( "continue" )
				.detail( "partner", waitCaller_->getLabel() )
				.scope( typeid(Wait) );

		return true;
	}

	// Wait for condition, block process execution
	while( ! checkObserved() )
	{
		waitCaller_->sleep();

		// Handle interrupt
		if( waitCaller_->isInterrupted() )
		{
			// Remove this as observer
			for( i = observedProcesses_.begin(); i != observedProcesses_.end(); ++i )
			{
				(*i)->data::Observable< base::ProcessObserver >::removeObserver( this );
			}

			ODEMX_TRACE << log( "continue" )
					.detail( "partner", waitCaller_->getLabel() )
					.scope( typeid(Wait) );

			return false;
		}
	}
	// Condition fulfilled, block released

	// Remove this as observer
	for( i = observedProcesses_.begin(); i != observedProcesses_.end(); ++i )
	{
		(*i)->data::Observable<base::ProcessObserver>::removeObserver( this );
	}

	ODEMX_TRACE << log( "continue" )
			.detail( "partner", waitCaller_->getLabel() )
			.scope( typeid(Wait) );

	return true;
}

void Wait::initObservedList( size_t size, base::Process* p[] )
{
	base::Process* proc = 0;

	// Add entries from p in observedProcesses_
	for( size_t i = 0; i < size; ++i )
	{
		proc = p[i];

		if( proc != 0 )
		{
			addProcess( proc );
		}
	}
}

bool Wait::checkObserved()
{
	// Check Condition: one(all) observed process(es) TERMINATED
	base::ProcessList::iterator i;

	if( waitForAll_ )
	{
		for( i = observedProcesses_.begin(); i != observedProcesses_.end(); ++i )
		{
			// return false if not all processes have terminated
			if( (*i)->getProcessState() != base::Process::TERMINATED )
			{
				return false;
			}
		}
		return true;
	}
	else
	{
		for( i = observedProcesses_.begin(); i != observedProcesses_.end(); ++i )
		{
			// return true if one of the observed processes has terminated
			if( (*i)->getProcessState() == base::Process::TERMINATED )
			{
				return true;
			}
		}
		return false;
	}
}

} } // namespace odemx::sync

#endif /* ODEMX_USE_OBSERVATION */
