//------------------------------------------------------------------------------
//	Copyright (C) 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 Event.cpp
 * @author Ronald Kluth
 * @date created at 2007/04/04
 * @brief Implementation of odemx::base::Event
 * @sa Event.h
 * @since 2.0
 */

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

#include <string>
#include <cassert>

namespace odemx {
namespace base {

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

Event::Event( Simulation& sim, const data::Label& label, EventObserver* obs )
:	Sched( sim, label, EVENT )
,	data::Observable< EventObserver >( obs )
,	executionTime_( static_cast< SimTime >( 0 ) )
,	priority_( 0.0 )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(Event) );

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

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

//----------------------------------------------------------------execution time

SimTime Event::getExecutionTime() const
{
	return executionTime_;
}

SimTime Event::setExecutionTime( SimTime newTime )
{
        assert(newTime >= getTime());
	SimTime oldTime = executionTime_;
	executionTime_ = newTime;

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

	// observer
	ODEMX_OBS_ATTR(EventObserver, ExecutionTime, oldTime, executionTime_);

	return oldTime;
}

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

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

	// observer
	ODEMX_OBS( EventObserver, Schedule( this ) );

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

void Event::scheduleIn( SimTime delay )
{
	assert( ! (delay < 0) );

	ODEMX_TRACE << log( "schedule in" )
			.detail( "current", getPartner() )
			.detail( "relative time", delay )
			.scope( typeid(Event) );

	// observer
	ODEMX_OBS( EventObserver, ScheduleIn( this, delay ) );

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

void Event::scheduleAt( SimTime absoluteTime )
{
	assert( ! (absoluteTime < getTime()) );

	ODEMX_TRACE << log( "schedule at" )
			.detail( "current", getPartner() )
			.detail( "absolute time", absoluteTime )
			.scope( typeid(Event) );

	// observer
	ODEMX_OBS( EventObserver, ScheduleAt( this,  absoluteTime ) );

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

void Event::scheduleAppend()
{
	ODEMX_TRACE << log( "schedule append" )
			.detail( "partner", getPartner() )
			.scope( typeid(Event) );

	// observer
	ODEMX_OBS( EventObserver, ScheduleAppend( this ) );

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

void Event::scheduleAppendIn( SimTime delay )
{
	assert( ! (delay < 0) );

	ODEMX_TRACE << log( "schedule append in" )
			.detail( "current", getPartner() )
			.detail( "relative time", delay )
			.scope( typeid(Event) );

	// observer
	ODEMX_OBS( EventObserver, ScheduleAppendIn( this, delay ) );

	// scheduling
	setExecutionTime( getTime() + delay );
	getSimulation().getScheduler().addSched( this );
}

void Event::scheduleAppendAt( SimTime absoluteTime )
{
	assert( ! (absoluteTime < getTime()) );

	ODEMX_TRACE << log( "schedule append at" )
			.detail( "current", getPartner() )
			.detail( "absolute time", absoluteTime )
			.scope( typeid(Event) );

	// observer
	ODEMX_OBS( EventObserver, ScheduleAppendAt( this,  absoluteTime ) );

	// scheduling
	setExecutionTime( absoluteTime );
	getSimulation().getScheduler().addSched( this );
}

void Event::removeFromSchedule()
{
	if( isScheduled() )
	{
		getSimulation().getScheduler().removeSched( this );

		ODEMX_TRACE << log( "remove from schedule" )
				.detail( "partner", getPartner() )
				.scope( typeid(Event) );

		// observer
		ODEMX_OBS( EventObserver, RemoveFromSchedule( this ) );
	}
	else
	{
		error << log( "Event::removeFromSchedule(): event is not scheduled" )
				.detail( "partner", getPartner() )
				.scope( typeid(Event) );
	}
}

//---------------------------------------------------------------------execution

void Event::execute()
{
	if( ! isScheduled() )
	{
		// error: Event has to be scheduled to be executed
		fatal << log( "Event::executeEvent(): event is not scheduled" )
				.scope( typeid(Event) );

		throw SchedulingException( "Event not scheduled" );
	}

	// remove Event from ExecutionList
	getSimulation().getScheduler().removeSched( this );

	ODEMX_TRACE << log( "execute event" ).scope( typeid(Event) );

	// observer
	ODEMX_OBS( EventObserver, ExecuteEvent( this ) );

	// current must be set here, the old value is needed for the trace above
	getSimulation().setCurrentSched( this );

	// execute the action represented by this Event
	eventAction();
}

//----------------------------------------------------------------------priority

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

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

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

	if( isScheduled() )
	{
		// setPriority() influences ExecutionList ordering
		getSimulation().getScheduler().inSort( this );
	}

	return oldPriority;
}

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

//-----------------------------------------------------------------------helpers

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

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

	return getSimulation().getLabel();
}

} } // namespace odemx::base
