/*!\file:  DakotaSpawnCore.cpp

 * \brief: run core ISSM solution using Dakota inputs coming from CPU 0.
 * \sa qmu.cpp DakotaPlugin.cpp
 *
 * This routine needs to be understood simultaneously with qmu.cpp and DakotaPlugin. 
 * DakotaSpawnCoreParallel is called by all CPUS, with CPU 0 holding Dakota variable values, along 
 * with variable descriptors. 
 *
 * DakotaSpawnCoreParallel takes care of broadcasting the variables and their descriptors across the MPI 
 * ring. Once this is done, we use the variables to modify the inputs for the solution core. 
 * For ex, if "rho_ice" is provided, for ex 920, we include "rho_ice" in the inputs, then 
 * call the core with the modified inputs. This is the way we get Dakota to explore the parameter 
 * spce of the core. 
 *
 * Once the core is called, we process the results of the core, and using the processed results, 
 * we compute response functions. The responses are computed on all CPUS, but they are targeted 
 * for CPU 0, which will get these values back to the Dakota engine. 
 *
 */ 

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

#include "../classes/objects/objects.h"
#include "../io/io.h"
#include "../EnumDefinitions/EnumDefinitions.h"
#include "../shared/shared.h"
#include "../include/include.h"
#include "../solutions/solutions.h"
#include "../modules/modules.h"

void DakotaMPI_Bcast(double** pvariables, char*** pvariables_descriptors,int* pnumvariables, int* pnumresponses);
void DakotaFree(double** pvariables,char*** pvariables_descriptors,char*** presponses_descriptors,int numvariables,int numresponses);

/*}}}*/

/*Notice the d_, which prefixes anything that is being provided to us by the Dakota pluggin. Careful. some things are ours, some are dakotas!: */
int DakotaSpawnCore(double* d_responses, int d_numresponses, double* d_variables, char** d_variables_descriptors,int d_numvariables, void* void_femmodel,int counter){

	char     **responses_descriptors    = NULL;      //these are our! there are only numresponsedescriptors of them, not d_numresponses!!!
	int        numresponsedescriptors;
	char      *string                   = NULL;
	int        solution_type;
	bool       control_analysis         = false;
	void     (*solutioncore)(FemModel*) = NULL;
	FemModel  *femmodel                 = NULL;
	bool       nodakotacore             = true;

	/*If counter==-1 on cpu0, it means that the dakota runs are done. In which case, bail out and return 0: */
	#ifdef _HAVE_MPI_
	MPI_Bcast(&counter,1,MPI_INT,0,IssmComm::GetComm()); 
	#endif
	if(counter==-1)return 0;

	/*cast void_femmodel to FemModel: */
	femmodel=reinterpret_cast<FemModel*>(void_femmodel);

	/*retrieve parameters: */
	femmodel->parameters->FindParam(&responses_descriptors,&numresponsedescriptors,QmuResponsedescriptorsEnum);
	femmodel->parameters->FindParam(&solution_type,SolutionTypeEnum);
	femmodel->parameters->FindParam(&control_analysis,InversionIscontrolEnum);

	if(VerboseQmu()) _pprintLine_("qmu iteration: " << counter);

	/* only cpu 0, running dakota is providing us with variables and variables_descriptors and numresponses: broadcast onto other cpus: */
	DakotaMPI_Bcast(&d_variables,&d_variables_descriptors,&d_numvariables,&d_numresponses);

	/*Modify core inputs in objects contained in femmodel, to reflect the dakota variables inputs: */
	InputUpdateFromDakotax(femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,d_variables,d_variables_descriptors,d_numvariables);

	/*Determine solution sequence: */
	if(VerboseQmu()) _pprintLine_("Starting " << EnumToStringx(solution_type) << " core:");
	WrapperCorePointerFromSolutionEnum(&solutioncore,femmodel->parameters,solution_type,nodakotacore);

	/*Run the core solution sequence: */
	solutioncore(femmodel);

	/*compute responses: */
	if(VerboseQmu()) _pprintLine_("compute dakota responses:");
	femmodel->DakotaResponsesx(d_responses,responses_descriptors,numresponsedescriptors,d_numresponses);

	/*Free ressources:*/
	DakotaFree(&d_variables,&d_variables_descriptors,&responses_descriptors, d_numvariables, numresponsedescriptors);

	return 1; //this is critical! do not return 0, otherwise, dakota_core will stop running!
}

void DakotaMPI_Bcast(double** pvariables, char*** pvariables_descriptors,int* pnumvariables, int* pnumresponses){ /*{{{*/

	/* * \brief: broadcast variables_descriptors, variables, numvariables and numresponses
	 * from cpu 0 to all other cpus.
	 */ 

	int i;
	int my_rank;

	/*inputs and outputs: */
	double* variables=NULL;
	char**  variables_descriptors=NULL;
	int     numvariables;
	int     numresponses;

	/*intermediary: */
	char* string=NULL;
	int   string_length;

	/*recover my_rank:*/
	my_rank=IssmComm::GetRank();

	/*recover inputs from pointers: */
	variables=*pvariables;
	variables_descriptors=*pvariables_descriptors;
	numvariables=*pnumvariables;
	numresponses=*pnumresponses;

	/*numvariables: */
	MPI_Bcast(&numvariables,1,MPI_INT,0,IssmComm::GetComm()); 

	/*variables:*/
	if(my_rank!=0)variables=xNew<double>(numvariables);
	MPI_Bcast(variables,numvariables,MPI_DOUBLE,0,IssmComm::GetComm()); 

	/*variables_descriptors: */
	if(my_rank!=0){
		variables_descriptors=xNew<char*>(numvariables);
	}
	for(i=0;i<numvariables;i++){
		if(my_rank==0){
			string=variables_descriptors[i];
			string_length=(strlen(string)+1)*sizeof(char);
		}
		MPI_Bcast(&string_length,1,MPI_INT,0,IssmComm::GetComm()); 
		if(my_rank!=0)string=xNew<char>(string_length);
		MPI_Bcast(string,string_length,MPI_CHAR,0,IssmComm::GetComm()); 
		if(my_rank!=0)variables_descriptors[i]=string;
	}

	/*numresponses: */
	MPI_Bcast(&numresponses,1,MPI_INT,0,IssmComm::GetComm()); 

	/*Assign output pointers:*/
	*pnumvariables=numvariables;
	*pvariables=variables;
	*pvariables_descriptors=variables_descriptors;
	*pnumresponses=numresponses;
} /*}}}*/
void DakotaFree(double** pvariables,char*** pvariables_descriptors,char*** presponses_descriptors,int numvariables,int numresponses){ /*{{{*/

	/*\brief DakotaFree: free allocations on other cpus, not done by Dakota.*/

	int i;
	int my_rank;

	double  *variables             = NULL;
	char   **variables_descriptors = NULL;
	char   **responses_descriptors = NULL;
	char    *string                = NULL;

	/*recover pointers: */
	variables=*pvariables;
	variables_descriptors=*pvariables_descriptors;
	responses_descriptors=*presponses_descriptors;

	/*recover my_rank:*/
	my_rank=IssmComm::GetRank();

	/*Free variables and variables_descriptors only on cpu !=0*/
	if(my_rank!=0){
		xDelete<double>(variables);
		for(i=0;i<numvariables;i++){
			string=variables_descriptors[i];
			xDelete<char>(string);
		}
		xDelete<char*>(variables_descriptors);
	}

	//responses descriptors on every cpu
	for(i=0;i<numresponses;i++){
		string=responses_descriptors[i];
		xDelete<char>(string);
	}
	//rest of dynamic allocations.
	xDelete<char*>(responses_descriptors);

	/*Assign output pointers:*/
	*pvariables=variables;
	*pvariables_descriptors=variables_descriptors;
	*presponses_descriptors=responses_descriptors;
} /*}}}*/
