//------------------------------------------------------------------------------
//	Copyright (C) 2002, 2004, 2007, 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 Process.cpp
 * @author Ralf Gerstenberger
 * @date created at 2002/01/30
 * @brief Implementation of odemx::base::Process
 * @sa Process.h
 * @since 1.0
 */

#include <odemx/base/Process.h>
#include <odemx/base/Event.h>
#include <odemx/base/Simulation.h>
#include <odemx/synchronization/ProcessQueue.h>
#include <odemx/synchronization/Timer.h>
#include <odemx/synchronization/Memory.h>
#include <odemx/util/Exceptions.h>

#include <string>
#include <memory> // auto_ptr

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


namespace odemx {
namespace base {

Process::Process( Simulation& sim, const data::Label& label, ProcessObserver* obs )
:	Sched( sim, label, PROCESS )
,	Coroutine( &sim, obs )
,	data::Observable< ProcessObserver >( obs )
,	processState_( CREATED )
,	priority_( 0.0 )
,	executionTime_( static_cast< SimTime >(0) )
,	validReturn_( false )
,	returnValue_( 0 )
,	queue_( 0 )
,	enqueueTime_( static_cast< SimTime >(0) )
,	dequeueTime_( static_cast< SimTime >(0) )
,	queuePriority_( 0.0 )
,	isInterrupted_( false )
,	interrupter_( 0 )
,	isAlerted_( false )
,	alerter_( 0 )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Create(this));

	this->getSimulation().setProcessAsCreated( this );
}

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

	// the simulation must not have a pointer to a destroyed process
	getSimulation().removeProcessFromList( this );
}

void Process::activate()
{
	ODEMX_TRACE << log( "activate" ).detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Activate(this));

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// reset interrupt state
	resetInterrupt();

	// reset memory alert state
	resetAlert();

	// scheduling
	setExecutionTime( getTime() );
	getSimulation().getScheduler().insertSched( this );

	// continue simulation
	getSimulation().switchTo();
}

void Process::activateIn( SimTime t )
{
	if ( t < 0 ) {
		error << log( "Process::activateIn(): Parameter t less than 0 not allowed. Setting it to 0." )
				.scope( typeid(Process) );
		t = 0;
	}

	ODEMX_TRACE << log( "activate in" )
			.detail( "relative time", t )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, ActivateIn(this, t));

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// reset interrupt state
	resetInterrupt();

	// reset Memory::alert state
	resetAlert();

	// scheduling
	setExecutionTime( getTime() + t );
	getSimulation().getScheduler().insertSched( this );

	// continue simulation
	getSimulation().switchTo();
}

void Process::activateAt( SimTime t )
{
	if ( t < getTime () ) {
		error << log( "Process::activateAt(): Parameter t less than current time not allowed. Setting it to current time." )
				.scope( typeid(Process) );
		t = getTime();
	}

	ODEMX_TRACE << log( "activate at" )
			.detail( "absolute time", t )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, ActivateAt(this, t));

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// reset interrupt state
	resetInterrupt();

	// reset Memory::alert state
	resetAlert();

	// scheduling
	setExecutionTime( t );
	getSimulation().getScheduler().insertSched( this );

	// continue simulation
	getSimulation().switchTo();
}

void Process::activateBefore( Sched* p )
{
	assert( p );

	ODEMX_TRACE << log( "activate before" )
			.detail( "partner", getPartner() )
			.detail( "before", p-> getLabel() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, ActivateBefore(this, p));

	// state management
	if (!setProcessState(RUNNABLE))
	{
		return;
	}

	// reset interrupt state
	resetInterrupt();

	// reset Memory::alert state
	resetAlert();

	// scheduling
	getSimulation().getScheduler().insertSchedBefore( this, p );

	// continue simulation
	getSimulation().switchTo();
}

void Process::activateAfter( Sched* p )
{
	assert( p );

	ODEMX_TRACE << log( "activate after" )
			.detail( "partner", getPartner() )
			.detail( "after", p->getLabel() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, ActivateAfter(this, p));

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// reset interrupt state
	resetInterrupt();

	// reset Memory::alert state
	resetAlert();

	// scheduling
	getSimulation().getScheduler().insertSchedAfter( this, p );

	// continue simulation ?
	// this function should never require a context change
	// because scheduling after another Sched object requires
	// for that to be executed first;

	// if compSimulation() is called here, it leads to odd
	// stepping results because the caller of this function
	// has to continue its life cycle anyway

	// an attempt to make this work with self-activation:
	if( this == getCurrentSched() )
	{
		getSimulation().switchTo();
	}
}

void Process::genericHoldUntil(SimTime t) {

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// reset interrupt state
	resetInterrupt();

	// reset Memory::alert state
	resetAlert();

	// scheduling
	setExecutionTime( t );
	getSimulation().getScheduler().addSched( this );

	// continue simulation
	getSimulation().switchTo();
}

void Process::hold()
{
	ODEMX_TRACE << log( "hold" )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Hold(this));
	genericHoldUntil(getTime());
}

void Process::holdFor( SimTime t )
{
	if ( t < 0 ) {
		error << log( "Process::holdFor(): Parameter t less than 0 not allowed. Setting it to 0." )
				.scope( typeid(Process) );
		t = 0;
	}

	ODEMX_TRACE << log( "hold for" ).detail( "relative time", t )
			.detail( "partner", getPartner() ).scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, HoldFor(this, t));

	genericHoldUntil(getTime() + t);
}

void Process::holdUntil( SimTime t )
{
	if ( t < getTime () ) {
		error << log( "Process::activateAt(): Parameter t less than current time not allowed. Setting it to current time." )
				.scope( typeid(Process) );
		t = getTime();
	}

	ODEMX_TRACE << log( "hold until" ).detail( "absolute time", t )
			.detail( "partner", getPartner() ).scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, HoldUntil(this, t));

	genericHoldUntil(t);

}


// m0 always has to be given, else no matching call
synchronization::IMemory* Process::wait( synchronization::IMemory* m0, synchronization::IMemory* m1,
		synchronization::IMemory* m2, synchronization::IMemory* m3, synchronization::IMemory* m4, synchronization::IMemory* m5 )
{
	assert( m0 );

	// TODO: implement this and the other overload with vector object, not ptr
	// so the internally created MemoryVector is cleaned up automatically
	std::auto_ptr< synchronization::IMemoryVector > memvec( new synchronization::IMemoryVector );

	memvec->reserve( 6 );
	memvec->push_back( m0 );

	// add more parameters to vector, if they are given
	if( m1 ) memvec->push_back( m1 );
	if( m2 ) memvec->push_back( m2 );
	if( m3 ) memvec->push_back( m3 );
	if( m4 ) memvec->push_back( m4 );
	if( m5 ) memvec->push_back( m5 );

	synchronization::IMemory* alerter = wait( memvec.get() );
	return alerter;
}

synchronization::IMemory* Process::wait( synchronization::IMemoryVector* memvec )
{
	assert( memvec );

	if( memvec->empty() )
	{
		warning << log( "Process::wait(): no memory objects given" )
				.scope( typeid(Process) );

		return 0;
	}

	traceWait( *memvec );

	// observer
	ODEMX_OBS( ProcessObserver, Wait( this, memvec ) );

	// before suspending, check availability of memory objects
	synchronization::IMemoryVector::iterator iter;
	for( iter = memvec->begin(); iter != memvec->end(); ++iter )
	{
		synchronization::IMemory* current = *iter;

		if ( current->isAvailable() )
		{
			// log the available object
			data::Producer* labeled = dynamic_cast< data::Producer* >( current );
			if( labeled != 0 )
			{
				ODEMX_TRACE << log( "available" ).detail( "memory object", labeled->getLabel() )
						.scope( typeid(Process) );
			}
			// memory object cannot be cast to labeled object, use address
			else
			{
				ODEMX_TRACE << log( "available" )
						.detail( "memory object", "no label" )
						.scope( typeid(Process) );
			}

			// if a Timer is not set, do not return, it might be set elsewhere
			if( current->getMemoryType() == synchronization::IMemory::TIMER )
			{
				warning << log( "Process::wait(): unset timer available" )
				.detail( "memory object", static_cast< synchronization::Timer* >( current )->getLabel() )
				.scope( typeid(Process) );
			}
			else // other available Memo objects will be returned
			{
				return current;
			}
		}
	}

	// since none are available, register this process with the given Memory objects
	for( iter = memvec->begin(); iter != memvec->end(); ++iter )
	{
		(*iter)->remember( this );
	}

	// the alert mechanism is normally only used with wait()
	// reset alerter and alerted anyway, if set somewhere else
	resetAlert();

	// suspend process
	sleep();

	// ************* beyond this point, the process was awakened ************

	// trace of alert and interrupt in other methods

	// check for Memory alert
	if( isAlerted() )
	{
		synchronization::IMemory* currentAlerter = getAlerter();

		for( iter = memvec->begin(); iter != memvec->end(); ++iter )
		{
			// currentAlerter has already removed this process
      // Note: We have to check if the process current process is really deleted
      // from all waiting lists, as PortHeadT doesn't delete on alert.

      // we try to dynamic_cast to Memory*. If it is a Memory object, we know that we can
      // use its processIsWaiting method.
      if ( (*iter) != currentAlerter )
      {
				(*iter)->forget( this );
      } 
      else
      {
        odemx::synchronization::Memory *memory = dynamic_cast< odemx::synchronization::Memory* > (*iter);
        if ( memory && memory -> processIsWaiting ( *this ) ) 
          (*iter) -> forget ( this );
      }
		}

		// reset so outdated alerted/alerter cannot be used elsewhere by accident
		resetAlert();

		return currentAlerter;
	}
	// the sleep() period can be interrupted by other processes
	else if( isInterrupted() )
	{
		// remove this from all Memos
		for( iter = memvec->begin(); iter != memvec->end(); ++iter )
		{
			(*iter)->forget( this );
		}

		warning << log( "Process::wait(): waiting period ended by interrupt" )
				.detail( "interrupter", getInterrupter()->getLabel() )
				.scope( typeid(Process) );

		// when wait returns 0, a Process should check for interrupts
		return 0;
	}
	else
	{
		error << log( "Process::wait(): Process awakened without alert or interrupt" )
				.scope( typeid(Process) );
	}
	return 0;
}

void Process::alertProcess( synchronization::IMemory* alerter )
{
	assert( alerter );

	// trace alert and alerter
	data::Producer* labeled = dynamic_cast< data::Producer* >( alerter );
	if( labeled != 0 )
	{
		ODEMX_TRACE << log( "alert" )
				.detail( "memory object", labeled->getLabel() )
				.scope( typeid(Process) );
	}
	// memory object cannot be cast to labeled object, use address
	else
	{
		ODEMX_TRACE << log( "alert" )
				.detail( "memory object", reinterpret_cast<size_t>( alerter ) )
				.scope( typeid(Process) );
	}

	// observer
	ODEMX_OBS( ProcessObserver, Alert( this, alerter ) );

	isAlerted_ = true;
	alerter_ = alerter;

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// scheduling
	setExecutionTime( getTime() );
	getSimulation().getScheduler().insertSched( this );
}

bool Process::isAlerted() const
{
	return isAlerted_;
}

synchronization::IMemory* Process::getAlerter() const
{
	return alerter_;
}

void Process::resetAlert()
{
	isAlerted_ = false;
	alerter_ = 0;
}

void Process::resetInterrupt()
{
	isInterrupted_ = false;
	interrupter_ = 0;
}

bool Process::isInterrupted() const
{
	return isInterrupted_;
}

Sched* Process::getInterrupter()
{
	return interrupter_;
}

void Process::interrupt()
{
	// ignore self interrupt
	if( this == getCurrentSched() )
	{
		return;
	}

	if( isInterrupted() )
	{
		warning << log( "Process::interrupt(): process is already interrupted" )
				.scope( typeid(Process) );

		// return; ???
	}

	ODEMX_TRACE << log( "interrupt" )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Interrupt(this));

	// Set interrupt data
	isInterrupted_ = true;
	interrupter_ = getCurrentSched();

	// state management
	if( ! setProcessState( RUNNABLE ) )
	{
		return;
	}

	// scheduling
	setExecutionTime( getTime() );
	getSimulation().getScheduler().insertSched( this );

	// continue simulation
	getSimulation().switchTo();
}

void Process::sleep()
{
	ODEMX_TRACE << log( "sleep" )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Sleep(this));

	// reset interrupt state
	resetInterrupt();

	// state management
	if( ! setProcessState( IDLE ) )
	{
		return;
	}

	// scheduling
	getSimulation().getScheduler().removeSched( this );

	// continue simulation
	getSimulation().switchTo();
}

void Process::cancel()
{
	ODEMX_TRACE << log( "cancel" )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Cancel(this));

	if( setProcessState( TERMINATED ) )
	{
		// de-scheduling
		if( isScheduled() )
		{
			getSimulation().getScheduler().removeSched( this );
		}

		// continue simulation
		getSimulation().switchTo();
	}
}

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

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

SimTime Process::getCurrentTime() const
{
	warning << log( "Process::getCurrentTime(): This method is deprecated. Use Sched::getTime() instead" ).scope( typeid(Process) );
	return getTime ();
}


void Process::execute()
{
	if( processState_ != RUNNABLE && processState_ != CURRENT )
	{
		fatal << log( "Process::execute(): process is not RUNNABLE or CURRENT" )
				.scope( typeid(Process) );

		throw SchedulingException( "Process::execute(): process state not CURRENT or RUNNABLE" );
	}

	ODEMX_TRACE << log( "execute process" )
			.detail( "partner", getPartner() )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Execute(this));

	// old currentSched value is needed in getPartner()
	setProcessState( CURRENT );

	// switch to Coroutine
	switchTo();
}

Process::ProcessState Process::getProcessState() const
{
	return processState_;
}

bool Process::setProcessState( ProcessState newState )
{
	if( processState_ == TERMINATED )
	{
		error << log( "Process::setProcessState(): attempt to change state of terminated process" )
				.detail( "partner", getPartner() ).scope( typeid(Process) );

		return false;
	}

	if( newState == CREATED )
	{
		error << log( "Process::setProcessState(): process state is already set as CREATED" )
				.detail( "partner", getPartner() ).scope( typeid(Process) );

		return false;
	}

	ProcessState oldState = processState_;

	switch( newState )
	{
	case CREATED:
		getSimulation().setProcessAsCreated( this );
		break;
	case RUNNABLE:
		getSimulation().setProcessAsRunnable( this );
		break;
	case CURRENT:
		getSimulation().setCurrentProcess( this );
		break;
	case IDLE:
		getSimulation().setProcessAsIdle( this );
		break;
	case TERMINATED:
		getSimulation().setProcessAsTerminated( this );
		break;
	}

	// must set state here because the old state is still needed
	// above by getSimulation().setProcessAsXXX
	processState_ = newState;

	ODEMX_TRACE << log( "change process state" )
			.valueChange( stateToString( oldState ), stateToString( newState ) )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS_ATTR(ProcessObserver, ProcessState, oldState, newState);

	return true;
}

void Process::run()
{
	returnValue_ = main();

	// Process lifeline is finished [returned from main()]

	getSimulation().getScheduler().removeSched( this );

	// The return value is valid now
	validReturn_ = true;

	ODEMX_TRACE << log( "return" ).detail( "value", returnValue_ ).scope( typeid(Process) );

	// observer
	ODEMX_OBS(ProcessObserver, Return(this));

	setProcessState( TERMINATED );

	// in case the context is switched, this coroutine will not be reactivated
	// so it must be cleared here
	Coroutine::clear();

	// continue simulation
	getSimulation().switchTo();
}

bool Process::hasReturned() const
{
	return validReturn_;
}

int Process::getReturnValue() const
{
	if( ! validReturn_ )
	{
		warning << log( "Process::getReturnValue(): return value is not valid" )
				.scope( typeid(Process) );
	}
	return returnValue_;
}

Priority Process::getPriority() const
{
	return priority_;
}

Priority Process::setPriority( Priority newPriority )
{
	Priority oldPriority = priority_;
	priority_ = newPriority;

	ODEMX_TRACE << log( "change priority" ).valueChange( oldPriority, newPriority )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS_ATTR(ProcessObserver, Priority, oldPriority, newPriority);

	// changes in priority may influence ExecutionList
	if( isScheduled() || getProcessState() == CURRENT )
	{
		getSimulation().getScheduler().inSort( this );
	}

	// precise check for context change because running insertSchedAfter()
	// creates unexpected stepping if its call to setPriority() causes
	// compSimulation() to be called without need
//	if( getCurrentSched()
//		&& getSimulation().getScheduler().getNextSched() != getCurrentSched() )
//	{
		getSimulation().switchTo();
//	}

	return oldPriority;
}

SimTime Process::getExecutionTime() const
{
	if( processState_ != RUNNABLE && processState_ != CURRENT )
	{
		return static_cast< SimTime >(0);
	}
	return executionTime_;
}

SimTime Process::setExecutionTime( SimTime newTime )
{
	if ( newTime < getTime() ) {
			error << log( "Process::setExecutionTime(): newTime is less than current time. Setting to current time." )
				.scope( typeid(Process) );
			newTime = getTime();
	}

	SimTime oldTime = executionTime_;
	executionTime_ = newTime;

	ODEMX_TRACE << log( "change execution time" ).valueChange( oldTime, newTime )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS_ATTR(ProcessObserver, ExecutionTime, oldTime, newTime);

	return oldTime;
}

void Process::enqueue( synchronization::ProcessQueue* inQueue )
{
	assert( inQueue );

	if( queue_ != 0 )
	{
		fatal << log( "Process::enqueue(): process already listed in a queue" )
				.scope( typeid(Process) );

		throw QueueingException( "Process::enqueue(): process already listed in a queue" );
	}

	queue_ = inQueue;
	enqueueTime_ = getTime();
}

SimTime Process::getEnqueueTime() const
{
	return enqueueTime_;
}

void Process::dequeue( synchronization::ProcessQueue* outQueue )
{
	assert( outQueue );

	if( queue_ != outQueue )
	{
		fatal << log( "Process::dequeue(): process dequeued by wrong queue" )
				.scope( typeid(Process) );

		throw QueueingException( "Process::dequeue(): process dequeued by wrong queue" );
	}

	queue_ = 0;
	dequeueTime_ = getTime();
}

SimTime Process::getDequeueTime() const
{
	return dequeueTime_;
}

synchronization::ProcessQueue* Process::getQueue() const
{
	return queue_;
}

Priority Process::getQueuePriority() const
{
	return queuePriority_;
}

Priority Process::setQueuePriority( Priority newQPriority, bool reactivate )
{
	Priority oldQPriority = queuePriority_;
	queuePriority_ = newQPriority;

	ODEMX_TRACE << log( "change queue priority" ).valueChange( oldQPriority, newQPriority )
			.scope( typeid(Process) );

	// observer
	ODEMX_OBS_ATTR(ProcessObserver, QueuePriority, oldQPriority, newQPriority);

	// check if this Process is in a queue, sort queue again
	if( queue_ != 0 )
	{
		queue_->inSort( this );
	}

	// this Process might have become the first in a waiting queue,
	// it will still be blocked in queue if condition not met
	if( reactivate )
	{
		activate();
		getSimulation().switchTo();
	}
	return oldQPriority;
}

void Process::traceWait( synchronization::IMemoryVector& memvec )
{
#ifdef ODEMX_TRACE_ENABLED
    // create a record object
    data::SimRecord record = log( "wait" ).scope( typeid(Process) );

    // add details to record:
    synchronization::IMemoryVector::const_iterator iter;
    for( iter = memvec.begin(); iter != memvec.end(); ++iter )
    {
        data::Producer* labeled = dynamic_cast< data::Producer* >( *iter );

        // memvec contains IMemory*, check if it is castable to labeled object
        if( labeled )
        {
            record.detail( "memory object", labeled->getLabel() );
        }
        else // not a labeled object
        {
            record.detail( "memory object", "no label" );
        }
    }

    // log record to trace channel
    ODEMX_TRACE << record;
#endif
}

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

	return getSimulation().getLabel();
}

std::string Process::stateToString( ProcessState state )
{
	switch( state )
	{
	case CREATED: return "CREATED";
	case RUNNABLE: return "RUNNABLE";
	case CURRENT: return "CURRENT";
	case IDLE: return "IDLE";
	case TERMINATED: return "TERMINATED";
	}
	// this will never happen
	return "THISCANTHAPPEN";
}

} } // namespace odemx::base
