/*!\file Gradjx
 * \brief: compute inverse method gradient
 */

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

void Gradjx(Vector<IssmDouble>** pgradient,IssmDouble** pnorm_list, Elements* elements,Nodes* nodes, Vertices* vertices, Loads* loads, Materials* materials, Parameters* parameters){

	int     i,j,numberofvertices;
	int     num_controls;
	IssmDouble   norm_inf;
	IssmDouble  *norm_list     = NULL;
	int     *control_type  = NULL;
	Vector<IssmDouble>  *gradient      = NULL;
	Vector<IssmDouble> **gradient_list = NULL;

	/*retrieve some parameters: */
	parameters->FindParam(&num_controls,InversionNumControlParametersEnum);   _assert_(num_controls);
	parameters->FindParam(&control_type,NULL,InversionControlParametersEnum);
	numberofvertices=vertices->NumberOfVertices();

	/*Allocate gradient_list */
	gradient_list = xNew<Vector<IssmDouble>*>(num_controls);
	norm_list = xNew<IssmDouble>(num_controls);
	for(i=0;i<num_controls;i++){
		gradient_list[i]=new Vector<IssmDouble>(num_controls*numberofvertices);
	}
	gradient=new Vector<IssmDouble>(num_controls*numberofvertices);

	/*Compute all gradient_list*/
	for(i=0;i<num_controls;i++){

		for(j=0;j<elements->Size();j++){

			Element* element=(Element*)elements->GetObjectByOffset(j);
			element->Gradj(gradient_list[i],control_type[i],i);
		}

		gradient_list[i]->Assemble();

		norm_list[i]=gradient_list[i]->Norm(NORM_INF);
	}

	/*Add all gradient_list together*/
	for(i=0;i<num_controls;i++){
		gradient->AXPY(gradient_list[i],1.0);
		delete gradient_list[i];
	}

	/*Check that gradient is clean*/
	norm_inf=gradient->Norm(NORM_INF);
	if(norm_inf<=0)    _error_("||∂J/∂α||∞ = 0    gradient norm is zero");
	if(xIsNan<IssmDouble>(norm_inf))_error_("||∂J/∂α||∞ = NaN  gradient norm is NaN");

	/*Clean-up and assign output pointer*/
	if(pnorm_list){
		*pnorm_list=norm_list;
	}
	else{
		xDelete<IssmDouble>(norm_list);
	}
	if(pgradient)  *pgradient=gradient;
	xDelete<Vector<IssmDouble>*>(gradient_list);
	xDelete<int>(control_type);
}
