//----------------------------------------------------------------------------
//	Copyright (C) 2002, 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 WaitQ.cpp

	\author Ralf Gerstenberger

	\date created at 2002/03/26

	\brief Implementation of odemx::sync::WaitQ

	\sa WaitQ.h

	\since 1.0
*/

#include <odemx/synchronization/WaitQ.h>
#include <odemx/base/Process.h>
#include <odemx/base/Sched.h>
#include <odemx/base/Simulation.h>

#include <string>
#include <cassert>

namespace odemx {
namespace synchronization {

WaitQ::WaitQ( base::Simulation& sim, const data::Label& label, WaitQObserver* obs )
:	data::Producer( sim, label )
,	data::Observable< WaitQObserver >( obs )
,	masterQueue_( sim, getLabel() + ".master-queue" )
,	slaveQueue_( sim, getLabel() + ".slave-queue" )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(WaitQ) );

	statistics << param( "master queue", masterQueue_.getLabel() ).scope( typeid(WaitQ) );
	statistics << param( "slave queue", slaveQueue_.getLabel() ).scope( typeid(WaitQ) );

	// observer
	ODEMX_OBS(WaitQObserver, Create(this));
}

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

bool WaitQ::wait()
{
	// WaitQ synchronization only implemented for processes
	if( ! getCurrentSched() || getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << log( "WaitQ::wait(): called by non-Process object" )
				.scope( typeid(WaitQ) );
		return false;
	}

	base::Process* slave = getCurrentProcess();

	ODEMX_TRACE << log( "wait" ).detail( "slave", slave->getLabel() ).scope( typeid(WaitQ) );

	// observer
	ODEMX_OBS(WaitQObserver, Wait(this, slave));

	// awake masters
	awakeAll( &masterQueue_ );

	// slave waits for master
	slaveQueue_.inSort( slave );

	// entrance time in queue is stored equal to execution time
	// sleep
	slave->sleep();

	if( slave->isInterrupted() )
	{
		slaveQueue_.remove( slave );
		return false;
	}

	return true;
}

bool WaitQ::wait( base::Weight weightFct )
{
	// WaitQ synchronization only implemented for processes
	if( ! getCurrentSched() || getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << log( "WaitQ::wait(): called by non-Process object" )
				.scope( typeid(WaitQ) );
		return false;
	}

	base::Process* slave = getCurrentProcess();

	ODEMX_TRACE << log( "wait weight" ).detail( "slave", slave->getLabel() ).scope( typeid(WaitQ) );

	// observer
	ODEMX_OBS(WaitQObserver, WaitWeight(this, slave));

	if( ! masterQueue_.isEmpty() )
	{
		// evaluate master weight function for this slave
		base::ProcessList::const_iterator i = masterQueue_.getList().begin();
		base::ProcessList::const_iterator end = masterQueue_.getList().end();

		// initialize max weighted master variable
		base::Process* maxWeightMaster = *i;
		// get weight of slave in relation to first master
		double maxWeight = ( maxWeightMaster ->* weightFct )( slave );

		// iterate over master queue to get the master
		// where this slave's weight is highest
		for( ++i; i != end; ++i )
		{
			// get the slave's weight for the current master
			double currentWeight = ( (*i) ->* weightFct )( slave );

			// if current master's slave weight is highest so far, remember it
			if( currentWeight > maxWeight )
			{
				maxWeight = currentWeight;
				maxWeightMaster = *i;
			}
		}
		// awaken the master process with the highest weight evaluation
		maxWeightMaster->activateAfter( slave );
	}

	// slave waits for master
	slaveQueue_.inSort( slave );

	// entrance time in queue is stored equal to execution time
	// sleep
	slave->sleep();

	if( slave->isInterrupted() )
	{
		slaveQueue_.remove( slave );
		return false;
	}

	return true;
}

base::Process* WaitQ::coopt( base::Weight weightFct )
{
	// WaitQ synchronization only implemented for processes
	if( ! getCurrentSched() || getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << log( "WaitQ::coopt(): called by non-Process object" )
				.scope( typeid(WaitQ) );
		return 0;
	}

	base::Process* master = getCurrentProcess();
	base::Process* slave = getWeightedSlave( master, weightFct );

	// no slave available
	if( slave == 0 )
	{
		ODEMX_TRACE << log( "coopt weight failed" )
				.detail( "master", master->getLabel() )
				.scope( typeid(WaitQ) );

		// observer
		ODEMX_OBS(WaitQObserver, CooptWeightFail(this, master));

		// master is waiting for slave
		masterQueue_.inSort( master );

		// this master must wait
		do
		{
			master->sleep();

			if( master->isInterrupted() )
			{
				masterQueue_.remove( master );
				return 0;
			}
			slave = getWeightedSlave( master, weightFct );
		}
		while( slave == 0 );

		// block released

		masterQueue_.remove( master );
	}

	// remove chosen slave from queue
	slaveQueue_.remove( slave );

	// statistics
	updateStatistics( master, slave );

	ODEMX_TRACE << log( "coopt weight succeeded" )
			.detail( "master", master->getLabel() )
			.detail( "slave", slave->getLabel() )
			.scope( typeid(WaitQ) );

	// observer
	ODEMX_OBS( WaitQObserver, CooptWeightSucceed( this, master, slave ) )

	return slave;
}

base::Process* WaitQ::coopt( base::Selection sel )
{
	// WaitQ synchronization only implemented for processes
	if( ! getCurrentSched() || getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << log( "WaitQ::coopt(): called by non-Process object" )
				.scope( typeid(WaitQ) );
		return 0;
	}

	// attempt blocking synchronization
	return sync( true, sel );
}

base::Process* WaitQ::avail( base::Selection sel )
{
	// WaitQ synchronization only implemented for processes
	if( ! getCurrentSched() || getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << log( "WaitQ::avail(): called by non-Process object" )
				.scope( typeid(WaitQ) );

		return 0;
	}

	// attempt non-blocking synchronization
	return sync( false, sel );
}

base::Process* WaitQ::sync( bool wait, base::Selection sel )
{
	base::Process* master = getCurrentProcess();
	base::Process* slave = getSlave( master, sel );

	if( slave == 0 )
	{
		// trace
		data::StringLiteral recordText;
		if( wait )
		{
			if( sel ) recordText = "coopt select failed";
			else recordText = "coopt failed";
		}
		else
		{
			if( sel ) recordText = "avail select failed";
			else recordText = "avail failed";
		}

		ODEMX_TRACE << log( recordText )
				.detail( "master", master->getLabel() )
				.scope( typeid(WaitQ) );

		// observer
		if( wait ) // called from coopt()
		{
			if( sel == 0 )
			{
				ODEMX_OBS(WaitQObserver, CooptFail(this, master))
			}
			else
			{
				ODEMX_OBS(WaitQObserver, CooptSelFail(this, master))
			}
		}
		else // called from avail()
		{
			if( sel == 0 )
			{
				ODEMX_OBS(WaitQObserver, AvailFail(this, master))
			}
			else
			{
				ODEMX_OBS(WaitQObserver, AvailSelFail(this, master))
			}
		}

		if( wait ) // called from coopt()
		{
			// master is waiting for slave
			masterQueue_.inSort( master );

			do
			{
				master->sleep();

				if( master->isInterrupted() )
				{
					masterQueue_.remove( master );
					return 0;
				}

				slave = getSlave( master, sel );
			}
			while( slave == 0 );

			// block released
			masterQueue_.remove( master );
		}
		else // called from avail()
		{
			return 0;
		}
	}

	// get slave
	slaveQueue_.remove( slave );

	// statistics
	updateStatistics( master, slave );

	// trace
	data::StringLiteral recordText;
	if( wait )
	{
		if( sel ) recordText = "coopt select succeeded";
		else recordText = "coopt succeeded";
	}
	else
	{
		if( sel ) recordText = "avail select succeeded";
		else recordText = "avail succeeded";
	}

	ODEMX_TRACE << log( recordText )
			.detail( "master", master->getLabel() )
			.detail( "slave", slave->getLabel() )
			.scope( typeid(WaitQ) );

	// observer
	if( wait ) // called from coopt()
	{
		if( sel == 0 )
		{
			ODEMX_OBS(WaitQObserver, CooptSucceed(this, master, slave))
		}
		else
		{
			ODEMX_OBS(WaitQObserver, CooptSelSucceed(this, master, slave))
		}
	}
	else // called from avail()
	{
		if( sel == 0 )
		{
			ODEMX_OBS(WaitQObserver, AvailSucceed(this, master, slave))
		}
		else
		{
			ODEMX_OBS(WaitQObserver, AvailSelSucceed(this, master, slave))
		}
	}

	return slave;
}

base::Process* WaitQ::getSlave( base::Process* master, base::Selection sel )
{
	if( slaveQueue_.isEmpty() || ( master == 0 ) )
	{
		return 0;
	}

	if( sel == 0 )
	{
		return slaveQueue_.getTop();
	}

	base::ProcessList::const_iterator i;
	base::ProcessList::const_iterator end = slaveQueue_.getList().end();

	for( i = slaveQueue_.getList().begin(); i != end; ++i )
	{
		// check if current slave fits the selection
		if( ( master->*sel )( *i ) )
		{
			return *i;
		}
	}

	return 0;
}

base::Process* WaitQ::getWeightedSlave( base::Process* master, base::Weight weightFct )
{
	// nothing to synchronize
	if( slaveQueue_.isEmpty() || ( master == 0 ) )
	{
		return 0;
	}

	// no weight to evaluate, take first in queue
	if( weightFct == 0 )
	{
		return slaveQueue_.getTop();
	}

	// evaluate master weight function
	base::ProcessList::const_iterator i = slaveQueue_.getList().begin();
	base::ProcessList::const_iterator end = slaveQueue_.getList().end();

	// initialize max weighted slave variable
	base::Process* maxWeightSlave = *i;
	// get weight of first slave
	double maxWeight = ( master ->* weightFct )( maxWeightSlave );

	// iterate over slave queue to get the maximum weighted slave
	for( ++i; i != end; ++i )
	{
		// get the current slave's weight
		double currentWeight = ( master ->* weightFct )( *i );

		// if current slave's weight is highest so far, remember it
		if( currentWeight > maxWeight )
		{
			maxWeight = currentWeight;
			maxWeightSlave = *i;
		}
	}

	return maxWeightSlave;
}

const base::ProcessList& WaitQ::getWaitingSlaves() const
{
	return slaveQueue_.getList();
}

const base::ProcessList& WaitQ::getWaitingMasters() const
{
	return masterQueue_.getList();
}

void WaitQ::updateStatistics( base::Process* master, base::Process* slave)
{
	assert( master && slave );

	// master and slave synchronized (served)
	statistics << count( "synchronizations" ).scope( typeid(WaitQ) );

	// calculate waiting times
	base::SimTime masterWaitTime = master->getDequeueTime() - master->getEnqueueTime();
	base::SimTime slaveWaitTime = slave->getDequeueTime() - slave->getEnqueueTime();

	statistics << update( "master wait time", masterWaitTime ).scope( typeid(WaitQ) );
	statistics << update( "slave wait time", slaveWaitTime ).scope( typeid(WaitQ) );
}

base::SimTime WaitQ::getTime() const
{
	return getSimulation().getTime();
}

base::Sched* WaitQ::getCurrentSched()
{
	return getSimulation().getCurrentSched();
}

base::Process* WaitQ::getCurrentProcess()
{
	return getSimulation().getCurrentProcess();
}

} } // namespace odemx::sync
