#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 "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 "operator_parts.hh"
#include "build_diff.hh"
#include "build_sys.hh"

int  build_sys::rhs_scp_testfuncs(const EL_INFO* el_info)
{
  S_CHAR *const bound_begin = bound; 
  DOF *const col_dof_begin = col_dof;
  for (int j_comp=0; j_comp< ncomps; j_comp++)
   {
	common_source_contrib->set_test_quad_fast(test_quad_fasts[j_comp]);
    	for (int i = 0; i < test_quad_fasts[j_comp]->n_bas_fcts; i++) //rhs not sol!!
     	{
		REAL val = 0.0;
     		if (/*!bound ||*/ bound[i] < DIRICHLET) // !bound means different fe-spaces, Das kann nicht funktionieren!
		{					//Speicher ist ja auf jeden fall angelegt worden!
			val = common_source_contrib->func_scp_testfunc( i, j_comp); //This may be
							//Gradient-Scalar-Product! Keep fill-flag!
         	}
		f_h[j_comp]->vec[col_dof[i]] += val;	//el_info->el_geom_cache.det Quadratur!!!
 	}
	bound 	+= test_quad_fasts[j_comp]->n_bas_fcts;
	col_dof += test_quad_fasts[j_comp]->n_bas_fcts;
    }
  bound   = bound_begin;	//zur Sicherheit
  col_dof = col_dof_begin;
  return(0);
}

/*******************************************************
Wir vereinbaren die folgenden Restriktionen an das System, aus folgenden Gruenden:

Dirichlet-Rand muss im Diagonalblock eingebunden sein, d.h die entsprechenden Matrixzeilen
muessen e_i sein, was in ALBERTA durch die Setzung von add_el_matrix(... ,bound) realisiert ist.
Diese Setzung ist aber nur dann moeglich, wenn in diesem Block 
		row_fe_space=col_fe_space 
ist! Deshalb gilt in der build_...-Erbfolge diese Einschraenkung, da wir hier nicht neue Methoden
fuer Randbedingungen schreiben wollen. Faellt mir auch eben keine Methode ein, bei der es unbedingt
anders sein muss.
Also: fe_space von uh[i] =	fe_space von fh[i]
Daraus folgt auch, dass ncomps Quadraturformeln aller Art genuegen. 
*/

/*taktische angelegenheit:
bound = nil haengt davon ab, ob die fe_spaces gleich oder verschieden sind wegen add_el_matrix.
Also haengt die Moeglichkeit des Einbindens von Randbedingungen davon ab, ob
row_fe_space=col_fe_space.
=>	fuer den Diagonalblock eines gekoppelten Systems muesste row_fe_space=col_fe_space
gelten, was hoofentlich ok ist.
Was passiert mit Nebendiagonalbloecken bei Dirichlet-RW's ??? Kann eigentlich nicht gut gehen,
denn add_el_matrix weiss nix davon und fuellt munter die betreffende Zeile.
Vorschlag: Matrizen nach assemblierung nachbehandeln
Dies bis zur Klaerung nicht loeschen!!!

*/

int build_sys::set_at_el(const EL_INFO * const el_info, REAL eval_time)
{
	int sol_index =0, rhs_index=0, col_comp, row_comp;
	fill_el_geom_cache(el_info, 0U); // scheinbar nicht noetig!!
//   	fill_el_geom_cache(el_info, FILL_EL_DET|FILL_EL_LAMBDA);//wg elast|FILL_EL_GRD_LAMBDA
	fem_elfunc::set_at_el( el_info, eval_time);
  	if (common_source_contrib)
   	   common_source_contrib->set_at_el(el_info, eval_time);
	for (col_comp=0; col_comp< ncomps; col_comp++)
	{
		row_comp = col_comp;
		sol_bas_fcts =	u_h[col_comp]->fe_space->bas_fcts;
   		sol_bas_fcts->get_dof_indices(el_info->el, 
					u_h[col_comp]->fe_space->admin,
						&row_dof[sol_index]);		//Baustelle.
       		sol_bas_fcts->get_real_vec(el_info->el, 	u_old[col_comp], 
			&uh_old_el[sol_index]);
//	}									//Koennte anders sein
//tatsaechlich braucht man die Schleife nicht, kann in obere
//	for (row_comp=0; row_comp< ncomps; row_comp++)
//	{
//row_dof u. col_dof stehen im gleichen Speicher, siehe Kommentar oben. Dies hier ist ueberfluessig 
//		test_bas_fcts->get_dof_indices(el_info->el
//				, f_h[row_comp]->fe_space->admin, &col_dof[rhs_index]);		
		GET_BOUND( test_quad_fasts[col_comp]->bas_fcts, el_info, &bound[rhs_index] );				// !bound means different fe-spaces
		sol_index += sol_bas_fcts->n_bas_fcts;
		rhs_index += test_quad_fasts[col_comp]->n_bas_fcts;
	}
	return(0);
}

//void build_sys::el_fct(const EL_INFO *el_info)
int build_sys::calc(const EL_INFO *el_info)
{
	int row_comp=0, col_comp=0;
	REAL *const u_old_begin = uh_old_el;
	REAL *const u_begin=uh_el;
	DOF *const row_dof_begin = row_dof;
	DOF *const col_dof_begin = col_dof;
//	LAMBDA_DET* lambda_det_ptr;
    	S_CHAR *const bound_begin = bound; 
//  	set_at_el( el_info, &(op_arg_info->lambda_det));
//	lambda_det_ptr= &op_arg_info->lambda_det;
	for (row_comp=0; row_comp< ncomps; row_comp++)
	{
//		test_bas_fcts = f_h[row_comp]->fe_space->bas_fcts;
 		f_vec	= f_h[row_comp]->vec;
  		b_vec	= bound_vec[row_comp]->vec; //ok
		test_quad_fast = test_quad_fasts[row_comp];
		for (col_comp=0; col_comp< ncomps; col_comp++)
		{
		  	u_vec	= u_h[col_comp]->vec;
			sol_bas_fcts = u_h[col_comp]->fe_space->bas_fcts;
			matrix = matrix_blocks[row_comp*ncomps + col_comp];
//nee, nee			clear_dof_matrix(matrix);
			op_arg_info 	= op_arg_infos[row_comp*ncomps + col_comp];
			matrix_info 	= &matrix_infos[row_comp*ncomps + col_comp];
			massmatrix_info = &massmatrix_infos[row_comp*ncomps + col_comp];
			prob_data_ptr = op_arg_info->prob_data_ptr;
			if (prob_data_ptr->f_sources)
				prob_data_ptr->f_sources->set_at_el(el_info,
							op_arg_info->eval_time);

 			if (massmatrix_info->el_matrix_fct)		//so besser
 //			if  (op_arg_info->prob_data_ptr->constflag ||
//				op_arg_info->prob_data_ptr->massfunc)	//(row_comp== col_comp)
				build_diff::calc(el_info);
			else
				build_stat::calc(el_info);
			//increment pointers
			uh_el 	+=	sol_bas_fcts->n_bas_fcts;
			uh_old_el +=	sol_bas_fcts->n_bas_fcts;
			row_dof += 	sol_bas_fcts->n_bas_fcts;
		}
		bound 	+= test_quad_fast->n_bas_fcts;
		col_dof += test_quad_fast->n_bas_fcts;
//reihe voll, fangen bei Loesung Komp. 0 wieder an:
		uh_el 	=   u_begin   ;
		uh_old_el=    u_old_begin ;
		row_dof =    row_dof_begin;
	}
//wichtig
  bound   = bound_begin;
  col_dof = col_dof_begin;
  if (common_source_contrib)
  {
  	rhs_scp_testfuncs(el_info);
  	bound   = bound_begin;	//zur Sicherheit
  }
  //dirichlet-Rand in den Matrizen??
 return 0;
}

//build_diff
int build_sys::assemble()
{
  FUNCNAME("assemble");

  const FLAGS          fill_flag= CALL_LEAF_EL|FILL_COORDS|FILL_BOUND|FILL_NEIGH;
					//letzteres f. Robin
/****************************************************************************/
/*  init functions for matrix assembling                                    */
/****************************************************************************/
 const REAL	eval_time_f = (*time) - (1-theta)*(*timestep);
    op_arg_info->eval_time	=	eval_time_f;
  for (int row_comp=0; row_comp< ncomps; row_comp++)
  {
	for (int col_comp=0; col_comp< ncomps; col_comp++)
	{
		clear_dof_matrix( matrix_blocks[row_comp*ncomps + col_comp]);
  		INFO(info_level, 5,
			"%s diffcoeff(0,0) of el 0: %g\n",matrix_blocks[row_comp*ncomps + col_comp]->name
			,op_arg_infos[row_comp*ncomps + col_comp]->prob_data_ptr->diffmat->getcomp(0,0) );
	}		//kracht natuerlich immer wenn kein solcher da...
  }
/****************************************************************************/
/*  and now assemble the matrix and right hand side                         */
/****************************************************************************/

  traverse(u_h[0]->fe_space->mesh, -1, fill_flag, this);
/*dirichlet_bound(g_D, fh, u_h, nil); */
  if (info_level >= 8)
  {
  	for (int row_comp=0; row_comp< ncomps; row_comp++)
	{
	 	print_dof_real_vec(u_h[row_comp]);
	 	print_dof_real_vec(f_h[row_comp]);
		if (info_level >= 9)
  		{
 			for (int col_comp=0; col_comp< ncomps; col_comp++)
			{
				if (matrix_blocks[row_comp*ncomps + col_comp])
					{MSG("Block Matrix row %d  col %d after multi mesh traverse:\n",row_comp, col_comp );
		 			print_dof_matrix(matrix_blocks[row_comp*ncomps + col_comp]);
					}
			}
		}
	}
  }
  return(0);
}




build_sys::build_sys(const char *const name,  FE_SPACE const *const fe_space,
		       const PROB_DATA_DIFF * const * const prob_data
		       , fem_elfunc *  common_source
		       , int quad_degree, int ncomps
			, REAL const   * const time, const  REAL *const timestep, 
					 const REAL theta)
  : build_diff(name, fe_space, prob_data,  quad_degree, ncomps
					,   time, timestep, theta)
{
				common_source_contrib=common_source;
 }

build_sys::build_sys(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
		       ,fem_elfunc *  common_source
			,const int ncomps, int quad_degree
			,REAL const   * const time, REAL const   * const timestep, const REAL theta)
  :  build_diff( name, u_h, u_h_old, bound_vec, f_h, prob_data, ncomps, quad_degree
				, time, timestep, theta)
//geht nich				, common_source_contrib(common_source_contrib)
{	common_source_contrib=common_source;
}
