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

#if defined(_MSC_VER) && defined(ODEMX_USE_SETJUMP)
#pragma warning(disable : 4530)
#endif

#include <odemx/coroutine/Coroutine.h>

#include <iostream>
#include <exception>
#include <memory.h> // memcpy
#include <stdexcept>

namespace odemx {
namespace coroutine {

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

Coroutine::Coroutine( CoroutineContext* c, CoroutineObserver* o )
:	data::Observable< CoroutineObserver >( o )
,	context( c )
,	parent( 0 )
,	caller( 0 )
,	state( CREATED )
{
	// check for context existence: if 0, set default
	if( ! context )
	{
		context = getDefaultContext();
	}
	// register coroutine with context
	getContext()->beginCoroutine( this );

	ODEMX_OBS(CoroutineObserver, Create(this));

	myFiber = fiber = 0;
}

Coroutine::~Coroutine()
{
	if( context->getActiveCoroutine() == this )
	{
		std::cerr << "ODEMx ERROR: Coroutine::~Coroutine():"
				  << "destruction of active coroutine"
				  << std::endl;
	}

	// clear Coroutine if necessary
	if( getState() != TERMINATED )
	{
		clear();
	}

	ODEMX_OBS(CoroutineObserver, Destroy(this));

  freeStack ();
}

//----------------------------------------------------------------------clean up


void Coroutine::freeStack() {
  if( fiber != 0 )
  {
    DeleteFiber( fiber );
    fiber = 0;
  }
}

void Coroutine::clear()
{
	if( state == TERMINATED )
	{
		return;
	}
	ODEMX_OBS(CoroutineObserver, Clear(this));

	setState( TERMINATED );

	// leave context
	getContext()->endCoroutine( this );
}

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

void Coroutine::initialize()
{
	if( state != CREATED )
	{
		throw std::runtime_error( "Coroutine::initialize(): internal error, "
				"coroutine is already initialized" );
	}

#ifdef _WIN32
	myFiber = fiber = CreateFiber( 0, (LPFIBER_START_ROUTINE)ExecFiber, this );
#else
	// TODO: fix this -> int cast with map index
	myFiber = fiber = CreateFiber( 0, (void(*)(void*))ExecFiber, this );
#endif

	if( fiber == 0 )
	{
		throw std::runtime_error( "Coroutine::initialize(): failed to create "
				"fiber, probably due to memory exhaustion" );
	}

	ODEMX_OBS(CoroutineObserver, Initialize(this));

	setState( RUNNABLE );
}

//-----------------------------------------------------------------------getters

CoroutineContext* Coroutine::getContext()
{
	return context;
}

Coroutine* Coroutine::getParent()
{
	return parent;
}

Coroutine* Coroutine::getCaller()
{
	return caller;
}

//-------------------------------------------------switch to coroutine execution

void Coroutine::switchTo()
{
	if( state == CREATED )
	{
		// store return point
		parent = context->getActiveCoroutine();
		initialize();
	}
	caller = context->getActiveCoroutine();

	if( caller != 0 )
	{
		ODEMX_OBS(CoroutineObserver, SwitchTo(this, caller))
	}
	else
	{
		ODEMX_OBS(CoroutineObserver, SwitchTo(this, context));
	}

	if( state != RUNNABLE )
	{
		std::cerr << "ODEMx WARNING: Coroutine::switchTo(): switch to coroutine "
				  << "failed: Coroutine is not RUNNABLE, continue in parent"
				  << std::endl;

		if( parent != 0 )
		{
			parent->switchTo();
		}
		else
		{
			getContext()->switchTo();
		}
	}

	if( context->getActiveCoroutine() == this )
	{
		return;
	}

	Coroutine* oldActive = context->setActiveCoroutine( this );

	if( oldActive != 0 )
	{
		// switchTo from Coroutine;
		// Coroutine has to save 'switch in' point.
		oldActive->saveFiber();
	}
	else
	{
		// switchTo from context;
		// CoroutineContext has to save 'switch out' point.
		context->saveFiber();
	}

	FiberSwitch( fiber );
}

void Coroutine::operator()()
{
	switchTo();
}

void Coroutine::yield()
{
	if( parent != 0 )
	{
		parent->switchTo();
	}
	else
	{
		context->switchTo();
	}
}


//-----------------------------------------------------------------fiber storage

void Coroutine::saveFiber()
{
	// get active fiber
	fiber = GetCurrentFiber();

	if( fiber == 0 )
	{
		throw std::runtime_error(
				"Coroutine::saveFiber(): could not retrieve active fiber" );
	}
}

//---------------------------------------------------------------fiber execution

VOID CALLBACK ExecFiber( PVOID param )
{

#ifdef _WIN32
	Coroutine *p = static_cast<Coroutine*>( param );
#else
	void* theCoroutine = ucFiber::getCoroutine(param);
	Coroutine* p = static_cast<Coroutine*>(theCoroutine);
#endif

	try
	{
		// start execution
		p->run();
	}
	catch( const std::exception& )
	{
		throw; // rethrow anything that is derived from std::exception
	}
	catch(...)
	{
		// throw a real exception for whatever arrives here
		throw std::runtime_error(
				"ExecFiber(): non-exception object thrown in "
				"coroutine: cannot say what()" );
	}

	// execution finished
	p->clear();

	// return
	if( p->parent != 0 )
	{
		p->parent->switchTo();
	}
	else
	{
		p->context->switchTo();
	}

	// This point should never be reached.
	throw std::runtime_error(
			"ExecFiber(): internal error, return from coroutine failed" );
}

/**
	\internal
	\note FiberSwitch is a 'Work around'.

	In Visual C++ 6.0, when running a debug session,
	SwitchToFiber() does not restore EIP-register (Instruction pointer). [Maybe correct]
	FiberSwitch() ensures, that all calls to SwitchToFiber()
	come from the same instruction point in code.
*/
void FiberSwitch( LPVOID fiber )
{
	SwitchToFiber( fiber );
}

//----------------------------------------------------------------state handling

Coroutine::State Coroutine::setState( Coroutine::State newState )
{
	Coroutine::State oldState = state;
	state = newState;

	ODEMX_OBS_ATTR(CoroutineObserver, State, oldState, newState);

	return oldState;
}

Coroutine::State Coroutine::getState() const
{
	return state;
}

} } // namespace odemx::coroutine
