/**********************************************************************
** This program is part of 'MOOSE', the
** Messaging Object Oriented Simulation Environment,
**           copyright (C) 2003-2004 Upinder S. Bhalla. and NCBS
** It is made available under the terms of the
** GNU Lesser General Public License version 2.1
** See the file COPYING.LIB for the full notice.
**********************************************************************/

#include <fstream>
#include "header.h"
#include "../shell/Shell.h"

#define DO_CSPACE_DEBUG 0

#include "ReadCspace.h"

const double ReadCspace::SCALE = 1.0;
const double ReadCspace::DEFAULT_CONC = 1.0;
const double ReadCspace::DEFAULT_RATE = 0.1;
const double ReadCspace::DEFAULT_KM = 1.0;
const bool ReadCspace::USE_PIPE = 1;

ReadCspace::ReadCspace()
	:
		base_(),
		compt_(),
		mesh_(),
		fout_( &cout )
{;}

void ReadCspace::printHeader()
{
	reaclist_.resize( 0 );
	mollist_.resize( 0 );
}

void ReadCspace::printFooter()
{
	string separator = ( USE_PIPE ) ? "|" : "" ;
	// We do this in all cases, regardless of the doOrdering flag.
	sort( mollist_.begin(), mollist_.end() );
	sort( reaclist_.begin(), reaclist_.end() );
	unsigned int i;
	*fout_ << separator;
	for ( i = 0; i < reaclist_.size(); i++ )
		*fout_ << reaclist_[ i ].name() << separator;

	for ( i = 0; i < mollist_.size(); i++ )
		*fout_ << " " << mollist_[i].conc();
	for ( i = 0; i < reaclist_.size(); i++ )
		*fout_ << " " << reaclist_[i].r1() << " " << reaclist_[i].r2();
	*fout_ << "\n";
}

void ReadCspace::printMol( Id id, double conc, double concinit, double vol)
{

	// Skip explicit enzyme complexes.
	ObjId parent = Neutral::parent( id.eref() );
	if ( parent.element()->cinfo()->isA( "Enzyme" ) &&
		id.element()->getName() == ( parent.element()->getName() + "_cplx" )
	)
		return;

	CspaceMolInfo m( id.element()->getName()[ 0 ], concinit );
	mollist_.push_back( m );
	// Need to look up concs in a final step so that the sorted order
	// is maintained.
}

void ReadCspace::printReac( Id id, double kf, double kb)
{
	CspaceReacInfo r( id.element()->getName(), kf, kb );
	reaclist_.push_back( r );
}

void ReadCspace::printEnz( Id id, Id cplx, double k1, double k2, double k3)
{
	CspaceReacInfo r( id.element()->getName(), k3, (k3 + k2) / k1 );
	reaclist_.push_back( r );
}

// Model string always includes topology, after that the parameters
// are filled in according to how many there are.
Id ReadCspace::readModelString( const string& model,
	const string& modelname, Id pa, const string& solverClass )
{
	// Defined in ReadKkit.cpp
	extern Id makeStandardElements( Id pa, const string& modelname );
	unsigned long pos = model.find_first_of( "|" );
	if ( pos == string::npos ) {
		cerr << "ReadCspace::readModelString: Error: model undefined in\n";
		cerr << model << "\n";
		return Id();
	}
	mol_.resize( 0 );
	molseq_.resize( 0 );
	reac_.resize( 0 );
	molparms_.resize( 0 );
	parms_.resize( 0 );
	// Shell* s = reinterpret_cast< Shell* >( Id().eref().data() );
	base_ = makeStandardElements( pa, modelname );
	assert( base_ != Id() );
	string modelpath = base_.path();
	compt_ = Id( modelpath + "/kinetics");
	assert( compt_ != Id() );
	Field< double >::set( compt_, "volume", 1e-18 );
	// SetGet2< double, unsigned int >::set( compt_, "buildDefaultMesh",     1e-18, 1 );
	string temp = model.substr( pos + 1 );
	pos = temp.find_first_of( " 	\n" );

	for (unsigned long i = 0 ; i < temp.length() && i < pos; i += 5 ) {
		build( temp.c_str() + i );
		if ( temp[ i + 4 ] != '|' )
			break;
	}

	parms_.insert( parms_.begin(), molparms_.begin(), molparms_.end() );

	pos = model.find_last_of( "|" ) + 1;
	double val = 0;
	unsigned int i = 0;
	while ( pos < model.length() ) {
		if ( model[ pos ] == ' ' ) {
			val = atof( model.c_str() + pos + 1 );
			assert( i < parms_.size() );
			parms_[ i++ ] = val;
		}
		pos++;
	}

	deployParameters();

	// SetGet1< string >::set( base_, "build", solverClass );
	return base_;
}

void ReadCspace::makePlots( double plotdt )
{
	Shell* shell = reinterpret_cast< Shell* >( Id().eref().data() );
	vector< Id > children;
	Neutral::children( compt_.eref(), children );
	string basepath = base_.path();
	Id graphs( basepath + "/graphs" );
	assert( graphs != Id () );
	for ( unsigned int i = 0; i < children.size(); ++i ) {
		const Cinfo* kidCinfo = children[i].element()->cinfo();
		if ( kidCinfo->isA( "PoolBase" ) ) {
			string plotname = "plot" + children[i].element()->getName();
    		Id tab = shell->doCreate( "Table2", graphs, plotname, 1 );
			assert( tab != Id() );
			// cout << "ReadCspace made plot " << plotname << endl;
			ObjId mid = shell->doAddMsg( "Single",
				tab, "requestOut", children[i], "getConc" );
			assert( mid != ObjId() );
		}
	}
	/* Clocks are now assigned automatically
    shell->doSetClock( 8, plotdt );

    string plotpath = basepath + "/graphs/##[TYPE=Table2]";
    shell->doUseClock( plotpath, "process", 8 );
	*/
}


/////////////////////////////////////////////////////////////////////
//	From reacdef.cpp in CSPACE:
//             if (line == "A <==> B") type = 'A';
//        else if (line == "2A <==> B") type = 'B';
//        else if (line == "A --A--> B") type = 'C';
//        else if (line == "A --B--> B") type = 'D';
//        else if (line == "A <==> B + C") type = 'E';
//        else if (line == "2A <==> B + C") type = 'F';
//        else if (line == "2A + B <==> C") type = 'G';
//        else if (line == "2A + B <==> 2C") type = 'H';
//        else if (line == "4A + B <==> C") type = 'I';
//        else if (line == "A --B--> C") type = 'J';
//        else if (line == "A --A--> B + C") type = 'K';
//        else if (line == "A --B--> B + C") type = 'L';
/////////////////////////////////////////////////////////////////////

void ReadCspace::expandEnzyme(
	const char* name, int e, int s, int p, int p2 )
{
	static Shell* shell = reinterpret_cast< Shell* >( Id().eref().data() );

	Id enzMolId = mol_[ name[e] - 'a' ];

	Id enzId = shell->doCreate( "Enz", enzMolId, name, 1 );
	assert( enzId != Id() );
	string cplxName = name;
	cplxName += "_cplx";
	Id cplxId = shell->doCreate( "Pool", enzId, cplxName, 1 );
	assert( cplxId != Id() );
	ObjId ret = shell->doAddMsg( "OneToOne",
		enzId, "cplx", cplxId, "reac" );

	ret = shell->doAddMsg( "OneToOne",
		enzMolId, "reac", enzId, "enz" );

	ret = shell->doAddMsg( "OneToOne",
		mol_[ name[ s ] - 'a' ], "reac", enzId, "sub" );

	ret = shell->doAddMsg( "OneToOne",
		mol_[ name[ p ] - 'a' ], "reac", enzId, "prd" );

	if ( p2 != 0 )
		ret = shell->doAddMsg( "OneToOne",
			mol_[ name[ p2 ] - 'a' ], "reac", enzId, "prd" );

	assert( ret != ObjId() );

	reac_.push_back( enzId );
	parms_.push_back( DEFAULT_RATE );
	parms_.push_back( DEFAULT_KM );
}

void ReadCspace::expandReaction( const char* name, int nm1 )
{
	static Shell* s = reinterpret_cast< Shell* >( Id().eref().data() );

	if ( name[0] == 'C' || name[0] == 'D' || name[0] >= 'J' ) // enzymes
		return;
	int i;

	Id reacId = s->doCreate( "Reac", compt_, name, 1 );

	// A is always a substrate
	for (i = 0; i < nm1; i++ ) {
		s->doAddMsg( "OneToOne", reacId, "sub", mol_[ name[1] - 'a' ], "reac" );
	}

	if ( name[0] < 'G' ) { // B is a product
		s->doAddMsg( "OneToOne", reacId, "prd", mol_[ name[2] - 'a' ], "reac" );
	} else { // B is a substrate
		s->doAddMsg( "OneToOne", reacId, "sub", mol_[ name[2] - 'a' ], "reac" );
	}

	if ( name[0] > 'D' ) { // C is a product
		s->doAddMsg( "OneToOne", reacId, "prd", mol_[ name[3] - 'a' ], "reac" );
	}

	if ( name[0] == 'H' ) { // C is a dual product
		s->doAddMsg( "OneToOne", reacId, "prd", mol_[ name[3] - 'a' ], "reac" );
	}
	reac_.push_back( reacId );
	parms_.push_back( DEFAULT_RATE );
	parms_.push_back( DEFAULT_RATE );
}

void ReadCspace::build( const char* name )
{
	makeMolecule( name[1] );
	makeMolecule( name[2] );
	makeMolecule( name[3] );
	char tname[6];
	strncpy( tname, name, 4 );
	tname[4] = '\0';

	switch ( tname[0] ) {
		case 'A':
		case 'E':
			expandReaction( tname, 1 );
			break;
		case 'B':
		case 'F':
		case 'G':
		case 'H':
			expandReaction( tname, 2 );
			break;
		case 'I':
			expandReaction( tname, 4 );
			break;
		case 'C':
			expandEnzyme( tname, 1, 1, 2 );
			break;
		case 'D':
			expandEnzyme( tname, 2, 1, 2 );
			break;
		case 'J':
			expandEnzyme( tname, 2, 1, 3 );
			break;
		case 'K':
			expandEnzyme( tname, 1, 1, 2, 3 );
			break;
		case 'L':
			expandEnzyme( tname, 2, 1, 2, 3 );
			break;
		default:
			break;
	}
}

void ReadCspace::makeMolecule( char name )
{
	static Shell* s = reinterpret_cast< Shell* >( Id().eref().data() );

	if ( name == 'X' ) // silently ignore it, as it is a legal state
		return;
	if ( name < 'a' || name > 'z' ) {
		cerr << "ReadCspace::makeMolecule Error: name '" << name <<
			"' out of range 'a' to 'z'\n";
		return;
	}

	unsigned int index = 1 + name - 'a';

	// Put in molecule if it is a new one.
	if ( find( molseq_.begin(), molseq_.end(), index - 1 ) ==
					molseq_.end() )
			molseq_.push_back( index - 1 );

	for ( unsigned int i = mol_.size(); i < index; i++ ) {
		string molname("");
		molname += 'a' + i;
		/*
		stringstream ss( "a" );
		ss << i ;
		string molname = ss.str();
		*/
		Id temp = s->doCreate( "Pool", compt_, molname, 1 );
		mol_.push_back( temp );
		molparms_.push_back( DEFAULT_CONC );
	}
}

void ReadCspace::deployParameters( )
{
	// static Shell* shell = reinterpret_cast< Shell* >( Id().eref().data() );
	unsigned long i, j;
	if ( parms_.size() != mol_.size() + 2 * reac_.size() ) {
		cerr << "ReadCspace::deployParameters: Error: # of parms mismatch\n";
		return;
	}
	for ( i = 0; i < mol_.size(); i++ ) {
		// SetField(mol_[ i ], "volscale", volscale );
		// SetField(mol_[ molseq_[i] ], "ninit", parms_[ i ] );

		// Parameters are in micromolar, but the conc units are millimolar.
		Field< double >::set( mol_[i], "concInit", parms_[i] *  1e-3 );
	}
	for ( j = 0; j < reac_.size(); j++ ) {
		if ( reac_[ j ].element()->cinfo()->isA( "Reac" ) ) {
			Field< double >::set( reac_[j], "Kf", parms_[i++] );
			Field< double >::set( reac_[j], "Kb", parms_[i++] );
		} else {
			Field< double >::set( reac_[j], "k3", parms_[i] );
			Field< double >::set( reac_[j], "k2", 4.0 * parms_[i++] );
			// Again, note that conc units in MOOSE are millimolar, so we
			// need to convert from the CSPACE micromolar units.
			Field< double >::set( reac_[j], "Km", parms_[i++] * 1e-3 );
			vector< Id > cplx( 0 );
			Neutral::children( reac_[j].eref(), cplx );
			assert( cplx.size() == 1 );
		}
	}
}

void ReadCspace::testReadModel( )
{
	const double CONCSCALE = 1e-3;
	Shell* shell = reinterpret_cast< Shell* >( Id().eref().data() );
	// cout << "Testing ReadCspace::readModelString()\n";
	Id modelId = readModelString( "|Habc|Lbca|", "mod1", Id(), "Neutral" );
	assert( mol_.size() == 3 );
	assert( reac_.size() == 2 );

	shell->doDelete( modelId );

	modelId = readModelString( "|AabX|BbcX|CcdX|DdeX|Eefg|Ffgh|Gghi|Hhij|Iijk|Jjkl|Kklm|Llmn| 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 101 102 201 202 301 302 401 402 501 502 601 602 701 702 801 802 901 902 1001 1002 1101 1102 1201 1202",
		"model", Id(), "Neutral" );
	assert( mol_.size() == 14 );
	assert( reac_.size() == 12 );
	double concInit;
	int i;
	// cout << "\nTesting ReadCspace:: conc assignment\n";
	for ( i = 0; i < 14; i++ ) {
		string path( base_.path() + "/kinetics/" );
		path += 'a' + i;
		/*
		stringstream ss( "/kinetics/" );
		// const char* molname = ss.str();
		ss << 'a' + i;
		Id temp( ss.str() );
		*/
		Id temp( path );
		concInit = Field< double >::get( temp, "concInit" );

		// The 0.001 is for converting to millimolar, which is the internal
		// unit used in MOOSE.
		assert( doubleEq( concInit, 0.001 * ( i + 1 ) ) );
	}

	// cout << "\nTesting ReadCspace:: reac construction\n";
	assert( reac_[ 0 ].path() == "/model/kinetics/AabX" );
	assert( reac_[ 1 ].path() == "/model/kinetics/BbcX" );
	assert( reac_[ 2 ].path() == "/model/kinetics/c/CcdX" );
	assert( reac_[ 3 ].path() == "/model/kinetics/e/DdeX" );
	assert( reac_[ 4 ].path() == "/model/kinetics/Eefg" );
	assert( reac_[ 5 ].path() == "/model/kinetics/Ffgh" );
	assert( reac_[ 6 ].path() == "/model/kinetics/Gghi" );
	assert( reac_[ 7 ].path() == "/model/kinetics/Hhij" );
	assert( reac_[ 8 ].path() == "/model/kinetics/Iijk" );
	assert( reac_[ 9 ].path() == "/model/kinetics/k/Jjkl" );
	assert( reac_[ 10 ].path() == "/model/kinetics/k/Kklm" );
	assert( reac_[ 11 ].path() == "/model/kinetics/m/Llmn" );

	// cout << "\nTesting ReadCspace:: reac rate assignment\n";
	Id tempA( "/model/kinetics/AabX" );
	double r1 = Field< double >::get( tempA, "Kf");
	double r2 = Field< double >::get( tempA, "Kb");
	assert( doubleEq( r1, 101 ) && doubleEq( r2, 102 ) );

	Id tempB( "/model/kinetics/BbcX" );
	r1 = Field< double >::get( tempB, "Kf");
	r2 = Field< double >::get( tempB, "Kb");
	assert( doubleEq( r1, 201 ) && doubleEq( r2, 202 ) );

	Id tempC( "/model/kinetics/c/CcdX" );
	r1 = Field< double >::get( tempC, "k3");
	r2 = Field< double >::get( tempC, "Km");
	assert( doubleEq( r1, 301 ) && doubleEq( r2, 302 * CONCSCALE ) );

	Id tempD( "/model/kinetics/e/DdeX" );
	r1 = Field< double >::get( tempD, "k3");
	r2 = Field< double >::get( tempD, "Km");
	assert( doubleEq( r1, 401 ) && doubleEq( r2, 402 * CONCSCALE ) );

	for ( i = 4; i < 9; i++ ) {
		/*
		stringstream ss( "/kinetics/A" );
		ss << i << 'a' + i << 'b' + i << 'c' + i;
		// sprintf( ename, "/kinetics/%c%c%c%c", 'A' + i, 'a' + i, 'b' + i, 'c' + i );
		Id temp( ss.str() );
		*/

		string path( "/model/kinetics/" );
		path += 'A' + i;
		path += 'a' + i;
		path += 'b' + i;
		path += 'c' + i;
		Id temp( path );
		r1 = Field< double >::get( temp, "Kf");
		r2 = Field< double >::get( temp, "Kb");
		assert( doubleEq( r1, i* 100 + 101 ) &&
			doubleEq( r2, i * 100 + 102 ) );
	}

	Id tempJ( "/model/kinetics/k/Jjkl" );
	r1 = Field< double >::get( tempJ, "k3");
	r2 = Field< double >::get( tempJ, "Km");
	assert( doubleEq( r1, 1001 ) && doubleEq( r2, 1002 * CONCSCALE ) );

	Id tempK( "/model/kinetics/k/Kklm" );
	r1 = Field< double >::get( tempK, "k3");
	r2 = Field< double >::get( tempK, "Km");
	assert( doubleEq( r1, 1101 ) && doubleEq( r2, 1102 * CONCSCALE ) );

	Id tempL( "/model/kinetics/m/Llmn" );
	r1 = Field< double >::get( tempL, "k3");
	r2 = Field< double >::get( tempL, "Km");
	assert( doubleEq( r1, 1201 ) && doubleEq( r2, 1202 * CONCSCALE ) );
	shell->doDelete( modelId );
}