//----------------------------------------------------------------------------
//	Copyright (C) 2002, 2004, 2007 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 continuous/Continuous.cpp

 \author Michael Fiedler

 \date created at 2009/01/02

 \brief Implementation of class continuous

 \sa Continuous.h

 \since 3.0
 */

#include <odemx/base/continuous/Continuous.h>
#include <odemx/base/continuous/Monitor.h>
#include <iomanip>

using namespace odemx;
using namespace odemx::base;
using namespace odemx::base::continuous;
using namespace std;

//----------------------------------------------------------------construction/destruction
Continuous::Continuous(Simulation& sim, const data::Label& label, int dimension)
:	Producer( sim, label )
,	monitor(0)
,	dimension(dimension)
,	baseIndex(0)
{}

Continuous::Continuous(Simulation& sim, const data::Label& label, Monitor* monitor, int dimension, ODEObject* equation)
:	Producer( sim, label )
,	monitor(0)
,	dimension(dimension)
,	baseIndex(0)
{
	setMonitor(monitor);
	if(equation != 0) addODE(equation);
}

Continuous::Continuous(Simulation& sim, const data::Label& label, Monitor* monitor, int dimension, std::list<ODEObject*> equationList)
:	Producer( sim, label ),
	monitor(0),
	dimension(dimension)
,	baseIndex(0)
{
	setMonitor(monitor);
	addODEList(equationList);
}

Continuous::~Continuous() {
	/*nothing to do*/
}

//--------------------------------------------------------------handle responsible Monitor
void Continuous::setMonitor(Monitor *monitor) {
	if (this->monitor != 0) { //remove from old
		this->monitor->removeContinuous(this); //all actions are implemented in Monitor
	}

	if (monitor != 0) {	//add to new
		monitor->addContinuous(this); //all actions are implemented in Monitor
	}
}

Monitor* Continuous::getMonitor() {
	return monitor;
}

//---------------------------------------------------------------handle active equations
void Continuous::addODEList(std::list<ODEObject*> equationList) {
	std::list<ODEObject*>::iterator iter;
	for (iter = equationList.begin(); iter != equationList.end(); iter++)
		addODE(*iter);
}

void Continuous::addODE(ODEObject *equation) {
	if (equation != 0) { //only add real objects
		if (equation->continuous != 0) { //delete from old Continuous
			equation->continuous->removeODE(equation);
		}
		//add to this Continuous
		equation->continuous = this;
		this->equations.push_back(equation);
		//tell monitor that system changed
		this->setChanged();
	}
}

void Continuous::removeODE(ODEObject *equation) {
	//go to position..
	std::list<ODEObject*>::iterator iter = find(this->equations.begin(),
												this->equations.end(), equation);
	//..and delete
	if (*iter == equation) {
		this->equations.erase(iter);
	}
	equation->continuous = 0;
	//tell monitor that system changed
	this->setChanged();
}

//--------------------------------------------------------------------------------stuff
int Continuous::getDimension() {
	return dimension;
}

void Continuous::setChanged() {
	if (monitor) monitor->setChanged();
}

//-----------------------------------------------------handle values between integration
void Continuous::setValue(int i, double value) {
	if (monitor != 0) {
		monitor->variables->setVariable(this->baseIndex + i, value);
		//tell monitor that system changed since init value changed
		this->setChanged();
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

double Continuous::getValue(int i) {
	if (monitor != 0) {
		return monitor->variables->getVariable(this->baseIndex + i);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

int Continuous::getBaseIndex() {
	return baseIndex;
}

//---------------------------------------------------------handle values in integration
double Continuous::getValueForDerivative(int i) {
	if (monitor != 0) {
		return monitor->variables->getValue(this->baseIndex + i);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

void Continuous::setDerivative(int i, double value) {
	if (monitor != 0) {
		monitor->variables->addToDerivative(this->baseIndex + i, value);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

double Continuous::getDerivative(int i) {
	if (monitor != 0) {
		return monitor->variables->getDerivative(this->baseIndex + i);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

void Continuous::setJacobi(int i, int j, double value) {
	if (monitor != 0) {
		monitor->variables->addToJacobi(this->baseIndex + i, this->baseIndex + j, value);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

double Continuous::getJacobi(int i, int j) {
	if (monitor != 0) {
		return monitor->variables->getJacobi(this->baseIndex + i, this->baseIndex + j);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

void Continuous::setJacobi(int i, Continuous* continuous, int j, double value) {
	if (monitor != 0) {
		monitor->variables->addToJacobi(this->baseIndex + i, continuous->baseIndex + j, value);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

double Continuous::getJacobi(int i, Continuous* continuous, int j) {
	if (monitor != 0) {
		return monitor->variables->getJacobi(this->baseIndex + i, continuous->baseIndex + j);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

void Continuous::setDfDt(int i, double value) {
	if (monitor != 0) {
		monitor->variables->addToDfDt(this->baseIndex + i, value);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

double Continuous::getDfDt(int i) {
	if (monitor != 0) {
		return monitor->variables->getDfDt(this->baseIndex + i);
	} else {
		//throw NotAssignedException for missing Monitor
		throw new NotAssignedException("Monitor", "Continuous");
	}
}

void Continuous::notifyValidState() {
	cout << "Continuous::notifyValidState()" << endl;
	//observers
	ODEMX_OBS(ContinuousObserver, NewValidState(this));
}

ContinuousTrace::ContinuousTrace(Continuous* contu, const string & fileName)
: out(0),
  firstTime(true),
  fileName(fileName),
  continuous(contu)
{
#ifdef ODEMX_USE_OBSERVATION
	if( contu != 0 )
	{
		contu->data::Observable< ContinuousObserver >::addObserver( this );
	}
#endif

	if( ! fileName.empty() )
	{
		openFile( fileName );
	}
	else if( contu != 0 )
	{
		std::string fn = contu->getLabel();
		fn += "_trace.txt";
		openFile( fn );
	}
}

ContinuousTrace::~ContinuousTrace()
{
	if( out == 0 || out == &std::cout )
	{
		return;
	}

	static_cast< std::ofstream* >( out )->close();
	delete out;
}

void ContinuousTrace::onNewValidState( Continuous* sender )
{
	cout << "ContinuousTrace::onNewValidState( " << sender->getLabel() << " )" << endl;
	using namespace std;

	assert( sender != 0 );

	if( firstTime )
	{
		if( out == 0 )
		{
			std::string fn = fileName;
			fn += "_trace.txt";
			openFile( fn );
		}

		*out << endl;
		*out << setw(60) << "T R A C E   F I L E   F O R  " << sender->getLabel() << " managed by monitor " << sender->getMonitor()->getLabel() << endl << endl;
		*out << setw(13) << "Time";
		*out << setw(13) << "Steplength";
		*out << setw(13) << "Error";

		for( int i = 0; i < continuous->getDimension(); ++i ) {
			*out << setw(15) << "state[" << i << "]";
		}

		for( int i = 0; i < continuous->getDimension(); ++i ) {
			*out << setw(10) << "rate[" << i << "]";
		}

		*out << endl;

		firstTime=false;
	}

	( *out ).setf( std::ios::showpoint );
	( *out ).setf( std::ios::fixed );
	*out << setw( 13 ) << setprecision( 10 ) << sender->getMonitor()->getTime();
	*out << setw( 13 ) << setprecision( 10 ) << sender->getMonitor()->getODESolver()->getStepLength();
	*out << setw( 13 ) << setprecision( 10 ) << sender->getMonitor()->getODESolver()->getErrorLimit();

	//State values
	for( int i = 0; i < sender->getDimension(); ++i ) {
		*out << setw( 18 ) << setprecision( 10 ) << sender->getValue(i);
	}

	//rate values
	for( int i = 0; i < sender->getDimension(); ++i ) {
		*out << setw( 18 ) << setprecision( 10 ) << sender->getDerivative(i);
	}

	*out << endl;
}

void ContinuousTrace::onSetMonitor( Continuous* sender, Monitor* monitor ) {
	cout << "The continuous object " << continuous->getLabel() << " added to the monitor " << sender->getLabel() << endl;
}

void ContinuousTrace::openFile( const std::string& fileName )
{
	out = new std::ofstream( fileName.c_str() );
	if( out == 0 || !( *out ) )
	{
		// Error: cannot open file
		std::cerr << "MonitorTrace::openFile(): cannot open file; sending output to stdout." << std::endl;
		out = &std::cout;
	}
}
