/*
 * \file Parameters.cpp
 * \brief: Implementation of the Parameters 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 "./Parameters.h"
#include "./Param.h"

#include "./BoolParam.h"
#include "./DoubleMatParam.h"
#include "./DataSetParam.h"
#include "./DoubleParam.h"
#include "./DoubleVecParam.h"
#include "./IntParam.h"
#include "./IntVecParam.h"
#include "./IntMatParam.h"
#include "./FileParam.h"
#include "./MatrixParam.h"
#include "./VectorParam.h"
#include "./StringArrayParam.h"
#include "./StringParam.h"

#include "../../shared/shared.h"
#include "../../toolkits/toolkits.h"

using namespace std;
/*}}}*/

/*Object constructors and destructor*/
Parameters::Parameters(){/*{{{*/
	enum_type=ParametersEnum;
	return;
}
/*}}}*/
Parameters::~Parameters(){/*{{{*/
	return;
}
/*}}}*/

/*Object management*/
bool Parameters::Exist(int enum_type){/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

	for ( object=objects.begin() ; object < objects.end(); object++ ){
		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type) return true;
	}
	return false;
}
/*}}}*/
void Parameters::FindParam(bool* pbool,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pbool);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));
}
/*}}}*/
void Parameters::FindParam(int* pinteger,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pinteger);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));
}
/*}}}*/
void Parameters::FindParam(IssmDouble* pscalar, int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pscalar);
			return;
		}
	}
	_error_("could not find parameter " <<  EnumToStringx(enum_type));
}
/*}}}*/
void Parameters::FindParam(IssmDouble* pscalar, int enum_type,IssmDouble time){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pscalar,time);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));
}
/*}}}*/
void Parameters::FindParam(char** pstring,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pstring);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(char*** pstringarray,int* pM,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pstringarray,pM);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(int** pintarray,int* pM, int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pintarray,pM);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(int** pintarray,int* pM,int *pN,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pintarray,pM,pN);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(IssmDouble** pIssmDoublearray,int* pM, int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pIssmDoublearray,pM);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(IssmDouble** pIssmDoublearray,int* pM, int* pN,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pIssmDoublearray,pM,pN);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(IssmDouble*** parray,int* pM,int** pmdims_array,int** pndims_array,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(parray,pM,pmdims_array,pndims_array);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));
}
/*}}}*/
void Parameters::FindParam(Vector<IssmDouble>** pvec,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pvec);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(Matrix<IssmDouble>** pmat,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pmat);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));

}
/*}}}*/
void Parameters::FindParam(FILE** pfid,int enum_type){ _assert_(this);/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pfid);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));
}
/*}}}*/
void Parameters::FindParam(DataSet** pdataset,int enum_type){ /*{{{*/
	_assert_(this);

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			param->GetParameterValue(pdataset);
			return;
		}
	}
	_error_("could not find parameter " << EnumToStringx(enum_type));
}
/*}}}*/

void   Parameters::SetParam(bool boolean,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(boolean); //already exists, just set it.
	else this->AddObject(new BoolParam(enum_type,boolean)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(int integer,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(integer); //already exists, just set it.
	else this->AddObject(new IntParam(enum_type,integer)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(IssmDouble scalar,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(scalar); //already exists, just set it.
	else this->AddObject(new DoubleParam(enum_type,scalar)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(char* string,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(string); //already exists, just set it.
	else this->AddObject(new StringParam(enum_type,string)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(char** stringarray,int M, int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(stringarray,M); //already exists, just set it.
	else this->AddObject(new StringArrayParam(enum_type,stringarray,M)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(IssmDouble* IssmDoublearray,int M, int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(IssmDoublearray,M); //already exists, just set it.
	else this->AddObject(new DoubleVecParam(enum_type,IssmDoublearray,M)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(IssmDouble* IssmDoublearray,int M, int N, int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(IssmDoublearray,M,N); //already exists, just set it.
	else this->AddObject(new DoubleMatParam(enum_type,IssmDoublearray,M,N)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(int* intarray,int M, int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(intarray,M); //already exists, just set it.
	else this->AddObject(new IntVecParam(enum_type,intarray,M)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(int* intarray,int M, int N, int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(intarray,M,N); //already exists, just set it.
	else this->AddObject(new IntMatParam(enum_type,intarray,M,N)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(Vector<IssmDouble>* vector,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(vector); //already exists, just set it.
	else this->AddObject(new VectorParam(enum_type,vector)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(Matrix<IssmDouble>* matrix,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(matrix); //already exists, just set it.
	else this->AddObject(new MatrixParam(enum_type,matrix)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(FILE* fid,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(fid); //already exists, just set it.
	else this->AddObject(new FileParam(enum_type,fid)); //just add the new parameter.
}
/*}}}*/
void   Parameters::SetParam(DataSet* dataset,int enum_type){/*{{{*/

	Param* param=NULL;

	/*first, figure out if the param has already been created: */
	param=dynamic_cast<Param*>(this->FindParamObject(enum_type));

	if(param) param->SetValue(dataset); //already exists, just set it.
	else this->AddObject(new DataSetParam(enum_type,dataset)); //just add the new parameter.
}
/*}}}*/

Object* Parameters::FindParamObject(int enum_type){/*{{{*/

	vector<Object*>::iterator object;
	Param* param=NULL;

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

		param=dynamic_cast<Param*>(*object);
		if(param->InstanceEnum()==enum_type){
			return (*object);
		}
	}
	return NULL;
}
/*}}}*/

/*Methods relating to parameters: */
char* OptionsFromAnalysis(Parameters* parameters,int analysis_type){ /*{{{*/

	/* figure out ISSM options for current analysis, return a string. */ 

	/*output: */
	char*   outstring=NULL;

	/*intermediary: */
	int          dummy;
	IssmDouble  *analyses    = NULL;
	char       **strings     = NULL;
	char        *string      = NULL;
	int          numanalyses;
	int          found       = -1;
	int          i;

	numanalyses=0;
	parameters->FindParam(&strings,&numanalyses,ToolkitsOptionsStringsEnum);

	parameters->FindParam(&analyses,&dummy,ToolkitsOptionsAnalysesEnum);

	if(numanalyses==0)return NULL; //we did not find petsc options, don't bother.

	/*ok, go through analyses and figure out if it corresponds to our analysis_type: */
	for(i=0;i<numanalyses;i++){
		if(analyses[i]==analysis_type){
			found=i;
			break;
		}
	}
	if(found==-1){
		/*still haven't found a list of petsc options, go find the default one, for analysis type DefaultAnalysisEnum: */
		for(i=0;i<numanalyses;i++){
			if(analyses[i]==DefaultAnalysisEnum){
				found=i;
				break;
			}
		}
	}
	if (found==-1){
		_error_("could find neither a default analysis nor analysis " << EnumToStringx(analysis_type));
	}

	/*ok, grab the option string: */
	outstring=xNew<char>(strlen(strings[found])+1);
	strcpy(outstring,strings[found]);

	/*Free ressources*/
	xDelete<IssmDouble>(analyses);
	for(i=0;i<numanalyses;i++){
		string=strings[i];
		xDelete<char>(string);
	}
	xDelete<char*>(strings);
	return outstring;
} 
/*}}}*/
void ToolkitsOptionsFromAnalysis(Parameters* parameters,int analysis_type){ /*{{{*/

	/*!\file:  ToolkitsOptionsFromAnalysis.cpp
	 * \brief: for each analysis, setup the issmoptions string. 
	 * This is mainly for the case where we run our toolkits using petsc. In this case, we need to 
	 * plug our toolkits options directly into the petsc options database. This is the case for each analysis type 
	 * and parameters
	 */ 

	char* options=NULL;

	/*Recover first the options string for this analysis: */
	options=OptionsFromAnalysis(parameters,analysis_type);

	/*Initialize our Toolkit Options: */
	ToolkitOptions::Init(options);

	#ifdef _HAVE_PETSC_
		/*In case we are using PETSC, we do not rely on issmoptions. Instead, we dump issmoptions into the Petsc 
		 * options database: */

		#if _PETSC_MAJOR_ == 2 
		PetscOptionsDestroy();
		PetscOptionsCreate();
		//PetscOptionsCheckInitial_Private();
		//PetscOptionsCheckInitial_Components();
		PetscOptionsSetFromOptions();
		PetscOptionsInsertMultipleString(options); //our patch
		#else
		PetscOptionsSetFromOptions();
		PetscOptionsClear();
		//PetscOptionsSetFromOptions();
		PetscOptionsInsertMultipleString(options); //our patch
		#endif

	#endif

	xDelete<char>(options);
}
/*}}}*/
