//----------------------------------------------------------------------------
//	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 Bin.cpp

	\author Ralf Gerstenberger

	\date created at 2002/03/26

	\brief Implementation of odemx::sync::Bin

	\sa Bin.h

	\since 1.0
*/

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

#include <cassert>

namespace odemx {
namespace synchronization {

Bin::Bin( base::Simulation& sim, const data::Label& label, std::size_t initialTokens, BinObserver* obs )
:	data::Producer( sim, label )
,	data::Observable< BinObserver >( obs )
,	tokens_( initialTokens )
,	takeQueue_( sim, getLabel() + " queue" )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Bin) );

	statistics << param( "queue", takeQueue_.getLabel() ).scope( typeid(Bin) );
	statistics << param( "initial tokens", initialTokens ).scope( typeid(Bin) );
	statistics << update( "tokens", tokens_ ).scope( typeid(Bin) );

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

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

std::size_t Bin::take( std::size_t n )
{
	// resource handling only implemented for processes
	if( ! getCurrentSched()	|| getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << log( "Bin::take(): called by non-Process object" )
				.scope( typeid(Bin) );
		return 0;
	}

	base::Process* currentProcess = getCurrentProcess();

	// compute order of service, insert the process into the queue
	takeQueue_.inSort( currentProcess );

	// if not enough tokens or not the first process to serve
	if( n > tokens_ || currentProcess != takeQueue_.getTop() )
	{
		ODEMX_TRACE << log( "take failed" )
				.detail( "partner", getPartner() )
				.detail( "requested tokens", n )
				.scope( typeid(Bin) );

		// observer
		ODEMX_OBS(BinObserver, TakeFail(this, n));

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

		// block execution
		while( n > tokens_ || currentProcess != takeQueue_.getTop() )
		{
			currentProcess->sleep();

			if( currentProcess->isInterrupted() )
			{
				takeQueue_.remove( currentProcess );
				return 0;
			}
		}
		// block released here

		// statistics, log the waiting period
		base::SimTime waitTime = getTime() - waitStart;
		statistics << update( "wait time", waitTime ).scope( typeid(Bin) );
	}

	ODEMX_TRACE << log( "change token number" ).valueChange( tokens_, tokens_ - n )
			.scope( typeid(Bin) );

	// observer
	ODEMX_OBS_ATTR(BinObserver, TokenNumber, tokens_, tokens_ - n);

	// take tokens
	tokens_ -= n;

	ODEMX_TRACE << log( "take succeeded" )
			.detail( "partner", getPartner() )
			.detail( "requested tokens", n )
			.scope( typeid(Bin) );

	// statistics
	statistics << count( "users" ).scope( typeid(Bin) );
	statistics << update( "tokens", tokens_ ).scope( typeid(Bin) );

	// observer
	ODEMX_OBS(BinObserver, TakeSucceed(this, n));

	// remove from list
	takeQueue_.remove( currentProcess );

	// awake next process
	awakeFirst( &takeQueue_ );

	return n;
}

void Bin::give( std::size_t n )
{
	ODEMX_TRACE << log( "change token number" ).valueChange( tokens_, tokens_ + n )
			.scope( typeid(Bin) );

	// observer
	ODEMX_OBS_ATTR(BinObserver, TokenNumber, tokens_, tokens_+n);

	// release tokens
	tokens_ += n;

	ODEMX_TRACE << log( "give" )
			.detail( "partner", getPartner() )
			.detail( "given tokens", n )
			.scope( typeid(Bin) );

	// statistics
	statistics << count( "providers" ).scope( typeid(Bin) );
	statistics << update( "tokens", tokens_ ).scope( typeid(Bin) );

	// observer
	ODEMX_OBS(BinObserver, Give(this, n));

	// awake next process
	awakeFirst( &takeQueue_ );
}

std::size_t Bin::getTokenNumber() const
{
	return tokens_;
}

const base::ProcessList& Bin::getWaitingProcesses() const
{
	return takeQueue_.getList();
}

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

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

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

const data::Label& Bin::getPartner()
{
	if( getSimulation().getCurrentSched() != 0 )
	{
		return getSimulation().getCurrentSched()->getLabel();
	}

	return getSimulation().getLabel();
}

} } // namespace odemx::sync
