//------------------------------------------------------------------------------
//	Copyright (C) 2008, 2009 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 BinTImpl.cpp
 * @author Ronald Kluth
 * @date created at 2008/02/18
 * @brief Implementation of odemx::sync::BinT
 * @sa BinTImpl.h
 * @since 2.1
 */

#include <odemx/util/TypeToString.h>
#include <odemx/synchronization/bin/BinTImpl.h>
#include <odemx/base/Sched.h>
#include <odemx/base/Process.h>

#include <iterator>

namespace odemx {
namespace synchronization {

template< typename TokenT >
BinT< TokenT >::BinT( base::Simulation& sim, const data::Label& label,
		BinTObserver< TokenT >* obs )
:	data::Producer( sim, label )
,	data::Observable< BinTObserver< TokenT > >( obs )
,	tokenStorage_()
,	tokenCount_( 0 )
,	takeQueue_( sim, getLabel() + ".queue" )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(BinT< TokenT >) );

	statistics << param( "queue", takeQueue_.getLabel() ).scope( typeid(BinT< TokenT >) );
	statistics << param( "initial tokens", tokenCount_ ).scope( typeid(BinT< TokenT >) );
	statistics << param( "token type", odemx::typeToString( typeid(TokenT) ) )
			.scope( typeid(BinT< TokenT >) );
	
	statistics << update( "tokens", tokenCount_ ).scope( typeid(BinT< TokenT >) );

	// observer
	ODEMX_OBS_T(BinTObserver< TokenT >, Create(this));
}

template < typename TokenT >
BinT< TokenT >::BinT( base::Simulation& sim, const data::Label& label,
		typename BinT< TokenT >::TokenVec& initialTokens,
		BinTObserver< TokenT >* obs )
:	data::Producer( sim, label )
,	data::Observable< BinTObserver< TokenT > >( obs )
,	tokenStorage_( initialTokens.begin(), initialTokens.end() )
,	tokenCount_( tokenStorage_.size() )
,	takeQueue_( sim, getLabel() + ".queue" )
{
	ODEMX_TRACE << log( "create and tokens" ).scope( typeid(BinT< TokenT >) );

	statistics << param( "queue", takeQueue_.getLabel() ).scope( typeid(BinT< TokenT >) );
	statistics << param( "initial tokens", tokenCount_ ).scope( typeid(BinT< TokenT >) );
	statistics << param( "token type", odemx::typeToString( typeid(TokenT) ) )
			.scope( typeid(BinT< TokenT >) );

	statistics << update( "tokens", tokenCount_ ).scope( typeid(BinT< TokenT >) );

	// observer
	ODEMX_OBS_T(BinTObserver< TokenT >, Create(this));
}

template < typename TokenT >
BinT< TokenT >::~BinT()
{
	ODEMX_TRACE << log( "destroy" ).scope( typeid(BinT< TokenT >) );
}

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

	base::Process* currentProcess = getCurrentProcess();

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

	if( n > tokenCount_ || currentProcess != takeQueue_.getTop() )
	{
		ODEMX_TRACE << log( "take failed" )
				.detail( "partner", currentProcess )
				.detail( "requested tokens", n )
				.scope( typeid(BinT< TokenT >) );

		// observer
		ODEMX_OBS_T(BinTObserver< TokenT >, TakeFail(this, n));

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

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

			if( currentProcess->isInterrupted() )
			{
				takeQueue_.remove( currentProcess );
				return std::auto_ptr< TokenVec >(0);
			}
		}
		// block released here

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

	// create return vector
	std::auto_ptr< typename BinT< TokenT >::TokenVec > tokenReturn(
			new typename BinT< TokenT >::TokenVec(
					tokenStorage_.begin(), tokenStorage_.begin() + n ) );

	ODEMX_TRACE << log( "change token number" ).valueChange( tokenCount_, tokenCount_ - n )
			.scope( typeid(BinT< TokenT >) );

	// observer
	ODEMX_OBS_ATTR_T(BinTObserver< TokenT >, TokenNumber, tokenCount_, tokenCount_ - n);

	// remove tokens from store
	tokenStorage_.erase( tokenStorage_.begin(), tokenStorage_.begin() + n );
	tokenCount_ -= n;

	ODEMX_TRACE << log( "take succeeded" )
			.detail( "partner", currentProcess )
			.detail( "requested tokens", n )
			.scope( typeid(BinT< TokenT >) );

	// statistics
	statistics << count( "users" ).scope( typeid(BinT< TokenT >) );
	statistics << update( "tokens", tokenCount_ ).scope( typeid(BinT< TokenT >) );

	// observer
	ODEMX_OBS_T(BinTObserver< TokenT >, TakeSucceed(this, n));

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

	// awake next
	awakeFirst( &takeQueue_ );

	// return token
	return tokenReturn;
}

template < typename TokenT >
void BinT< TokenT >::give( const TokenT& token )
{
	std::vector< TokenT > tmpVec;
	tmpVec.push_back( token );
	give( tmpVec );
}

template < typename TokenT >
void BinT< TokenT >::give( const typename BinT< TokenT >::TokenVec& tokens )
{
	if ( tokens.empty() )
	{
		error << log( "BinT::give(): vector empty; no tokens provided" )
				.scope( typeid(BinT< TokenT >) );
		return;
	}

	std::size_t n = tokens.size();

	ODEMX_TRACE << log( "change token number" ).valueChange( tokenCount_, tokenCount_ + n )
			.scope( typeid(BinT< TokenT >) );

	// observer
	ODEMX_OBS_ATTR_T(BinTObserver< TokenT >, TokenNumber, tokenCount_, tokenCount_ + n);

	// transfer token to tokenStore
	tokenStorage_.insert( tokenStorage_.end(), tokens.begin(), tokens.end() );
	// set token count
	tokenCount_ += n;

	ODEMX_TRACE << log( "give" )
			.detail( "partner", getCurrentProcess() )
			.detail( "given tokens", n )
			.scope( typeid(BinT< TokenT >) );

	// statistics
	statistics << count( "providers" ).scope( typeid(BinT< TokenT >) );
	statistics << update( "tokens", tokenCount_ ).scope( typeid(BinT< TokenT >) );

	// observer
	ODEMX_OBS_T(BinTObserver< TokenT >, Give(this, n));

	// wake up wating process objects
	awakeFirst( &takeQueue_ );
}

template < typename TokenT >
std::size_t BinT< TokenT >::getTokenNumber() const
{
	return tokenCount_;
}

template < typename TokenT >
const typename BinT< TokenT >::StorageType&
BinT< TokenT >::getTokenStore() const
{
	return tokenStorage_;
}

template < typename TokenT >
const base::ProcessList& BinT< TokenT >::getWaitingProcesses() const
{
	return takeQueue_.getList();
}

template < typename TokenT >
base::SimTime BinT< TokenT >::getCurrentTime() const
{
	return getSimulation().getTime();
}

template < typename TokenT >
base::Sched* BinT< TokenT >::getCurrentSched()
{
	return getSimulation().getCurrentSched();
}

template < typename TokenT >
base::Process* BinT< TokenT >::getCurrentProcess()
{
	return getSimulation().getCurrentProcess();
}

} } // namespace odemx::sync
