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

	\author Ronald Kluth

	\date created at 2007/04/04

	\brief Implementation of odemx::sync::Memory

	\sa Memory.h

	\since 2.0
*/

#include <odemx/base/Process.h>
#include <odemx/base/DefaultSimulation.h>
#include <odemx/synchronization/Memory.h>
#include <CppLog/Channel.h>

#include <algorithm>
#ifdef _MSC_VER
#include <functional>
#else
#include <tr1/functional>
#endif


namespace odemx {
namespace synchronization {

Memory::Memory( base::Simulation& sim, const data::Label& label, Type type,
		MemoryObserver* obs )
:	data::Producer( sim, label )
,	data::Observable< MemoryObserver >( obs )
,	memoryType_( type )
,	memoryList_()
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Memory) );

	statistics << update( "waiting", countWaiting() ).scope( typeid(Memory) );

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

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

bool Memory::remember( base::Sched* newObject )
{
	assert( newObject );

	// check if object is already memorized
	base::SchedList::const_iterator found =
			std::find( memoryList_.begin(), memoryList_.end(), newObject );

	if ( found != memoryList_.end() )
	{
		warning << log( "Memory::remember(): object already stored in list" )
				.detail( "partner", newObject->getLabel() )
				.scope( typeid(Memory) );

		return false;
	}

	// store the new object
	memoryList_.push_back( newObject );

	ODEMX_TRACE << log( "remember" ).detail( "partner", newObject->getLabel() )
			.scope( typeid(Memory) );

	statistics << update( "waiting", countWaiting() ).scope( typeid(Memory) );

	// observer
	ODEMX_OBS( MemoryObserver, Remember( this, newObject ) );

	return true;
}

bool Memory::forget( base::Sched* rememberedObject )
{
	assert( rememberedObject );

	// check if rememberedObject is in memoryList_
	base::SchedList::iterator found =
			std::find( memoryList_.begin(), memoryList_.end(), rememberedObject );

	// error if not found
	if( found == memoryList_.end() )
	{
		error << log( "Memory::forget(): object not stored in list" )
				.detail( "partner", rememberedObject->getLabel() )
				.scope( typeid(Memory) );

		return false;
	}

	// remove the object from the list
	memoryList_.erase( found );

	ODEMX_TRACE << log( "forget" ).detail( "partner", rememberedObject->getLabel() )
			.scope( typeid(Memory) );

	statistics << update( "waiting", countWaiting() ).scope( typeid(Memory) );

	// observer
	ODEMX_OBS( MemoryObserver, Forget( this, rememberedObject ) );

	return true;
}

bool Memory::processIsWaiting (base::Process& process) const {
	base::SchedList::const_iterator found =
			std::find( memoryList_.begin(), memoryList_.end(), &process );
  return found != memoryList_.end ();
}

void Memory::eraseMemory()
{
	memoryList_.clear();

	ODEMX_TRACE << log( "erase" ).scope( typeid(Memory) );

	statistics << update( "waiting", countWaiting() ).scope( typeid(Memory) );
}

void Memory::alert()
{
	alert( this );
}

void Memory::alert( IMemory* caller )
{
	assert( caller );

	// warning if list is empty
	if( memoryList_.empty() )
	{
		warning << log( "Memory::alert(): list empty, cannot alert any object" )
				.scope( typeid(Memory) );
		return;
	}

	// trace record with the whole memory list as details
	traceAlert();

	// observer
	ODEMX_OBS( MemoryObserver, Alert( this ) );

	// iterate over memoryList_ and schedule processes
	base::SchedList::iterator iter;
	for( iter = memoryList_.begin(); iter != memoryList_.end(); ++iter )
	{
		// check if Sched* really is a sleeping process and
		// reschedule CREATED and IDLE processes in memory
		if( checkSchedForAlert( *iter ) )
		{
			// wake up the process
			static_cast< base::Process* >( *iter )->alertProcess( caller );
		}
		else
		{
			warning << log( "Memory::alert(): could not alert Sched object" )
					.detail( "partner", ( *iter )->getLabel() )
					.scope( typeid(Memory) );
		}
	}

	// remove stored objects from memory
	eraseMemory();
}

bool Memory::checkSchedForAlert( base::Sched* s )
{
	assert( s );

	// check if s really is a sleeping process
	if( s->getSchedType() != base::Sched::PROCESS )
	{
		warning << log( "Memory::checkSchedForAlert(): object is not a process" )
				.detail( "partner", s->getLabel() )
				.scope( typeid(Memory) );
		return false;
	}

	base::Process* p = static_cast< base::Process* >( s );

	// only sleeping Processes are allowed to be alerted
	switch( p->getProcessState() )
	{
	case base::Process::CREATED: // fall-through
	case base::Process::IDLE:
		return true;

	case base::Process::CURRENT:
		warning << log( "Memory::checkSchedForAlert(): attempt to alert current process" )
				.detail( "partner", p->getLabel() )
				.scope( typeid(Memory) );
		break;

	case base::Process::RUNNABLE:
		warning << log( "Memory::checkSchedForAlert(): process already scheduled" )
				.detail( "partner", p->getLabel() )
				.scope( typeid(Memory) );
		break;

	case base::Process::TERMINATED:
		error << log( "Memory::checkSchedForAlert(): process already terminated" )
				.detail( "partner", p->getLabel() )
				.scope( typeid(Memory) );
		break;

	}
	return false;
}

bool Memory::isAvailable()
{
	return false;
}


bool Memory::waiting() const
{
	return ! memoryList_.empty();
}

Memory::SizeType Memory::countWaiting() const
{
	return memoryList_.size();
}

const base::SchedList& Memory::getWaiting() const
{
	return memoryList_;
}

IMemory::Type Memory::getMemoryType() const
{
	return memoryType_;
}

void Memory::traceAlert()
{
	if( ODEMX_TRACE_ENABLED )
	{
		// create record for alert with scope Memory
		data::SimRecord record = log( "alert" ).scope( typeid(Memory) );

		// add record details: stored objects
		base::SchedList::const_iterator iter;
		for( iter = memoryList_.begin(); iter != memoryList_.end(); ++iter )
		{
			record.detail( "partner", ( *iter )->getLabel() );
		}

		// log the record to the trace
		ODEMX_TRACE << record;
	}
}

} } // namespace odemx::sync
