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

#include <odemx/base/ExecutionList.h>
#include <odemx/base/Comparators.h>
#include <odemx/base/Event.h>
#include <odemx/base/Process.h>
#include <odemx/base/Simulation.h>
#include <odemx/util/ListInSort.h>

#include <algorithm>
#include <cassert>
#include <iostream>
#include <iomanip>
#include <sstream>

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

namespace odemx {
namespace base {

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

ExecutionList::ExecutionList( Simulation& sim, const data::Label& label )
:	data::Producer( sim, label )
{
	ODEMX_TRACE << log( "create" ).scope( typeid(ExecutionList) );
}

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

//---------------------------------------------------------------sched insertion

void ExecutionList::addSched( Sched* s )
{
	assert( s );

	ODEMX_TRACE << log( "add" )
			.detail( "partner", s->getLabel() )
			.scope( typeid(ExecutionList) );

	// adjust execution time if necessary
	if ( s->getExecutionTime() < getTime() )
	{
		s->setExecutionTime( getTime() );
	}

	// append Sched s at its ExecutionTime
	// to ExecutionList (FIFO), considering priority
	inSort( s );
}

void ExecutionList::insertSched( Sched* s )
{
	assert( s );

	ODEMX_TRACE << log( "insert" )
			.detail( "partner", s->getLabel() )
			.scope( typeid(ExecutionList) );

	// adjust execution time
	if ( s->getExecutionTime() < getTime() )
	{
		s->setExecutionTime( getTime() );
	}

	// insert Sched s at ExecutionTime in
	// ExecutionList (LIFO), considering priority
	inSort( s, false );
}

void ExecutionList::insertSchedAfter( Sched* s, Sched* partner )
{
	assert( s && partner );

	if( s == partner )
	{
		error << log( "ExecutionList::insertSchedAfter(): "
				"sched and partner are the same" )
				.scope( typeid(ExecutionList) );
		return;
	}

	if( ! partner->isScheduled() )
	{
		error << log( "ExecutionList::insertSchedAfter(): "
				"partner not scheduled" )
				.scope( typeid(ExecutionList) );
		return;
	}

	// remove Sched s from ExecutionList if scheduled
	if( s->isScheduled() )
	{
		removeSched( s );
	}

	Priority prevPrio = partner->getPriority();

	// adjust priority so the list order remains correct for s and partner
	if ( s->getPriority() > prevPrio )
	{
		s->setPriority( prevPrio );
	}

	// set s to execution time of partner
	s->setExecutionTime( partner->getExecutionTime() );

	// insert Sched s after partner
	SchedList::iterator pos = partner->execListIter_;
	++pos;
	s->execListIter_ = partner->scheduleLoc_.second->insert( pos, s );
	s->scheduleLoc_ = partner->scheduleLoc_;
	s->scheduled_ = true;

	ODEMX_TRACE << log( "insert after" )
			.detail( "partner", s->getLabel() )
			.detail( "after", partner->getLabel() )
			.scope( typeid(ExecutionList) );
}

void ExecutionList::insertSchedBefore( Sched* s, Sched* partner )
{
	assert( s && partner );

	if( s == partner )
	{
		error << log( "ExecutionList::insertSchedBefore(): "
				"sched and partner are the same" )
				.scope( typeid(ExecutionList) );
		return;
	}

	if( ! partner->isScheduled() )
	{
		error << log( "ExecutionList::insertSchedBefore(): "
				"partner not scheduled" )
				.scope( typeid(ExecutionList) );
		return;
	}

	// remove Sched s from ExecutionList if scheduled
	if( s->isScheduled() )
	{
		removeSched( s );
	}

	Priority partnerPrio = partner->getPriority();

	// adjust priority to maintain list order
	if ( s->getPriority() < partnerPrio )
	{
		s->setPriority( partnerPrio );
	}

	// set s execution time to execution time of partner
	s->setExecutionTime( partner->getExecutionTime() );

	// insert Sched s before partner
	assert( partner->scheduleLoc_.second );
	s->execListIter_ = partner->scheduleLoc_.second->insert( partner->execListIter_, s );
	s->scheduleLoc_ = partner->scheduleLoc_;
	s->scheduled_ = true;

	ODEMX_TRACE << log( "insert before" )
			.detail( "partner", s->getLabel() )
			.detail( "before", partner->getLabel() )
			.scope( typeid(ExecutionList) );
}

void ExecutionList::inSort( Sched* s, bool fifo )
{
	assert( s );

	// remove the object from exec list if it is already scheduled
	if ( s->isScheduled() )
	{
		removeSched( s );
	}

	// insert s into ordered list and store the location as iterator
	s->scheduleLoc_ = std::make_pair( s->getExecutionTime(),
			&( schedule_[ s->getExecutionTime() ] ) );

	s->execListIter_ = ListInSort< Sched*, DefaultCmp >(
						*s->scheduleLoc_.second, s, defCmp, fifo );

	s->scheduled_ = true;
}

//-----------------------------------------------------------------sched removal

void ExecutionList::removeSched( Sched* s )
{
	assert( s );

	if( ! s->isScheduled() )
	{
		error << log( "ExecutionList::removeSched(): "
				"attempt to remove unscheduled object" )
				.detail( "sched", s->getLabel() )
				.scope( typeid(ExecutionList) );
		return;
	}

	// remove s from its schedule list
	assert( s->scheduleLoc_.second );
	s->scheduleLoc_.second->erase( s->execListIter_ );

	// erase the map entry from the list if no other object is scheduled at the time
	if( s->scheduleLoc_.second->empty() )
	{
		schedule_.erase( s->scheduleLoc_.first );
	}

	// reset members
	s->scheduleLoc_ = std::make_pair( static_cast< SimTime >( 0 ),
			static_cast< SchedList* >( 0 ) );
	s->scheduled_ = false;

	ODEMX_TRACE << log( "remove" )
			.detail( "sched", s->getLabel() )
			.scope( typeid(ExecutionList) );
}

//-------------------------------------------------------------various accessors

Sched* ExecutionList::getNextSched() const
{
	if( schedule_.empty() )
	{
		return 0;
	}
	return schedule_.begin()->second.front();
}

SimTime ExecutionList::getNextExecutionTime() const
{
	if( schedule_.empty() )
	{
		return 0;
	}
	return schedule_.begin()->first;
}

bool ExecutionList::isEmpty() const
{
	return schedule_.empty();
}

SimTime ExecutionList::getTime()
{
	return getSimulation().getTime();
}

//-----------------------------------------------------------------------logging

void ExecutionList::logToChannel( const ChannelPtr channel ) const
{
	// create record
	data::SimRecord record = log( "scheduled objects" ).scope( typeid(ExecutionList) );
	record.detail( "time entries", schedule_.size() );

	// add record details ( Position, Time, Label )
	std::ostringstream tmpStream;
	size_t position = 0;

	// iterate over the map
	for( Schedule::const_iterator iter = schedule_.begin();
		iter != schedule_.end(); ++iter )
	{
		// iterate over each list
		for( SchedList::const_iterator listIter = iter->second.begin();
			listIter != iter->second.end(); ++listIter, ++position )
		{
			Sched* scheduled = *listIter;

			record.detail( "pos", position )
				.detail( "time", scheduled->getExecutionTime() )
				.detail( "obj", scheduled->getLabel() );
		}
	}

	// log the whole record to the given channel
	channel << record;
}

} } // namespace odemx::base
