/*!\file TransientInput.c
 * \brief: implementation of the TransientInput object
 */
/*Headers{{{*/
#ifdef HAVE_CONFIG_H
	#include <config.h>
#else
#error "Cannot compile with HAVE_CONFIG_H symbol! run configure first!"
#endif

#include "../classes.h"
#include "../../shared/shared.h"
/*}}}*/

/*TransientInput constructors and destructor*/
/*FUNCTION TransientInput::TransientInput(){{{*/
TransientInput::TransientInput(){

	enum_type=UNDEF;
	inputs=NULL;
	this->numtimesteps=0;
	this->parameters=NULL;
	this->timesteps=NULL;

}
/*}}}*/
/*FUNCTION TransientInput::TransientInput(int in_enum_type){{{*/
TransientInput::TransientInput(int in_enum_type)
{
	/*Set Enum*/
	enum_type=in_enum_type;

	/*Allocate values and timesteps, and copy: */
	this->numtimesteps=0;
	this->timesteps=NULL;
	inputs = new Inputs();
	this->parameters=NULL;

}
/*}}}*/
/*FUNCTION TransientInput::~TransientInput{{{*/
TransientInput::~TransientInput(){
	xDelete(this->timesteps);
	this->timesteps=NULL;
	this->numtimesteps=0;
	parameters=NULL;
	delete this->inputs;
	return;
}
/*}}}*/

/*Object virtual functions definitions:*/
/*FUNCTION TransientInput::Echo {{{*/
void TransientInput::Echo(void){
	this->DeepEcho();
}
/*}}}*/
/*FUNCTION TransientInput::DeepEcho{{{*/
void TransientInput::DeepEcho(void){

	int i;

	_printf_("TransientInput:\n");
	_printf_("   enum: " << this->enum_type << " (" << EnumToStringx(this->enum_type) << ")\n");
	_printf_("   numtimesteps: " << this->numtimesteps << "\n");
	_printf_("---inputs: \n"); 
	for(i=0;i<this->numtimesteps;i++){
		_printf_("   time: " << this->timesteps[i] << "  \n");
		((Input*)this->inputs->GetObjectByOffset(i))->Echo();
	}
}
/*}}}*/
/*FUNCTION TransientInput::Id{{{*/
int    TransientInput::Id(void){ return -1; }
/*}}}*/
/*FUNCTION TransientInput::ObjectEnum{{{*/
int TransientInput::ObjectEnum(void){

	return TransientInputEnum;

}
/*}}}*/
/*FUNCTION TransientInput::copy{{{*/
Object* TransientInput::copy() {

	TransientInput* output=NULL;

	output = new TransientInput();
	output->enum_type=this->enum_type;
	output->numtimesteps=this->numtimesteps;
	output->timesteps=xNew<IssmDouble>(this->numtimesteps);
        xMemCpy(output->timesteps,this->timesteps,this->numtimesteps);
	output->inputs=(Inputs*)this->inputs->Copy();
	output->parameters=this->parameters;

	return output;

}
/*}}}*/

/*TransientInput management*/
/*FUNCTION TransientInput::InstanceEnum{{{*/
int TransientInput::InstanceEnum(void){

	return this->enum_type;

}
/*}}}*/
/*FUNCTION TransientInput::SpawnTriaInput{{{*/
Input* TransientInput::SpawnTriaInput(int location){

	/*output*/
	TransientInput* outinput=NULL;

	/*Create new Transientinput (copy of current input)*/
	outinput=new TransientInput();
	outinput->enum_type=this->enum_type;
	outinput->numtimesteps=this->numtimesteps;
	outinput->timesteps=xNew<IssmDouble>(this->numtimesteps);
	xMemCpy(outinput->timesteps,this->timesteps,this->numtimesteps);
	outinput->inputs=(Inputs*)this->inputs->SpawnTriaInputs(location);
	outinput->parameters=this->parameters;

	/*Assign output*/
	return outinput;

}
/*}}}*/
/*FUNCTION TransientInput::SpawnResult{{{*/
ElementResult* TransientInput::SpawnResult(int step, IssmDouble time){

	ElementResult* elementresult=NULL;

	/*Ok, we want to spawn an ElementResult. We have the time, just get 
	 *the correct values: */
	Input* input=GetTimeInput(time);

	elementresult=input->SpawnResult(step,time);

   delete input;

	return elementresult;
}
/*}}}*/

/*Object functions*/
/*FUNCTION TransientInput::GetInputValue(IssmDouble* pvalue,GaussTria* gauss){{{*/
void TransientInput::GetInputValue(IssmDouble* pvalue,GaussTria* gauss){
	IssmDouble time;

	/*First, recover current time from parameters: */
	this->parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetInputValue(pvalue,gauss);

	delete input;
}
/*}}}*/
/*FUNCTION TransientInput::GetInputValue(IssmDouble* pvalue,GaussPenta* gauss){{{*/
void TransientInput::GetInputValue(IssmDouble* pvalue,GaussPenta* gauss){
	IssmDouble time;

	/*First, recover current time from parameters: */
	this->parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetInputValue(pvalue,gauss);

	delete input;
}
/*}}}*/
/*FUNCTION TransientInput::GetInputValue(IssmDouble* pvalue,GaussTria* gauss,IssmDouble time){{{*/
void TransientInput::GetInputValue(IssmDouble* pvalue,GaussTria* gauss,IssmDouble time){

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetInputValue(pvalue,gauss);

	delete input;
}
/*}}}*/
/*FUNCTION TransientInput::GetInputValue(IssmDouble* pvalue,GaussPenta* gauss,IssmDouble time){{{*/
void TransientInput::GetInputValue(IssmDouble* pvalue,GaussPenta* gauss,IssmDouble time){

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetInputValue(pvalue,gauss);

	delete input;
}
/*}}}*/
/*FUNCTION TransientInput::GetInputDerivativeValue(IssmDouble* p, IssmDouble* xyz_list, GaussTria* gauss){{{*/
void TransientInput::GetInputDerivativeValue(IssmDouble* p, IssmDouble* xyz_list, GaussTria* gauss){

	IssmDouble time;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetInputDerivativeValue(p,xyz_list,gauss);

	delete input;

}
/*}}}*/
/*FUNCTION TransientInput::ChangeEnum{{{*/
void TransientInput::ChangeEnum(int newenumtype){
	this->enum_type=newenumtype;
}
/*}}}*/
/*FUNCTION TransientInput::GetInputAverage{{{*/
void TransientInput::GetInputAverage(IssmDouble* pvalue){

	IssmDouble time;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetInputAverage(pvalue);

	delete input;

}
/*}}}*/
/*FUNCTION TransientInput::GetInputAllTimeAverages{{{*/
void TransientInput::GetInputAllTimeAverages(IssmDouble** pvalues,IssmDouble** ptimes, int* pnumtimes){

	int i;
	IssmDouble* times=NULL;
	IssmDouble* values=NULL;

	/*allocate: */
	times=xNew<IssmDouble>(this->numtimesteps);
	values=xNew<IssmDouble>(this->numtimesteps);

	for(i=0;i<numtimesteps;i++){
		Input* input=(Input*)this->inputs->GetObjectByOffset(i);
		input->GetInputAverage(values+i);
		times[i]=this->timesteps[i];
	}

	*pvalues=values;
	*ptimes=times;
	*pnumtimes=numtimesteps;
}
/*}}}*/
/*FUNCTION TransientInput::GetInputUpToCurrentTimeAverages{{{*/
void TransientInput::GetInputUpToCurrentTimeAverages(IssmDouble** pvalues, IssmDouble** ptimes, int* pnumtimes, IssmDouble currenttime){

	int i;
	IssmDouble* times=NULL;
	IssmDouble* values=NULL;
	int numsteps;
	int count;
	bool iscurrenttime_included=false;

	/*Figure out how many time steps we are going to return: */
	numsteps=0;
	for(i=0;i<numtimesteps;i++){
		if(this->timesteps[i]==currenttime)iscurrenttime_included=true;
		if (this->timesteps[i]>currenttime)break;
		else numsteps++;
	}
	if(iscurrenttime_included==false)numsteps++;

	/*allocate: */
	times=xNew<IssmDouble>(numsteps);
	values=xNew<IssmDouble>(numsteps);

	for(i=0;i<numsteps;i++){

		if((iscurrenttime_included==false) && (i==(numsteps-1))){

			/*Retrieve interpolated values for current time step: */
			Input* input=GetTimeInput(currenttime);
			input->GetInputAverage(values+i);
			times[i]=currenttime;
		}
		else{
			Input* input=(Input*)this->inputs->GetObjectByOffset(i);
			input->GetInputAverage(values+i);
			times[i]=this->timesteps[i];
		}
	}

	*pvalues=values;
	*ptimes=times;
	*pnumtimes=numtimesteps;
}
/*}}}*/

/*Intermediary*/
/*FUNCTION TransientInput::AddTimeInput{{{*/
void TransientInput::AddTimeInput(Input* input,IssmDouble time){

	/*insert values at time step: */
	if (this->numtimesteps>0 && time<=this->timesteps[this->numtimesteps-1]) _error_("timestep values must increase sequentially");

	//copy timesteps, add the new time, delete previous timesteps, and add the new input: inputs->AddObject(input);
	IssmDouble* old_timesteps=NULL;

	if (this->numtimesteps > 0){
		old_timesteps=xNew<IssmDouble>(this->numtimesteps);
		xMemCpy(old_timesteps,this->timesteps,this->numtimesteps);
		xDelete(this->timesteps);
	}

	this->numtimesteps=this->numtimesteps+1;
	this->timesteps=xNew<IssmDouble>(this->numtimesteps);

	if (this->numtimesteps > 1){
		xMemCpy(this->timesteps,old_timesteps,this->numtimesteps-1);
		xDelete(old_timesteps);
	}

	/*go ahead and plug: */
	this->timesteps[this->numtimesteps-1]=time;
	inputs->AddObject(input);

}
/*}}}*/
/*FUNCTION TransientInput::Extrude{{{*/
void TransientInput::Extrude(void){

	for(int i=0;i<this->numtimesteps;i++){
		((Input*)this->inputs->GetObjectByOffset(i))->Extrude();
	}
}
/*}}}*/
/*FUNCTION TransientInput::SquareMin{{{*/
void TransientInput::SquareMin(IssmDouble* psquaremin,Parameters* parameters){

	IssmDouble time;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

   /*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->SquareMin(psquaremin,parameters);

	delete input;

}
/*}}}*/
/*FUNCTION TransientInput::InfinityNorm{{{*/
IssmDouble TransientInput::InfinityNorm(void){

	IssmDouble time;
	IssmDouble infnorm;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

   /*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	infnorm=input->InfinityNorm();

	/*Clean-up and return*/
	delete input;
	return infnorm;
}
/*}}}*/
/*FUNCTION TransientInput::Max{{{*/
IssmDouble TransientInput::Max(void){

	IssmDouble time;
	IssmDouble max;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

   /*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	max=input->Max();

	delete input;

	return max;
}
/*}}}*/
/*FUNCTION TransientInput::MaxAbs{{{*/
IssmDouble TransientInput::MaxAbs(void){

	IssmDouble time;
	IssmDouble maxabs;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	maxabs=input->MaxAbs();

	/*Clean-up and return*/
	delete input;
	return maxabs;

}
/*}}}*/
/*FUNCTION TransientInput::Min{{{*/
IssmDouble TransientInput::Min(void){

	IssmDouble time;
	IssmDouble min;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

   /*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	min=input->Min();

	/*Clean-up and return*/
	delete input;
	return min;

}
/*}}}*/
/*FUNCTION TransientInput::MinAbs{{{*/
IssmDouble TransientInput::MinAbs(void){

	IssmDouble time;
	IssmDouble minabs;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	minabs=input->MinAbs();

	/*Clean-up and return*/
	delete input;
	return minabs;
}
/*}}}*/
/*FUNCTION TransientInput::GetVectorFromInputs{{{*/
void TransientInput::GetVectorFromInputs(Vector<IssmDouble>* vector,int* doflist){

	IssmDouble time;

	/*First, recover current time from parameters: */
	parameters->FindParam(&time,TimeEnum);

	/*Retrieve interpolated values for this time step: */
	Input* input=GetTimeInput(time);

	/*Call input function*/
	input->GetVectorFromInputs(vector,doflist);

	delete input;

} /*}}}*/
/*FUNCTION TransientInput::GetTimeInput{{{*/
Input* TransientInput::GetTimeInput(IssmDouble intime){

	IssmDouble deltat;
	IssmDouble alpha1,alpha2;
	int        found;
	int        offset;

	Input *input  = NULL;
	Input *input1 = NULL;
	Input *input2 = NULL;

	/*go through the timesteps, and figure out which interval we 
	 *fall within. Then interpolate the values on this interval: */
	found=binary_search(&offset,intime,this->timesteps,this->numtimesteps);
	if(!found) _error_("Input not found (is TransientInput sorted ?)");

	if (offset==-1){
		/*get values for the first time: */
		_assert_(intime<this->timesteps[0]);
		input=(Input*)((Input*)this->inputs->GetObjectByOffset(0))->copy();
	}
	else if(offset==(this->numtimesteps-1)){
		/*get values for the last time: */
		_assert_(intime>=this->timesteps[offset]);
		input=(Input*)((Input*)this->inputs->GetObjectByOffset(offset))->copy();
	}
	else{
		/*get values between two times [offset:offset+1[, Interpolate linearly*/
		_assert_(intime>=this->timesteps[offset] && intime<this->timesteps[offset+1]);
		deltat=this->timesteps[offset+1]-this->timesteps[offset];
		alpha2=(intime-this->timesteps[offset])/deltat;
		alpha1=(1.0-alpha2);

		input1=(Input*)this->inputs->GetObjectByOffset(offset); 
		input2=(Input*)this->inputs->GetObjectByOffset(offset+1);

		input=(Input*)input1->copy();
		input->Scale(alpha1);
		input->AXPY(input2,alpha2);
	}

	/*Assign output pointer*/
	return input;
}
/*}}}*/
/*FUNCTION TransientInput::Configure{{{*/
void TransientInput::Configure(Parameters* parameters){
	this->parameters=parameters;
}
/*}}}*/
