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

#include <odemx/base/Event.h>
#include <odemx/base/Process.h>
#include <odemx/base/Simulation.h>
#include <odemx/util/Exceptions.h>

#include <Poco/UUIDGenerator.h>

#include <iostream>
#include <fstream>

namespace odemx {
namespace base {

//------------------------------------------------------construction/destruction

Simulation::Simulation( const data::Label& label, SimulationObserver* obs )
:	data::LoggingManager()
,	data::Producer( *this, label )
,	coroutine::CoroutineContext( obs )
,	data::Observable< SimulationObserver >( obs )
,	description_( "no description" )
,	isInitialized_( false )
,	isStopped_( false )
,	startTime_( 0 )
,	currentTime_( 0 )
,	scheduler_( *this, getLabel() + " scheduler" )
,	currentProcess_( 0 )
,	currentSched_( 0 )
,	createdProcesses_()
,	runnableProcesses_()
,	idleProcesses_()
,	terminatedProcesses_()
,	autoDelete_( false )
,	uniqueId_( Poco::UUIDGenerator::defaultGenerator().createRandom() )
{
	// since sim owns the channels, there are no consumers for trace records yet
	// ODEMX_TRACE << log( "create" ).scope( typeid(Simulation) );

	ODEMX_OBS(SimulationObserver, Create(this));
}

Simulation::Simulation( const data::Label& label, SimTime startTime, SimulationObserver* obs )
:	data::LoggingManager()
,	data::Producer( *this, label )
,	coroutine::CoroutineContext( obs )
,	data::Observable< SimulationObserver >( obs )
,	description_( "no description" )
,	isInitialized_( false )
,	isStopped_( false )
,	startTime_( startTime )
,	currentTime_( startTime )
,	scheduler_( *this, getLabel() + " scheduler" )
,	currentProcess_( 0 )
,	currentSched_( 0 )
,	createdProcesses_()
,	runnableProcesses_()
,	idleProcesses_()
,	terminatedProcesses_()
,	autoDelete_( false )
,	uniqueId_( Poco::UUIDGenerator::defaultGenerator().createRandom() )
{
	ODEMX_TRACE << log( "create with start time" ).detail( "time", startTime_ )
			.scope( typeid(Simulation) );

	ODEMX_OBS(SimulationObserver, Create(this));
}

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

//----------------------------------------------------------------description/ID

SimulationId Simulation::getId() const
{
	return uniqueId_;
}

void Simulation::setDescription( const std::string& description )
{
	description_ = description;
}

const std::string& Simulation::getDescription() const
{
	return description_;
}

//----------------------------------------------------------------initialization

void Simulation::init()
{
	if( !isInitialized_ )
	{
		ODEMX_TRACE << log( "init" ).scope( typeid(Simulation) );

		// observer
		ODEMX_OBS(SimulationObserver, Initialization(this));

		initSimulation();
		isInitialized_ = true;
	}
}

//----------------------------------------------------------------simulation run

void Simulation::run()
{
	ODEMX_TRACE << log( "run" ).scope( typeid(Simulation) );

	// observer
	ODEMX_OBS(SimulationObserver, Run(this));

	init();
	scheduler_.run();
}

void Simulation::step()
{
	ODEMX_TRACE << log( "step" ).scope( typeid(Simulation) );

	// observer
	ODEMX_OBS(SimulationObserver, Step(this));

	init();
	scheduler_.step();
}

void Simulation::runUntil( SimTime stopTime )
{
	assert( stopTime >= currentTime_ );

	ODEMX_TRACE << log( "run until" )
			.detail( "stop time", stopTime )
			.scope( typeid(Simulation) );

	// observer
	ODEMX_OBS(SimulationObserver, RunUntil(this, stopTime));

	init();
	scheduler_.runUntil( stopTime );
}

//----------------------------------------------------------------simulation end

void Simulation::exitSimulation()
{
	ODEMX_TRACE << log( "exit" ).scope( typeid(Simulation) );

	// observer
	ODEMX_OBS(SimulationObserver, ExitSimulation(this));

	// setting isInitialized may not be formally correct, but it will
	// effect that isFinished() returns true, which makes sense
	// after calling exit on a simulation
	isInitialized_ = true;
	isStopped_ = true;

	// if this function is called from an active coroutine,
	// we need to restore the environment
	switchTo();
}

bool Simulation::isFinished() const
{
	return isInitialized_
			&& ( isStopped_ || scheduler_.getExecutionList().isEmpty() );
}

//---------------------------------------------------------------simulation time

SimTime Simulation::getStartTime() const
{
	return startTime_;
}

void Simulation::setCurrentTime( SimTime newTime )
{
	ODEMX_TRACE << log( "time" )
			.valueChange( currentTime_, newTime )
			.scope( typeid(Simulation) );

	// observer
	ODEMX_OBS_ATTR( SimulationObserver, Time, currentTime_, newTime );

	currentTime_ = newTime;
}

SimTime Simulation::getTime() const
{
	return currentTime_;
}

//--------------------------------------------------------------------scheduling

void Simulation::setCurrentSched( Sched* s )
{
	assert( s );

	ODEMX_TRACE << log( "set current" )
			.detail( "partner", s->getLabel() )
			.scope( typeid(Simulation) );

	// observer
	ODEMX_OBS_ATTR(SimulationObserver, CurrentSched, currentSched_, s);

	currentSched_ = s;
}

void Simulation::setCurrentProcess( Process* p )
{
	assert( p );

	// erase process from previous state list
	removeProcessFromList( p );

	currentProcess_ = p;
	currentSched_ = p;
}

Scheduler& Simulation::getScheduler()
{
	return scheduler_;
}

Sched* Simulation::getCurrentSched()
{
	return currentSched_;
}

Process* Simulation::getCurrentProcess()
{
	return currentProcess_;
}

void Simulation::resetCurrentSched()
{
	currentSched_ = 0;
}

void Simulation::resetCurrentProcess()
{
	currentProcess_ = 0;
}

//------------------------------------------------------------process management

ProcessList& Simulation::getCreatedProcesses()
{
	return createdProcesses_;
}

const ProcessList& Simulation::getCreatedProcesses() const
{
	return createdProcesses_;
}

ProcessList& Simulation::getRunnableProcesses()
{
	return runnableProcesses_;
}

const ProcessList& Simulation::getRunnableProcesses() const
{
	return runnableProcesses_;
}

ProcessList& Simulation::getIdleProcesses()
{
	return idleProcesses_;
}

const ProcessList& Simulation::getIdleProcesses() const
{
	return idleProcesses_;
}

ProcessList& Simulation::getTerminatedProcesses()
{
	return terminatedProcesses_;
}

const ProcessList& Simulation::getTerminatedProcesses() const
{
	return terminatedProcesses_;
}

void Simulation::setProcessAsCreated( Process* p )
{
	assert( p );

	ODEMX_OBS(SimulationObserver, ChangeProcessList(this, p, CREATED));

	// insert p
	p->simListIter_ = createdProcesses_.insert( createdProcesses_.end(), p );
}

void Simulation::setProcessAsRunnable( Process* p )
{
	assert( p );

	// erase process from previous state list
	removeProcessFromList( p );

	ODEMX_OBS(SimulationObserver, ChangeProcessList(this, p, RUNNABLE));

	// insert p
	p->simListIter_ = runnableProcesses_.insert( runnableProcesses_.end(), p );
}

void Simulation::setProcessAsIdle( Process* p )
{
	assert( p );

	// erase process from previous state list
	removeProcessFromList( p );

	ODEMX_OBS(SimulationObserver, ChangeProcessList(this, p, IDLE));

	// insert p
	p->simListIter_ = idleProcesses_.insert( idleProcesses_.end(), p );
}

void Simulation::setProcessAsTerminated( Process* p )
{
	assert( p );

	// erase process from previous state list
	removeProcessFromList( p );

	ODEMX_OBS(SimulationObserver, ChangeProcessList(this, p, TERMINATED));

	// insert p
	p->simListIter_ = terminatedProcesses_.insert( terminatedProcesses_.end(), p );
}

void Simulation::removeProcessFromList( Process* p )
{
	assert( p );

	switch ( p->getProcessState() )
	{
	case Process::CREATED:
		createdProcesses_.erase( p->simListIter_ );
		break;
	case Process::RUNNABLE:
		runnableProcesses_.erase( p->simListIter_ );
		break;
	case Process::IDLE:
		idleProcesses_.erase( p->simListIter_ );
		break;
	case Process::TERMINATED:
		// usually should not happen, no reactivation after termination
		// however, this function may be used to remove processes from lists
		// before their destruction, so no warning or error
		terminatedProcesses_.erase( p->simListIter_ );
		break;
	default: // Process::CURRENT, no list needed
		break;
	}
}

//---------------------------------------------automatic process object deletion

void Simulation::autoDeleteProcesses( bool value )
{
	autoDelete_ = value;
}

void Simulation::checkAutoDelete()
{
	if( autoDelete_ )
	{
		ProcessList::iterator iter = terminatedProcesses_.begin();
		while( iter != terminatedProcesses_.end()  )
		{
			delete *iter++;
		}
		terminatedProcesses_.clear();
	}
}

} } // namespace odemx::base
