/*
 * \file Inputs.c
 * \brief: implementation of the Inputs class, derived from DataSet class
 */

/*Headers: {{{*/
#ifdef HAVE_CONFIG_H
	#include <config.h>
#else
#error "Cannot compile with HAVE_CONFIG_H symbol! run configure first!"
#endif

#include <vector>
#include <functional>
#include <algorithm>
#include <iostream>

#include "./DataSet.h"
#include "../shared/shared.h"
#include "../include/include.h"
#include "../EnumDefinitions/EnumDefinitions.h"

using namespace std;
/*}}}*/

/*Object constructors and destructor*/
/*FUNCTION Inputs::Inputs(){{{*/
Inputs::Inputs(){
	return;
}
/*}}}*/
/*FUNCTION Inputs::~Inputs(){{{*/
Inputs::~Inputs(){
	return;
}
/*}}}*/

/*Object management*/
/*FUNCTION Inputs::GetInputValue(bool* pvalue,int enum-type){{{*/
void Inputs::GetInputValue(bool* pvalue,int enum_type){

	vector<Object*>::iterator object;
	Input* input=NULL;
	bool   found=false;

	/*Go through inputs and check whether any input with the same name is already in: */
	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 
		if (input->InstanceEnum()==enum_type){
			found=true;
			break;
		}
	}

	if (!found){
		/*we could not find an input with the correct enum type. No defaults values were provided, 
		 * error out: */
		_error2_("could not find input with enum type " << enum_type << " (" << EnumToStringx(enum_type) << ")");
	}

	/*Ok, we have an input if we made it here, request the input to return the value: */
	input->GetInputValue(pvalue);

}
/*}}}*/
/*FUNCTION Inputs::GetInputValue(int* pvalue,int enum-type){{{*/
void Inputs::GetInputValue(int* pvalue,int enum_type){

	vector<Object*>::iterator object;
	Input* input=NULL;
	bool   found=false;

	/*Go through inputs and check whether any input with the same name is already in: */
	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 
		if (input->InstanceEnum()==enum_type){
			found=true;
			break;
		}
	}

	if (!found){
		/*we could not find an input with the correct enum type. No defaults values were provided, 
		 * error out: */
		_error2_("could not find input with enum type " << enum_type << " (" << EnumToStringx(enum_type) << ")");
	}

	/*Ok, we have an input if we made it here, request the input to return the value: */
	input->GetInputValue(pvalue);

}
/*}}}*/
/*FUNCTION Inputs::GetInputValue(IssmDouble* pvalue,int enum-type){{{*/
void Inputs::GetInputValue(IssmDouble* pvalue,int enum_type){

	vector<Object*>::iterator object;
	Input* input=NULL;
	bool   found=false;

	/*Go through inputs and check whether any input with the same name is already in: */
	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 
		if (input->InstanceEnum()==enum_type){
			found=true;
			break;
		}
	}

	if (!found){
		/*we could not find an input with the correct enum type. No defaults values were provided, 
		 * error out: */
		_error2_("could not find input with enum type " << enum_type << " (" << EnumToStringx(enum_type) << ")");
	}

	/*Ok, we have an input if we made it here, request the input to return the value: */
	input->GetInputValue(pvalue);

}
/*}}}*/
/*FUNCTION Inputs::GetInputAverage{{{*/
void Inputs::GetInputAverage(IssmDouble* pvalue,int enum_type){

	vector<Object*>::iterator object;
	Input* input=NULL;
	bool   found=false;

	/*Go through inputs and check whether any input with the same name is already in: */
	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 
		if (input->InstanceEnum()==enum_type){
			found=true;
			break;
		}
	}

	if (!found){
		/*we could not find an input with the correct enum type. No defaults values were provided, 
		 * error out: */
		_error2_("could not find input with enum type " << enum_type << " (" << EnumToStringx(enum_type) << ")");
	}

	/*Ok, we have an input if we made it here, request the input to return the value: */
	input->GetInputAverage(pvalue);

}
/*}}}*/
/*FUNCTION Inputs::AddInput{{{*/
int  Inputs::AddInput(Input* in_input){

	/*First, go through dataset of inputs and check whether any input 
	 * with the same name is already in. If so, erase the corresponding 
	 * object before adding this new one: */
	vector<Object*>::iterator object;
	Input* input=NULL;

	/*In debugging mode, check that the input is not a NULL pointer*/
	_assert_(in_input);

	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 

		if (input->InstanceEnum()==in_input->InstanceEnum()){
			this->DeleteObject(input);
			break;
		}
	}
	this->AddObject(in_input);

	return 1;
}
/*}}}*/
/*FUNCTION Inputs::ChangeEnum{{{*/
void  Inputs::ChangeEnum(int oldenumtype,int newenumtype){

	/*Go through dataset of inputs and look for input with 
	 * same enum as input enum, once found, just change its name */
	vector<Object*>::iterator object;
	Input* input=NULL;

	/*Delete existing input of newenumtype if it exists*/
	for ( object=objects.begin() ; object < objects.end(); object++ ){
		input=(Input*)(*object); 

		if (input->InstanceEnum()==newenumtype){
			this->DeleteObject(input);
			break;
		}
	}

	/*Change enum_type of input of oldenumtype*/
	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 

		if (input->InstanceEnum()==oldenumtype){
			input->ChangeEnum(newenumtype);
			break;
		}
	}
}
/*}}}*/
/*FUNCTION Inputs::ConstrainMin{{{*/
void  Inputs::ConstrainMin(int constrain_enum, IssmDouble minimum){
	   
	Input* constrain_input=NULL; 
	/*Find x and y inputs: */
	constrain_input=(Input*)this->GetInput(constrain_enum);

	/*some checks: */
	if(!constrain_input) _error2_("input " << EnumToStringx(constrain_enum) << " could not be found!");

	/*Apply ContrainMin: */
	constrain_input->ConstrainMin(minimum);
}
/*}}}*/
/*FUNCTION Inputs::InfinityNorm{{{*/
IssmDouble Inputs::InfinityNorm(int enumtype){

	/*Output*/
	IssmDouble norm;

	/*Get input*/
	Input* input=(Input*)this->GetInput(enumtype);

	/*Apply ContrainMin: */
	if (input){
		norm=input->InfinityNorm();
	}
	else{
		norm=0;
	}

	/*Return output*/
	return norm;
}
/*}}}*/
/*FUNCTION Inputs::Max{{{*/
IssmDouble Inputs::Max(int enumtype){

	/*Output*/
	IssmDouble max;

	/*Get input*/
	Input* input=(Input*)this->GetInput(enumtype);

	/*Apply ContrainMin: */
	if (input){
		max=input->Max();
	}
	else{
		_error2_("Input " << EnumToStringx(enumtype) << " not found");
	}

	/*Return output*/
	return max;
}
/*}}}*/
/*FUNCTION Inputs::MaxAbs{{{*/
IssmDouble Inputs::MaxAbs(int enumtype){

	/*Output*/
	IssmDouble max;

	/*Get input*/
	Input* input=(Input*)this->GetInput(enumtype);

	/*Apply ContrainMin: */
	if (input){
		max=input->MaxAbs();
	}
	else{
		_error2_("Input " << EnumToStringx(enumtype) << " not found");
	}

	/*Return output*/
	return max;
}
/*}}}*/
/*FUNCTION Inputs::Min{{{*/
IssmDouble Inputs::Min(int enumtype){

	/*Output*/
	IssmDouble min;

	/*Get input*/
	Input* input=(Input*)this->GetInput(enumtype);

	/*Apply ContrainMin: */
	if (input){
		min=input->Min();
	}
	else{
		_error2_("Input " << EnumToStringx(enumtype) << " not found");
	}

	/*Return output*/
	return min;
}
/*}}}*/
/*FUNCTION Inputs::MinAbs{{{*/
IssmDouble Inputs::MinAbs(int enumtype){

	/*Output*/
	IssmDouble min;

	/*Get input*/
	Input* input=(Input*)this->GetInput(enumtype);

	/*Apply ContrainMin: */
	if (input){
		min=input->MinAbs();
	}
	else{
		_error2_("Input " << EnumToStringx(enumtype) << " not found");
	}

	/*Return output*/
	return min;
}
/*}}}*/
/*FUNCTION Inputs::GetInput{{{*/
Input* Inputs::GetInput(int enum_name){

	vector<Object*>::iterator object;
	Input* input=NULL;

	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 

		if (input->InstanceEnum()==enum_name){
			return input;
		}
	}
	return NULL;
}
/*}}}*/
/*FUNCTION Inputs::DeleteInput{{{*/
int  Inputs::DeleteInput(int enum_type){

	vector<Object*>::iterator object;
	Input* input=NULL;

	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object); 

		if (input->InstanceEnum()==enum_type){
			this->DeleteObject(input);
			break;
		}
	}

	return 1;

}
/*}}}*/
/*FUNCTION Inputs::DuplicateInput{{{*/
void  Inputs::DuplicateInput(int original_enum,int new_enum){

	Input* original=NULL;
	Input* copy=NULL;

	/*Make a copy of the original input: */
	original=(Input*)this->GetInput(original_enum);
	if(!original)_error2_("could not find input with enum: " << EnumToStringx(original_enum)); 
	copy=(Input*)original->copy();

	/*Change copy enum to reinitialized_enum: */
	copy->ChangeEnum(new_enum);

	/*Add copy into inputs, it will wipe off the one already there: */
	this->AddInput((Input*)copy);
}
/*}}}*/
/*FUNCTION Inputs::SpawnTriaInputs{{{*/
Inputs* Inputs::SpawnTriaInputs(int* indices){

	/*Intermediary*/
	vector<Object*>::iterator object;
	Input* inputin=NULL;
	Input* inputout=NULL;

	/*Output*/
	Inputs* newinputs=new Inputs();

	/*Go through inputs and call Spawn function*/
	for ( object=objects.begin() ; object < objects.end(); object++ ){

		/*Create new input*/
		inputin=(Input*)(*object); 
		inputout=inputin->SpawnTriaInput(indices);

		/*Add input to new inputs*/
		newinputs->AddObject(inputout);
	}

	/*Assign output pointer*/
	return newinputs;
}
/*}}}*/
/*FUNCTION Inputs::AXPY{{{*/
void  Inputs::AXPY(int MeshYEnum, IssmDouble scalar, int MeshXEnum){
	   
	Input* xinput=NULL;
	Input* yinput=NULL;

	/*Find x and y inputs: */
	xinput=(Input*)this->GetInput(MeshXEnum);
	yinput=(Input*)this->GetInput(MeshYEnum);

	/*some checks: */
	if(!xinput) _error2_("input " << EnumToStringx(MeshXEnum) << " could not be found!");
	if(!yinput) _error2_("input " << EnumToStringx(MeshYEnum) << " could not be found!");

	/*Apply AXPY: */
	yinput->AXPY(xinput,scalar);
}
/*}}}*/
/*FUNCTION Inputs::Configure{{{*/
void Inputs::Configure(Parameters* parameters){

	vector<Object*>::iterator object;
	Input* input=NULL;

	for ( object=objects.begin() ; object < objects.end(); object++ ){

		input=(Input*)(*object);
		input->Configure(parameters);

	}

}
/*}}}*/
