/*!\file: control_core.cpp
 * \brief: core of the control solution 
 */ 
#include <config.h>
#include "../toolkits/toolkits.h"
#include "../classes/objects/objects.h"
#include "../shared/shared.h"
#include "../io/io.h"
#include "../EnumDefinitions/EnumDefinitions.h"
#include "./solutions.h"
#include "../modules/modules.h"
#include "../include/include.h"
#include "../solvers/solvers.h"

#if defined (_HAVE_TAO_) && defined (_HAVE_PETSC_) && (_PETSC_MAJOR_ == 3 && _PETSC_MINOR_ > 1)
#include <tao.h>

/*Local prototype*/
int FormFunctionGradient(TaoSolver,Vec,IssmDouble*,Vec,void*);
int IssmMonitor(TaoSolver,void*);
typedef struct {
	FemModel* femmodel;
	double*   J;
} AppCtx;

void controltao_core(FemModel* femmodel){

	/*TAO*/
	int                 ierr;
	int                 num_controls,solution_type;
	int                 nsteps,maxiter;
	AppCtx              user;
	TaoSolver           tao = 0;
	IssmDouble         *dummy        = NULL;
	int                *control_list = NULL;
	Vector<IssmDouble> *X            = NULL;
	Vector<IssmDouble> *XL           = NULL;
	Vector<IssmDouble> *XU           = NULL;

	/*Initialize TAO*/
	int argc; char **args=NULL;
	PetscGetArgs(&argc,&args);
	ierr = TaoInitialize(&argc,&args,(char*)0,"");
	if(ierr) _error_("Could not initialize Tao");

	/*Recover some parameters*/
	femmodel->parameters->FindParam(&solution_type,SolutionTypeEnum);
	femmodel->parameters->FindParam(&num_controls,InversionNumControlParametersEnum);
	femmodel->parameters->FindParam(&control_list,NULL,InversionControlParametersEnum);
	femmodel->parameters->FindParam(&nsteps,InversionNstepsEnum);
	femmodel->parameters->FindParam(&dummy,NULL,NULL,InversionMaxiterPerStepEnum);
	femmodel->parameters->SetParam(false,SaveResultsEnum);
	maxiter=nsteps*(int)dummy[0]; xDelete<IssmDouble>(dummy);

	/*Initialize TAO*/
	TaoCreate(IssmComm::GetComm(),&tao);
	if(VerboseControl()) _pprintLine_("   Initializing the Toolkit for Advanced Optimization (TAO)");
	TaoSetFromOptions(tao);
	TaoSetType(tao,"tao_blmvm");
	//TaoSetType(tao,"tao_cg");
	//TaoSetType(tao,"tao_lmvm");

	/*Prepare all TAO parameters*/
	TaoSetMonitor(tao,IssmMonitor,&user,NULL);
	TaoSetMaximumFunctionEvaluations(tao,maxiter);
	TaoSetMaximumIterations(tao,nsteps);
	TaoSetTolerances(tao,0.,0.,0.,0.,0.);

	GetVectorFromControlInputsx(&X, femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,"value");
	GetVectorFromControlInputsx(&XL,femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,"lowerbound");
	GetVectorFromControlInputsx(&XU,femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,"upperbound");
	TaoSetInitialVector(tao,X->pvector->vector);
	TaoSetVariableBounds(tao,XL->pvector->vector,XU->pvector->vector);
	xdelete(&XL);
	xdelete(&XU);

	user.J=xNewZeroInit<double>(maxiter+5);
	user.femmodel=femmodel;
	TaoSetObjectiveAndGradientRoutine(tao,FormFunctionGradient,(void*)&user); 

	/*Solver optimization problem*/
	if(VerboseControl()) _pprintLine_("   Starting optimization");
	TaoSolve(tao);
	TaoView(tao,PETSC_VIEWER_STDOUT_WORLD);
	TaoGetSolutionVector(tao,&X->pvector->vector);
	SetControlInputsFromVectorx(femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,X);
	for(int i=0;i<num_controls;i++){
		InputToResultx(femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,control_list[i]);
	}
	femmodel->results->AddObject(new GenericExternalResult<double*>(femmodel->results->Size()+1,JEnum,user.J,maxiter+3,1,1,0));

	/*Finalize*/
	if(VerboseControl()) _pprintLine_("   preparing final solution");
	femmodel->parameters->SetParam(true,SaveResultsEnum);
	void (*solutioncore)(FemModel*)=NULL;
	CorePointerFromSolutionEnum(&solutioncore,femmodel->parameters,solution_type);
	solutioncore(femmodel);

	/*Clean up and return*/
	xDelete<int>(control_list);
	xDelete<double>(user.J);
	xdelete(&X);
	TaoDestroy(&tao);
	TaoFinalize();
}
int FormFunctionGradient(TaoSolver tao, Vec Xpetsc, IssmDouble *fcn,Vec G,void *userCtx){

	/*Retreive arguments*/
	int                  solution_type,num_cost_functions;
	AppCtx              *user            = (AppCtx *)userCtx;
	FemModel            *femmodel        = user->femmodel;
	int                 *cost_functions  = NULL;
	IssmDouble          *cost_functionsd = NULL;
	Vector<IssmDouble>  *gradient        = NULL;
	Vector<IssmDouble>  *X               = NULL;

	/*Convert input to Vec*/
	X=new Vector<IssmDouble>(Xpetsc);

	/*Set new variable*/
	//VecView(X,PETSC_VIEWER_STDOUT_WORLD);
	SetControlInputsFromVectorx(femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters,X);
	xdelete(&X);

	/*Recover some parameters*/
	femmodel->parameters->FindParam(&solution_type,SolutionTypeEnum);
	femmodel->parameters->FindParam(&num_cost_functions,InversionNumCostFunctionsEnum);
	femmodel->parameters->FindParam(&cost_functionsd,NULL,NULL,InversionCostFunctionsEnum);

	/*Prepare objective function*/
	cost_functions=xNew<int>(num_cost_functions);
	for(int i=0;i<num_cost_functions;i++) cost_functions[i]=(int)cost_functionsd[i]; //FIXME
	femmodel->parameters->SetParam(cost_functions,1,num_cost_functions,StepResponsesEnum);

	/*Compute solution and adjoint*/
	void (*solutioncore)(FemModel*)=NULL;
	void (*adjointcore)(FemModel*)=NULL;
	CorePointerFromSolutionEnum(&solutioncore,femmodel->parameters,solution_type);
	AdjointCorePointerFromSolutionEnum(&adjointcore,solution_type);
	solutioncore(femmodel);
	adjointcore(femmodel);

	/*Compute objective function*/
	femmodel->CostFunctionx(fcn);

	/*Compute gradient*/
	Gradjx(&gradient,NULL,femmodel->elements,femmodel->nodes,femmodel->vertices,femmodel->loads,femmodel->materials,femmodel->parameters);
	VecCopy(gradient->pvector->vector,G); xdelete(&gradient);
	VecScale(G,-1.);

	/*Clean-up and return*/
	xDelete<int>(cost_functions);
	xDelete<IssmDouble>(cost_functionsd);
	return 0;
}
int IssmMonitor(TaoSolver tao, void *userCtx){

	int       i,its,num_responses;
	IssmDouble    f,gnorm,cnorm,xdiff;
	AppCtx   *user      = (AppCtx *)userCtx;
	FemModel *femmodel  = user->femmodel;
	Element  *element   = NULL;
	int      *responses = NULL;

	femmodel->parameters->FindParam(&num_responses,InversionNumCostFunctionsEnum);
	femmodel->parameters->FindParam(&responses,NULL,NULL,StepResponsesEnum);

	TaoGetSolutionStatus(tao, &its, &f, &gnorm, &cnorm, &xdiff, NULL);
	if(its==0) _pprintLine_("Iter       Function      Residual  |  List of contributions");
	if(its==0) _pprintLine_("-----------------------------------+-----------------------");
	_pprintString_(setw(4)<<its<<"   "<<setw(12)<<setprecision(7)<<f<<"  "<<setw(12)<<setprecision(7)<<gnorm<<"  | ");
	user->J[its]=f;

	/*Retrieve objective functions independently*/
	for(i=0;i<num_responses;i++){
		femmodel->Responsex(&f,EnumToStringx(responses[i]),false,i);
		_pprintString_(" "<<setw(12)<<setprecision(7)<<f);
	}
	_pprintLine_("");

	/*Clean-up and return*/
	xDelete<int>(responses);
	return 0;
}

#else
void controltao_core(FemModel* femmodel){
	_error_("TAO not installed or PETSc version not supported");
}
#endif //_HAVE_TAO_ 
