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

	\author Ralf Gerstenberger

	\date created at 2002/03/21

	\brief Implementation of odemx::sync::Res

	\sa Res.h

	\since 1.0
*/

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

#include <algorithm>
#include <cassert>
#include <string>

namespace odemx {
namespace synchronization {

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

	statistics << param( "queue", acquireQueue_.getLabel() ).scope( typeid(Res) );
	statistics << param( "initial tokens", initialTokens ).scope( typeid(Res) );
	statistics << param( "initial token limit", tokenLimit_ ).scope( typeid(Res) );
	statistics << update( "tokens", tokens_ ).scope( typeid(Res) );

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

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

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

	if( n > getTokenLimit() )
	{
		warning << log( "Res::acquire(): requesting more than max tokens will always block" )
				.scope( typeid(Res) );
	}

	base::Process* currentProcess = getCurrentProcess();

	// compute order of service
	acquireQueue_.inSort( currentProcess );

	// statistics
	base::SimTime waitTime = 0;

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

		// observer
		ODEMX_OBS(ResObserver, AcquireFail(this, n));

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

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

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

		// statistics
		waitTime = getTime() - waitStart;
	}

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

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

	// acquire tokens
	tokens_ -= n;

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

	// statistics
	statistics << count( "users" ).scope( typeid(Res) );
	statistics << update( "tokens", tokens_ ).scope( typeid(Res) );
	statistics << update( "wait time", waitTime ).scope( typeid(Res) );

	// observer
	ODEMX_OBS(ResObserver, AcquireSucceed(this, n));

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

	// awake next process
	awakeFirst( &acquireQueue_ );

	return n;
}

void Res::release( std::size_t n )
{
	// check for sufficient release space
	if( ( tokens_ + n ) > tokenLimit_ )
	{
		ODEMX_TRACE << log( "release failed" )
				.detail( "partner", getPartner() )
				.detail( "released tokens", n )
				.scope( typeid(Res) );

		// observer
		ODEMX_OBS(ResObserver, ReleaseFail(this, n));

		error << log( "Res::release(): limit reached, could not release all tokens" )
				.scope( typeid(Res) );

		// set released token number to whatever space was left
		n = tokenLimit_ - tokens_;
	}

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

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

	// release tokens
	tokens_ += n;

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

	ODEMX_TRACE << log( "release succeeded" )
			.detail( "partner", getPartner() )
			.detail( "released tokens", n )
			.scope( typeid(Res) );

	// observer
	ODEMX_OBS(ResObserver, ReleaseSucceed(this, n));

	// awake process waiting for release
	awakeFirst( &acquireQueue_ );
}

void Res::control( std::size_t n )
{
	ODEMX_TRACE << log( "increase token limit" )
			.detail( "amount", n ).scope( typeid(Res) );

	// observer
	ODEMX_OBS(ResObserver, Control(this, n));

	// change max token limit
	tokenLimit_ += n;

	// add tokens
	release( n );
}

std::size_t Res::unControl( std::size_t n )
{
	ODEMX_TRACE << log( "decrease token limit" )
		.detail( "amount", n ).scope( typeid(Res) );

	// observer
	ODEMX_OBS(ResObserver, UnControl(this, n));

	// secure enough tokens
	std::size_t tmp = acquire( n );

	// remove tokens
	tokenLimit_ -= tmp;

	return tmp;
}

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

std::size_t Res::getTokenLimit() const
{
	return tokenLimit_;
}

const base::ProcessList& Res::getWaitingProcesses() const
{
	return acquireQueue_.getList();
}

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

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

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

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

	return getSimulation().getLabel();
}

} } // namespace odemx::sync
