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

	\author Ronald Kluth

	\date created at 2008/02/18

	\brief Implementation of odemx::sync::ResTBase

	\sa ResTBase.h

	\since 2.1
*/

#include <odemx/base/DefaultSimulation.h>
#include <odemx/synchronization/res/ResTBase.h>

#include <odemx/base/Process.h>

namespace odemx {
namespace synchronization {

template < typename Token >
ResTBase< Token >::ResTBase( base::Simulation& sim,
					 const data::Label& label,
					 typename ResTBase< Token >::TokenVec& initialResources,
					 typename ResTBase< Token >::ObserverType* o /* = 0*/ )
:	data::Producer( sim, label )
,	data::Observable< ResTBaseObserver< Token > >( o )
,	tokenLimit( initialResources.size() )
,	tokenStore( initialResources.begin(), initialResources.end() )
,	acquireWait( sim, label + " acquire queue" )
{
	// tokenStore already filled in initializer list

	// set initial tokenNumber
	tokenNumber = tokenStore.size();

	// fill map tokenInfo with addresses of tokens and 0's for owner Processes
	typename std::list< Token >::iterator i;
	for( i = tokenStore.begin(); i != tokenStore.end(); ++i )
	{
		tokenInfo.insert( std::make_pair( &(*i), static_cast< base::Process* >(0) ) );
	}

	ODEMX_TRACE << this->log( "create" ).scope( typeid(ResTBase<Token>) );

	statistics << this->param( "queue", acquireWait.getLabel() ).scope( typeid(ResTBase<Token>) );
	statistics << this->param( "initial tokens", tokenStore.size() ).scope( typeid(ResTBase<Token>) );
	statistics << this->param( "initial token limit", tokenLimit ).scope( typeid(ResTBase<Token>) );
	statistics << this->update( "tokens", tokenNumber ).scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, Create(this));
}

template < typename Token >
ResTBase< Token >::~ResTBase()
{
	ODEMX_TRACE << this->log( "destroy" ).scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, Destroy(this));
}

template < typename Token >
void ResTBase< Token >::release( Token* t )
{
	// put free resource into a vector
	std::vector< TokenType* > vec;
	vec.push_back( t );

	// handle that with release() below
	return release( vec );
}

template < typename Token >
void ResTBase< Token >::release( std::vector< TokenType* >& releaseVec )
{
	base::Process* currentProcess = getCurrentProcess();
	std::size_t n = releaseVec.size();

	// for each released resource, update the tokenInfo map
	typename std::vector< Token* >::iterator released;
	typename std::map< Token*, base::Process* >::iterator stored;

	std::size_t countedReleases = 0;
	for( released = releaseVec.begin(); released != releaseVec.end(); ++released )
	{
		// try to get iterator to released token in tokenInfo
		stored = tokenInfo.find( *released );

		// check if the iterator is valid and whether
		// the currentProcess Process really owns that resource
		if( stored != tokenInfo.end() && stored->second == currentProcess )
		{
			// released resource found, set the resource user to 0
			stored->second = static_cast< base::Process* >(0);
			++countedReleases;
			continue;
		}

		if( stored == tokenInfo.end() )
		{
			error << this->log( "ResTBase::release(): token not found in token info" )
					.scope( typeid(ResTBase<Token>) );
		}
		else if( stored->second != currentProcess )
		{
			error << this->log( "ResTBase::release(): token returned by wrong user" )
					.scope( typeid(ResTBase<Token>) );
		}
	}

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

	// observer
	ODEMX_OBS_ATTR_T(ResTBaseObserver<Token>, TokenNumber, tokenNumber, tokenNumber+countedReleases);

	// release token
	tokenNumber += countedReleases;

	// check for failure
	if ( countedReleases < n )
	{
		warning << this->log( "ResTBase::release(): could not release all tokens" )
					.scope( typeid(ResTBase<Token>) );

		ODEMX_TRACE << this->log( "release failed" )
						.detail( "partner", getPartner() )
						.detail( "released tokens", n - countedReleases )
						.scope( typeid(ResTBase<Token>) );

		// observer
		ODEMX_OBS_T(ResTBaseObserver<Token>, ReleaseFail(this, n - countedReleases));
	}

	ODEMX_TRACE << this->log( "release succeeded")
					.detail( "partner", getPartner() )
					.detail( "released tokens", countedReleases )
					.scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, ReleaseSucceed(this, countedReleases));

	statistics << this->count( "providers" ).scope( typeid(ResTBase<Token>) );
	statistics << this->update( "tokens", tokenNumber ).scope( typeid(ResTBase<Token>) );

	// wake up waiting process objects
	awakeFirst( &acquireWait );
}

template < typename Token >
void ResTBase< Token >::transfer( Token* t, base::Process* oldOwner,
		base::Process* newOwner )
{
	std::vector< Token* > v;
	v.push_back( t );
	return transfer( v, oldOwner, newOwner );
}

template < typename Token >
void ResTBase< Token >::transfer( std::vector< Token* >& v,
		base::Process* oldOwner, base::Process* newOwner )
{
	assert( oldOwner );
	assert( newOwner );

	typename std::vector< Token* >::iterator transferToken;
	typename std::map< Token*, base::Process* >::iterator found;

	std::size_t transferCount = 0;

	// set a new owner for the given tokens
	for( transferToken = v.begin(); transferToken != v.end(); ++transferToken )
	{
		found = tokenInfo.find( *transferToken );

		// token not found ?
		if( found == tokenInfo.end() )
		{
			error << this->log( "ResTBase::transfer(): transfer token not found in token info" )
					.scope( typeid(ResTBase<Token>) );
			continue;
		}

		// correct owner ?
		if( found->second == oldOwner )
		{
			found->second = newOwner;
			++transferCount;
		}
		else // oldOwner is different from owner stored in tokenInfo
		{
			error << this->log( "ResTBase::transfer(): transfer token owned by another Process" )
					.scope( typeid(ResTBase<Token>) );
		}
	}

	ODEMX_TRACE << this->log( "transfer" )
					.detail( "amount", transferCount )
					.detail( "old owner", oldOwner->getLabel() )
					.detail( "new owner", newOwner->getLabel() )
					.scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, Transfer(this, transferCount, oldOwner, newOwner));
}

template < typename Token >
void ResTBase< Token >::control( Token t )
{
	ODEMX_TRACE << this->log( "add tokens" )
			.detail( "amount", 1 ).scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, Control(this, 1));

	// increase max by one
	++tokenLimit;

	// add token to tokenStore and tokenInfo
	tokenStore.push_back( t );
	tokenInfo.insert( std::make_pair( &(tokenStore.back()), static_cast< base::Process* >(0) ) );
	++tokenNumber;

	// wake up waiting process objects
	awakeFirst( &acquireWait );
}

template < typename Token >
void ResTBase< Token >::control( typename ResTBase< Token >::TokenVec& addVec )
{
	std::size_t n = addVec.size();

	ODEMX_TRACE << this->log( "add tokens" )
			.detail( "amount", n ).scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, Control(this, n));

	// increase by number of added tokens
	tokenLimit += n;

	// fill tokenStore and tokenInfo with tokens from addVec
	// as long as the tokenLimit is not reached yet
	typename std::vector< Token >::iterator i;
	for( i = addVec.begin();
		 i != addVec.end() && tokenStore.size() < tokenLimit;
		 ++i )
	{
		tokenStore.push_back( *i );
		tokenInfo.insert( std::make_pair( &(tokenStore.back()), static_cast< base::Process* >(0) ) );
		++tokenNumber;
	}

	// wake up waiting process objects
	awakeFirst( &acquireWait );
}

template < typename Token >
std::vector< Token > ResTBase< Token >::unControl( std::size_t n /* = 1 */)
{
	if( n > tokenStore.size() )
	{
		error << this->log( "ResTBase::unControl(): attempt to remove more than max tokens" )
				.scope( typeid(ResTBase<Token>) );
		return std::vector< Token >();
	}

	if( ! getCurrentSched() ||
		getCurrentSched()->getSchedType() != base::Sched::PROCESS )
	{
		error << this->log( "ResTBase::unControl(): called by non-Process object" )
				.scope( typeid(ResTBase<Token>) );
		return std::vector< Token >();
	}

	ODEMX_TRACE << this->log( "remove tokens" )
			.detail( "amount", n ).scope( typeid(ResTBase<Token>) );

	// observer
	ODEMX_OBS_T(ResTBaseObserver<Token>, UnControl(this, n));

	base::Process* currentProcess = getCurrentProcess();

	// acquire enough token
	// compute order of service
	acquireWait.inSort( currentProcess );

	if( n > tokenNumber ||
		acquireWait.getTop() != currentProcess )
	{
		// not enough tokens or not the first process in queue

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

			// handle interrupt
			if( currentProcess->isInterrupted() )
			{
				acquireWait.remove( currentProcess );
				return std::vector< Token >();
			}
		}

		// block released
	}

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

	// observer
	ODEMX_OBS_ATTR_T(ResTBaseObserver<Token>, TokenNumber, tokenNumber, tokenNumber-n);

	// fill return vector with the uncontrolled token objects
	typename std::map< Token*, base::Process* >::iterator tInfo = tokenInfo.begin();
	std::vector< Token > unControlTokens;

	std::size_t acquired = 0;
	while( tInfo != tokenInfo.end() && acquired < n )
	{
		if( tInfo->second == 0 )
		{
			unControlTokens.push_back( *(tInfo->first) );
			++acquired;

			// remove the uncontrolled token from store
			tokenStore.remove( *(tInfo->first) );

			// remove the uncontrolled token from managing info map
			// by using a copy of the non-incremented iterator returned by op++
			tokenInfo.erase( tInfo++ );
		}
		else
		{
			++tInfo;
		}
	}

	// acquire token
	tokenNumber -= acquired;

	// adjust token limit
	tokenLimit -= acquired;

	// remove process from queue
	acquireWait.remove( currentProcess );

	// awake next waiting process
	awakeFirst( &acquireWait );

	// return tokens
	return unControlTokens;
}


template < typename Token >
std::size_t ResTBase< Token >::getTokenNumber() const
{
	return tokenNumber;
}

template < typename Token >
std::size_t ResTBase< Token >::getTokenLimit() const
{
	return tokenLimit;
}

template < typename Token >
const std::map< Token*, base::Process* >& ResTBase< Token >::getTokenInfo() const
{
	return tokenInfo;
}

template < typename Token >
const std::list< Token >& ResTBase< Token >::getTokenStore() const
{
	return tokenStore;
}

template < typename Token >
const base::ProcessList& ResTBase< Token >::getWaitingProcesses() const
{
	return acquireWait.getList();
}

template < typename Token >
Queue* ResTBase< Token >::getAcquireWait()
{
	return &acquireWait;
}

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

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

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

template < typename Token >
const data::Label& ResTBase< Token >::getPartner()
{
	if( getSimulation().getCurrentSched() != 0 )
	{
		return getSimulation().getCurrentSched()->getLabel();
	}

	return getSimulation().getLabel();
}

} } // namespace odemx::sync
