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

	\author Ronald Kluth

	\date created at 2008/02/18

	\brief Implementation of odemx::sync::ResTChoice

	\sa ResTChoice.h

	\since 2.1
*/

#include <odemx/synchronization/res/ResTChoice.h>
#include <odemx/base/Sched.h>
#include <odemx/base/Process.h>

namespace odemx {
namespace synchronization {

template < typename Token >
ResTChoice< Token >::ResTChoice( base::Simulation& sim,
					 const data::Label& label,
					 std::vector< Token >& initialResources,
					 ResTChoiceObserver< Token >* o /* = 0*/ )
:	ResTBase< Token >( sim, label, initialResources, o ),
	data::Observable< ResTChoiceObserver< Token > >( o )
{
	// observer
	ODEMX_OBS_T(ResTChoiceObserver<Token>, Create(this));
}

template < typename Token >
ResTChoice<Token>::~ResTChoice()
{
	// observer
	ODEMX_OBS_T(ResTChoiceObserver<Token>, Destroy(this));
}

template < typename Token >
Token* ResTChoice< Token >::acquire()
{
	error << this->log( "ResTChoice::acquire(): selection function required, use overload" )
			.scope( typeid(ResTChoice<Token>) );
	return 0;
}

template < typename Token >
Token* ResTChoice< Token >::acquire( typename ResTChoice< Token >::Selection select )
{
	std::vector< Token* > found = acquire( select, 1 );

	if( ! found.empty() )
	{
		return found.front();
	}
	return 0;
}

template < typename Token >
const std::vector< Token* > ResTChoice< Token >::acquire( 
		typename ResTChoice< Token >::Selection select, std::size_t n )
{
	if( ! getCurrentSched() || getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		// error: resource handling only implemented for processes
		error << this->log( "ResTChoice::acquire(): called by non-Process object" )
					.scope( typeid(ResTChoice<Token>) );
		return std::vector< Token* >();
	}

	if( n > ResTBase< Token >::tokenLimit )
	{
		warning << this->log( "ResTChoice::acquire(): requesting more than max tokens will always block" )
					.scope( typeid(ResTChoice<Token>) );
	}

	base::Process* currentProcess = getCurrentProcess();
	base::SimTime waitTime = 0;

	// compute order of service
	ResTBase< Token >::acquireWait.inSort( currentProcess );

	// the return vector to be filled by getMatchingTokens()
	std::vector< Token* > matches;

	if( ResTBase< Token >::acquireWait.getTop() != currentProcess ||
		! getMatchingTokens( select, n, matches ) )
	{
		// not enough matching tokens or
		// not the first process in queue

		ODEMX_TRACE << this->log( "acquire failed" )
						.detail( "partner", currentProcess->getLabel() )
						.detail( "requested tokens", n )
						.scope( typeid(ResTChoice<Token>) );
		// observer
		ODEMX_OBS_T(ResTChoiceObserver<Token>, AcquireFail(this, n));

		// statistics
		base::SimTime waitStart = getCurrentTime();

		do
		{
			currentProcess->sleep();

			// handle interrupt
			if( currentProcess->isInterrupted() )
			{
				ResTBase< Token >::acquireWait.remove( currentProcess );
				return std::vector< Token* >();
			}
		}
		while( ResTBase< Token >::acquireWait.getTop() != currentProcess ||
				 ! getMatchingTokens( select, n, matches ) );

		// block released
		// statistics
		waitTime = getCurrentTime() - waitStart;
	}

	ODEMX_TRACE << this->log( "change token number" ).valueChange( ResTBase<Token>::tokenNumber, ResTBase<Token>::tokenNumber - n )
					.scope( typeid(ResTChoice<Token>) );

	// observer
	ODEMX_OBS_ATTR_T(ResTChoiceObserver<Token>, TokenNumber, ResTBase< Token >::tokenNumber, ResTBase< Token >::tokenNumber-n);

	// acquire token
	ResTBase< Token >::tokenNumber -= n;

	// fill return vector with pointers to free resource objects
	typename std::vector< Token* >::iterator i;

	for( i = matches.begin(); i != matches.end(); ++i )
	{
		// store the current user of selected resources in ResTBase< Token >::tokenInfo

		if( ResTBase< Token >::tokenInfo[ *i ] == 0 )
		{
			ResTBase< Token >::tokenInfo[ *i ] = currentProcess;
		}
		else
		{
			// this error should never happen
			error << this->log( "ResTChoice::acquire(): selected token is owned by another Process" )
					.scope( typeid(ResTChoice<Token>) );
		}
	}

	ODEMX_TRACE << this->log( "acquire succeeded" )
			.detail( "partner", currentProcess->getLabel() )
			.detail( "requested tokens", n )
			.scope( typeid(ResTChoice<Token>) );

	// statistics
	statistics << this->count( "users" ).scope( typeid(ResTChoice<Token>) );
	statistics << this->update( "tokens", ResTBase< Token >::tokenNumber ).scope( typeid(ResTChoice<Token>) );
	statistics << this->update( "wait time", waitTime ).scope( typeid(ResTChoice<Token>) );

	// observer
	ODEMX_OBS_T(ResTChoiceObserver<Token>, AcquireSucceed(this, n));

	// remove process from queue
	ResTBase< Token >::acquireWait.remove( currentProcess );

	// awake next
	awakeFirst( ResTBase< Token >::getAcquireWait() );

	return matches;
}

template < typename Token >
base::SimTime ResTChoice< Token >::getCurrentTime()
{
	return data::Producer::getSimulation().getTime();
}

template < typename Token >
base::Process* ResTChoice< Token >::getCurrentProcess()
{
	return data::Producer::getSimulation().getCurrentProcess();
}

template < typename Token >
base::Sched* ResTChoice< Token >::getCurrentSched()
{
	return data::Producer::getSimulation().getCurrentSched();
}

template < typename Token >
bool ResTChoice< Token >::getMatchingTokens( 
		typename ResTChoice< Token >::Selection select,
		std::size_t n, std::vector< Token* >& result )
{
	std::size_t count = 0;
	result.clear();

	// find matching tokens
	typename std::map< Token*, base::Process* >::iterator i;
	for( i = ResTBase< Token >::tokenInfo.begin(); i != ResTBase< Token >::tokenInfo.end(); ++i )
	{
		// count all matches
		if( select( getCurrentProcess(), i->first ) )
		{
			count++;

			// store free matches in result vector
			if( i->second == 0 )
			{
				result.push_back( i->first );

				if( result.size() == n )
					break;
			}
		}
	}

	if( count < n )
	{
		warning << this->log( "ResTChoice::getMatchingTokens(): too few matching tokens in resource, will always block" )
				.scope( typeid(ResTChoice<Token>) );
	}

	return result.size() == n;
}

} } // namespace odemx::sync
