#include <alberta.h>
#if (defined(ALBERTA_VER) && (ALBERTA_VER==2))
//# include <alberta_inlines.h>
//#include <alberta_util_inlines.h>
# include "alberta1_wrapper.h"
#endif
#include "newalberta.hh"
//#include "traverse_r_class.hh"
#include "do_on_el.hh"
#include "do_elfunc_on_el.hh"
#include "prototype_fem_elfunc.hh"
#include "fem_elfunc.hh"
#include "traverse.hh"
#include "fem_base.hh"
#include "build_stat.hh"
#include "build_diff.hh"
/*
!assembliert die Massenmatrix (ggf mit Faktor) und addiert sie zur Steifigkeitsmatrix, normal ohne sie separat zu
speichern.
Die Verwendung in Systemen wie build_sys ist folgendermassen gedacht:
ist prob_data_ptr->const_flag an der entsprechenden Stelle gesetzt, dann wird eine Massenmatrix 
buuild_sys::mass_mat
assembliert und
gespeichert u dann zur Systemmatrix addiert. 
Ist flags 0, dann ist die Massenmatrix sowieso fuer diese Komponente speziell u braucht nicht gespeichert zu
werden. Build_sys traegt in beiden faellen dafuer Sorge, dass
matrix 
richtig gesetzt ist.*/
static REAL unity_c(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
{
  return(el_info->el_geom_cache.det);
}

static _Bool m_init_element(const EL_INFO* el_info,const QUAD* quad[3], void * ud )
{
  OP_ARG_INFO *arg_info=(OP_ARG_INFO *)ud;
//  matfunc*	diff_mat_ptr; 
  elfunc* 	massfunc_ptr;
  	if ( (massfunc_ptr = arg_info->prob_data_ptr->massfunc))
		{massfunc_ptr->set_at_el(el_info, arg_info->eval_time);
		}
	return(0);
}

 REAL mass_std_c(const EL_INFO *el_info, const QUAD *quad, int iq, void *ud)
{
  OP_ARG_INFO *assem_el_info=(OP_ARG_INFO *)ud;
  elfunc* massfunc_ptr = assem_el_info->prob_data_ptr->massfunc;
  return(
		massfunc_ptr->calc( quad->lambda[iq])
				*(el_info->el_geom_cache.det) );		 
}

//void build_diff::el_fct(const EL_INFO *el_info)
int build_diff::calc(const EL_INFO *el_info)
{
 int i=0, j=0;
// build_stat::el_fct(el_info);
 build_stat::calc(el_info);
#if DEBUG
 //so kann ein stationaeres Problem ueber build_diff betrieben werden
 if (massmatrix_info->el_matrix_fct)
 {
#endif
 	mass_mat = 	(massmatrix_info->el_matrix_fct)(el_info, massmatrix_info->fill_info);
 	add_element_matrix(matrix, 1.0/(*timestep), test_quad_fast->n_bas_fcts
						,sol_bas_fcts->n_bas_fcts, 
 						row_dof, col_dof, mass_mat, bound); //siehe build_stat rhs_n_bas_fcts,sol_n_bas_fcts,
/****************************************************************************/
/*  f += {Mass/ tau}  u_old      Stiffness matrix already done                               */
/****************************************************************************/

     	for (i = 0; i < test_quad_fast->n_bas_fcts; i++) 
	{
//		MSG("mass_mat[%d][]: %e, %e, %e\n",col_dof[i], mass_mat[i][0]
//				,mass_mat[i][1],mass_mat[i][2]);
     		if (!bound || bound[i] < DIRICHLET) // !bound means different fe-spaces
//      	if (bound[i] < DIRICHLET) 
		{
 			INFO(info_level,10,"f_vec[[%d]]%e\n", col_dof[i], f_vec[col_dof[i]]);
    			REAL val = 0.0;
    			for (j = 0; j < sol_bas_fcts->n_bas_fcts; j++)
    			{
				val +=  (mass_mat[i][j]/ (*timestep))*uh_old_el[j];
			}
    			f_vec[col_dof[i]] += val;
		}
	}
#if DEBUG
  }
  else if (info_level, 10, "%s has no mass contribution\n", matrix->name);
#endif
	//not cheapest way but cleanest
 return 0;
}
#if 0
int build_diff::set_mass_op_info(int row, int col)
{
//	op_info.quad[2] = op_info.quad[0] =get_quadrature(DIM, 2*fe_space->bas_fcts->degree);
   if (op_arg_infos[row*ncomps + col]->prob_data_ptr->massfunc
	  	||op_arg_infos[row*ncomps + col]->prob_data_ptr->flags)
	{
	mass_op_info.user_data = &op_arg_info;
	mass_op_info.row_fe_space = op_info.row_fe_space;
//	op_info.col_fe_space = fe_space;
	mass_op_info.col_fe_space = op_info.col_fe_space;	//u_h->fe_space;
	mass_op_info.init_element = nil;
	mass_op_info.quad[2] = 	op_info.quad[2];		//muesste wurscht sein
	mass_op_info.quad[0] = 	op_info.quad[0];
	mass_op_info.LALt		= nil;
	mass_op_info.Lb0		= nil;
	mass_op_info.Lb1		= nil;
							/*kieken!*/
	mass_op_info.c		= unity_c;
	mass_op_info.c_pw_const	= true;
	mass_op_info.fill_flag		= 0;
	return(0);
	}
	else return(1);
} //und viell. noch einen f. spezielle ops
#define MASS_CONST (1 << 5)
#else
int build_diff::set_mass_op_info(int row, int col)
{
	memset(&mass_op_info, 0,sizeof(OPERATOR_INFO));
 	OP_ARG_INFO *oai = op_arg_infos[row*ncomps + col];
	if (oai->prob_data_ptr->massfunc
	  	|| (oai->prob_data_ptr->flags & MASS_CONST))
	{	//op_info lebt schon nicht mehr, nicht auf diesen beziehen!
	//	mass_op_info.user_data = op_arg_infos[row*ncomps + col];
		mass_op_info.row_fe_space = u_h[row]->fe_space;	
		mass_op_info.col_fe_space = f_h[col]->fe_space;		   

		if (oai->prob_data_ptr->flags & MASS_CONST)
		{
			mass_op_info.init_element = nil;
			mass_op_info.c		= unity_c;
			mass_op_info.c_pw_const	= true;
		}
		else
		{
			mass_op_info.init_element = m_init_element;
			mass_op_info.c		= mass_std_c;
			mass_op_info.c_pw_const	= false;
			mass_op_info.user_data 	= op_arg_infos[row*ncomps + col];
		}

		mass_op_info.quad[0] = 	quad; //quad_fast_uh->quad;//get_quadrature(DIM,1);geht nicht!!
	/*I think that all components should have equal integration formula degree*/
//		mass_op_info.quad[0] = 	get_quadrature(DIM,2);//geht nicht!!
//richtig oder musses hin?		mass_op_info.fill_flag		= CALL_LEAF_EL|FILL_COORDS|FILL_BOUND|FILL_NEIGH;
		if (row==col)
			mass_op_info.use_get_bound		= true;//noch keine Wirkung feststellen koennen
		else
			mass_op_info.use_get_bound		= false;
		return(0);
	}
	else
	{
		return(1);
	}
} //und viell. noch einen f. spezielle ops

#endif

int build_diff::common_construct_func()
{  FUNCNAME("build_diff common_construct_func");
  int  row_comp = 0, col_comp = 0, outcome;
  for (row_comp =0; row_comp<ncomps; row_comp++)
  {
    	for (col_comp =0; col_comp<ncomps; col_comp++)
  	{
		quad_fast_uh = test_quad_fasts[col_comp];//Argument for set_mass_op_info
		//in fem_base  	op_arg_info[comp] = new OP_ARG_INFO(prob_data[comp]);
		if ((outcome = set_mass_op_info( row_comp, col_comp)))
		{	INFO(info_level,6,"No %s Block %d %d mass_op_info\n",u_h[row_comp]->name, row_comp, col_comp);
			memset(&massmatrix_infos[row_comp*ncomps + col_comp], 0,sizeof(EL_MATRIX_INFO));
		}
		else
		{
			INFO(info_level,6,"%s Block %d %d set_mass_op_info done\n"
						,u_h[row_comp]->name, row_comp, col_comp);
			fill_matrix_info(&mass_op_info
					, &massmatrix_infos[row_comp*ncomps + col_comp]);
		} 
	}
 }
 massmatrix_info = massmatrix_infos; //falls ncomp = 1  
 return outcome;
}


build_diff::build_diff(const char *const name,  
			 DOF_REAL_VEC **u_h, DOF_REAL_VEC   **u_h_old, DOF_SCHAR_VEC** bound_vec
			 , DOF_REAL_VEC   **f_h 
			, const PROB_DATA_DIFF * const * const prob_data
			,const int ncomps, int quad_degree
			,REAL const   * const time, REAL const   * const timestep, const REAL theta)
  :  build_stat( name, u_h, u_h_old, bound_vec, f_h, prob_data, ncomps, quad_degree
				, time, timestep, theta)
	  , massmatrix_infos( 	new EL_MATRIX_INFO[ncomps*ncomps]) //viel zu viele, aber wenigstens gleich
		 							// indiziert
{
  FUNCNAME("build_diff constructor");
  common_construct_func();
}


build_diff::build_diff(const char *const name,fem_base const * const base
			, DOF_REAL_VEC   ** f_h, int quad_degree
			, const  REAL *const timestep, const REAL theta)
	:build_stat(name, base, f_h, quad_degree
			,  timestep, theta)
	,massmatrix_infos( 	new EL_MATRIX_INFO[ncomps*ncomps])
{
  FUNCNAME("build_diff");
	massmatrix_info = &massmatrix_infos[0];
	if (set_mass_op_info(0, 0))
	{	ERROR("No %s Block 0 0 mass_op_info\n",u_h[0]->name);
		memset(&massmatrix_infos[0], 0,sizeof(EL_MATRIX_INFO));
	}
	else
	{
		INFO(info_level,6,"%s Block  0 0 set_mass_op_info done\n"
				,u_h[0]->name);
		fill_matrix_info(&mass_op_info
				, massmatrix_info);
	} 
}


build_diff::build_diff(const char *const name,  FE_SPACE const *const fe_space
		      , const PROB_DATA_DIFF * const * const prob_data
		      , int ncomps ,  int quad_degree
				, REAL const   * const time, const  REAL *const timestep
				, const REAL theta)
  	: build_stat(name, fe_space, prob_data,  ncomps, quad_degree
			,   time, timestep, theta)
	,massmatrix_infos( 	new EL_MATRIX_INFO[ncomps*ncomps])
{
  FUNCNAME("build_diff constructor");
  common_construct_func();
#if 0
	massmatrix_info = &massmatrix_infos[0];
	if (set_mass_op_info(0, 0))
	{	ERROR("No %s Block 0 0 mass_op_info\n",u_h[0]->name);
	}
	else
	{
		INFO(info_level,6,"%s Block  0 0 set_mass_op_info done\n"
				,u_h[0]->name);
		fill_matrix_info(&mass_op_info
				, massmatrix_info);
	} 
#endif
}
