/**@file 
 *
 * Face-Bubble basis functions, meant to be chained with some standard
 * Lagrange space, optionally with divergence preserving interpolation
 * routine.
 *
 *      author:  Claus-Justus Heine
 *               Abteilung fuer Angewandte Mathematik                     
 *               Albert-Ludwigs-Universitaet Freiburg
 *               Hermann-Herder-Str. 10
 *               79104 Freiburg i.Br.
 *               Germany
 *               claus@mathematik.uni-freiburg.de
 *
 * Copyright (C) by Claus-Justus Heine (2008).
 *
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include <alberta.h>

#include "albas.h"

#define WEIGHT_1D 1.0
#define WEIGHT_2D 6.0
#define WEIGHT_3D 120.0

typedef struct wall_bubbles_data
{
  const EL             *cur_el;
  const EL_INFO        *cur_el_info;
  REAL_D               wall_normals[N_WALLS_MAX];
  const WALL_QUAD      *wquad;
  const WALL_QUAD_FAST *wqfast;
  int inter_deg;
} WB_DATA;

/* scalar factors */

#if DIM_OF_WORLD >= 1
/* <<< 1d */

static REAL wb_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_1D*lambda[1];
}

static REAL wb_phi_w1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_1D*lambda[0];
}

static const REAL *wb_grd_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = { 0.0, WEIGHT_1D, };
  return grd;
}

static const REAL *wb_grd_phi_w1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_B grd = { WEIGHT_1D, };
  return grd;
}

static const REAL_B *wb_D2_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;
  return D2;
}

static const REAL_B *wb_D2_phi_w1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;
  return D2;
}

/* >>> */
#endif

#if DIM_OF_WORLD >= 2
/* <<< 2d */

static REAL wb_phi_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[1]*lambda[2];
}

static REAL wb_phi_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[0]*lambda[2];
}

static REAL wb_phi_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[0]*lambda[1];
}

static const REAL *wb_grd_phi_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = WEIGHT_2D*lambda[2];
  grd[2] = WEIGHT_2D*lambda[1];

  return grd;
}

static const REAL *wb_grd_phi_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_2D*lambda[2];
  grd[2] = WEIGHT_2D*lambda[0];

  return grd;
}

static const REAL *wb_grd_phi_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_2D*lambda[1];
  grd[1] = WEIGHT_2D*lambda[0];

  return grd;
}

static const REAL_B *wb_D2_phi_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2 = {
    { 0.0, },
    { 0.0, 0.0, WEIGHT_2D, },
    { 0.0, WEIGHT_2D, 0.0, },
  };

  return D2;
}

static const REAL_B *wb_D2_phi_w1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2 = {
    { 0.0, 0.0, WEIGHT_2D, },
    { 0.0, },
    { WEIGHT_2D, },
  };

  return D2;
}

static const REAL_B *wb_D2_phi_w2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2 = {
    { 0.0, WEIGHT_2D, },
    { WEIGHT_2D, },
  };

  return D2;
}

/* >>> */
#endif

#if DIM_OF_WORLD >= 3
/* <<< 3d */

static REAL wb_phi_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[1]*lambda[2]*lambda[3];
}

static REAL wb_phi_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*lambda[2]*lambda[3];
}

static REAL wb_phi_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*lambda[1]*lambda[3];
}

static REAL wb_phi_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[0]*lambda[1]*lambda[2];
}

static const REAL *wb_grd_phi_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[1] = WEIGHT_3D*lambda[2]*lambda[3];
  grd[2] = WEIGHT_3D*lambda[1]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[1]*lambda[2];
  
  return grd;
}

static const REAL *wb_grd_phi_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*lambda[2]*lambda[3];
  grd[2] = WEIGHT_3D*lambda[0]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[0]*lambda[2];
  
  return grd;
}

static const REAL *wb_grd_phi_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*lambda[1]*lambda[3];
  grd[1] = WEIGHT_3D*lambda[0]*lambda[3];
  grd[3] = WEIGHT_3D*lambda[0]*lambda[1];
  
  return grd;
}

static const REAL *wb_grd_phi_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;
  
  grd[0] = WEIGHT_3D*lambda[1]*lambda[2];
  grd[1] = WEIGHT_3D*lambda[0]*lambda[2];
  grd[2] = WEIGHT_3D*lambda[0]*lambda[1];
  
  return grd;
}

static const REAL_B *wb_D2_phi_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[2][1] = D2[1][2] = WEIGHT_3D*lambda[3];
  D2[3][1] = D2[1][3] = WEIGHT_3D*lambda[2];
  D2[2][3] = D2[3][2] = WEIGHT_3D*lambda[1];

  return (const REAL_B *)D2;
}

static const REAL_B *wb_D2_phi_w1_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[2][0] = D2[0][2] = WEIGHT_3D*lambda[3];
  D2[3][0] = D2[0][3] = WEIGHT_3D*lambda[2];
  D2[2][3] = D2[3][2] = WEIGHT_3D*lambda[0];

  return (const REAL_B *)D2;
}

static const REAL_B *wb_D2_phi_w2_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[1][0] = D2[0][1] = WEIGHT_3D*lambda[3];
  D2[3][0] = D2[0][3] = WEIGHT_3D*lambda[1];
  D2[1][3] = D2[3][1] = WEIGHT_3D*lambda[0];

  return (const REAL_B *)D2;
}

static const REAL_B *wb_D2_phi_w3_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[1][0] = D2[0][1] = WEIGHT_3D*lambda[2];
  D2[2][0] = D2[0][2] = WEIGHT_3D*lambda[1];
  D2[1][2] = D2[2][1] = WEIGHT_3D*lambda[0];

  return (const REAL_B *)D2;
}

/* >>> */
#endif

#if DIM_OF_WORLD >= 0
static const REAL *
wb_phi_d_w0(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  WB_DATA *data = (WB_DATA *)bfcts->ext_data;

  return data->wall_normals[0];
}
#endif

#if DIM_OF_WORLD >= 1
static const REAL *
wb_phi_d_w1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  WB_DATA *data = (WB_DATA *)bfcts->ext_data;

  return data->wall_normals[1];
}
#endif

#if DIM_OF_WORLD >= 2
static const REAL *
wb_phi_d_w2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  WB_DATA *data = (WB_DATA *)bfcts->ext_data;

  return data->wall_normals[2];
}
#endif

#if DIM_OF_WORLD >= 3
static const REAL *
wb_phi_d_w3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  WB_DATA *data = (WB_DATA *)bfcts->ext_data;

  return data->wall_normals[3];
}
#endif

/* init_element() routines for vector-valued basis-functions: ALBERTA
 * assumes that the directional part is never constant, so the return
 * value should _ONLY_ reflect the status of the scalar factor.
 *
 * This version need FILL_NEIGH all the time. Maybe we should store
 * the wall normals globally, a suitable
 * refine_interpol()/coarse_restrict() routine could then be used to
 * update the global normals during mesh adaption. The normals would
 * be automatically oriented.
 */
static
INIT_EL_TAG wall_bubbles_init_element(const EL_INFO *el_info, void *thisptr)
{
  FUNCNAME("wall_bubbles_init_element");
  BAS_FCTS *self = (BAS_FCTS *)thisptr;
  WB_DATA *data = (WB_DATA *)self->ext_data;
  MESH *mesh;
  int w, dim;
  const EL_GEOM_CACHE *elgc;
  static bool neigh_warning = false;
  static bool coord_warning = false;

  if (el_info == NULL) {
    self->dir_pw_const = true;
    data->cur_el = NULL;
    data->cur_el_info = NULL;
    INIT_EL_TAG_CTX_DFLT(&self->tag_ctx);
    return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);
  }

  if (data->cur_el == el_info->el && data->cur_el_info == el_info) {
    return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
  }
  data->cur_el      = el_info->el;
  data->cur_el_info = el_info;
  
  mesh = el_info->mesh;
  dim = mesh->dim;

  if (mesh->parametric) {
    ERROR_EXIT("Not yet implemented for parametric meshes.\n");
  }

  if ((el_info->fill_flag & FILL_COORDS) == 0) {
    if (!coord_warning) {
      WARNING("FILL_COORDS not set, doing nothing.\n");
      coord_warning = true;
    }
    return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
  }

  elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_NORMALS);

  if (!neigh_warning && (el_info->fill_flag & FILL_NEIGH) == 0) {
    WARNING("Fill-flag FILL_NEIGH not set, unconditionally\n"
	    "using the outer normal.\n");
    for (w = 0; w < N_WALLS(dim); w++) {
      COPY_DOW(elgc->wall_normal[w], data->wall_normals[w]);
    }
    
    neigh_warning = true;
  } else for (w = 0; w < N_WALLS(dim); w++) {
    const EL *neigh;
    REAL orient = 1.0;

    if ((neigh = el_info->neigh[w]) != NULL) {
      int ov = el_info->opp_vertex[w];
      
      if (el_info->el->dof[w][0] < neigh->dof[ov][0]) {
	orient = -orient;
      }
    }
    AXEY_DOW(orient, elgc->wall_normal[w], data->wall_normals[w]);
  }

  return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
}

#undef DEF_EL_VEC_WALL_BUBBLE
#define DEF_EL_VEC_WALL_BUBBLE(type, name)				\
  DEF_EL_VEC_CONST(type, name, N_WALLS_MAX, N_WALLS_MAX)

#undef DEFUN_GET_EL_VEC_WALL_BUBBLE
#define DEFUN_GET_EL_VEC_WALL_BUBBLE(dim, name, type, admin, body, ...)	\
  static const EL_##type##_VEC *					\
  wall_bubbles_get_##name(type##_VEC_TYPE *vec, const EL *el, __VA_ARGS__) \
  {									\
    FUNCNAME("wall_bubble_get_"#name);					\
    static DEF_EL_VEC_WALL_BUBBLE(type, rvec_space);			\
    type##_VEC_TYPE *rvec = vec ? vec : rvec_space->vec;		\
    int n0, node, ibas, node_num;					\
    DOF **dofptr = el->dof, dof;					\
									\
    DEBUG_TEST_EXIT(true, "");						\
									\
    switch (dim) {							\
    case 0: node_num = VERTEX; break;					\
    case 1: node_num = VERTEX; break;					\
    case 2: node_num = EDGE; break;					\
    case 3: node_num = FACE; break;					\
    default:								\
      node_num = -1;							\
      ERROR_EXIT("Unsupport dimension: %d\n", dim);			\
      break;								\
    }									\
    node = (admin)->mesh->node[node_num];				\
    n0   = (admin)->n0_dof[node_num];					\
    for (ibas = 0; ibas < N_WALLS(dim); ibas++) {			\
      dof = dofptr[node+ibas][n0];					\
      body;								\
    }									\
									\
    if (vec) {								\
      return NULL;							\
    } else {								\
      rvec_space->n_components = N_WALLS(dim);				\
      return rvec_space;						\
    }									\
  }									\
  struct _AI_semicolon_dummy

#undef DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE
#define DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(name, type, ASSIGN)		\
  DEFUN_GET_EL_VEC_WALL_BUBBLE(dv->fe_space->admin->mesh->dim,		\
			       _##name##_vec, type, dv->fe_space->admin, \
			       ASSIGN(dv->vec[dof], rvec[ibas]),	\
			       const DOF_##type##_VEC *dv);		\
  static const EL_##type##_VEC *					\
  wall_bubbles_get_##name##_vec(type##_VEC_TYPE *vec, const EL *el,	\
				const DOF_##type##_VEC *dv)		\
  {									\
    EL_##type##_VEC *vec_loc = dv->vec_loc;				\
									\
    if (vec != NULL || vec_loc == NULL) {				\
      return wall_bubbles_get__##name##_vec(vec, el, dv);		\
    } else {								\
      wall_bubbles_get__##name##_vec(vec_loc->vec, el, dv);		\
      return vec_loc;							\
    }									\
  }									\
  struct _AI_semicolon_dummy


#undef COPY_EQ
#define COPY_EQ(a, b) (b) = (a)

/*******************************************************************************
 *  functions for combining basisfunctions with coefficients
 ******************************************************************************/

DEFUN_GET_EL_VEC_WALL_BUBBLE(thisptr->dim, dof_indices, DOF, admin,
			     rvec[ibas] = dof,
			     const DOF_ADMIN *admin, const BAS_FCTS *thisptr);

static const EL_BNDRY_VEC *
wall_bubbles_get_bound_1d(BNDRY_FLAGS *vec,
			  const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_WALL_BUBBLE(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w;

  for (w = 0; w < N_WALLS_1D; w++) {
    BNDRY_FLAGS_CPY(rvec[w], el_info->vertex_bound[1-w]);
  }

  return vec ? NULL : rvec_space;
}

#if DIM_MAX > 1
static const EL_BNDRY_VEC *
wall_bubbles_get_bound_2d(BNDRY_FLAGS *vec,
			  const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_WALL_BUBBLE(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w;

  for (w = 0; w < N_WALLS_2D; w++) {
    BNDRY_FLAGS_CPY(rvec[w], el_info->edge_bound[w]);
  }

  return vec ? NULL : rvec_space;
}
#endif

#if DIM_MAX > 2
static const EL_BNDRY_VEC *
wall_bubbles_get_bound_3d(BNDRY_FLAGS *vec,
			  const EL_INFO *el_info, const BAS_FCTS *thisptr)
{
  static DEF_EL_VEC_WALL_BUBBLE(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int w;

  for (w = 0; w < N_WALLS_3D; w++) {
    BNDRY_FLAGS_INIT(rvec[w]);
    BNDRY_FLAGS_SET(rvec[w], el_info->face_bound[w]);
  }

  return vec ? NULL : rvec_space;
}
#endif

/*******************************************************************************
 * function for accessing a local DOF_INT_VEC
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(int, INT, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_REAL_VEC            
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(real, REAL, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_REAL_D_VEC          
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(real_d, REAL_D, COPY_DOW);

/*******************************************************************************
 * function for accessing a local DOF_REAL_VEC_D          
 ******************************************************************************/

static const EL_REAL_VEC_D *
wall_bubbles_get_real_vec_d(REAL result[],
			    const EL *el, const DOF_REAL_VEC_D *dv)
{
  return (const EL_REAL_VEC_D *)
    wall_bubbles_get_real_vec(result, el, (const DOF_REAL_VEC *)dv);
}

/*******************************************************************************
 * function for accessing a local DOF_SCHAR_VEC           
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(schar, SCHAR, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_UCHAR_VEC           
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(uchar, UCHAR, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_PTR_VEC             
 ******************************************************************************/

DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(ptr, PTR, COPY_EQ);

/*******************************************************************************
 * function for accessing a local DOF_REAL_DD_VEC             
 ******************************************************************************/

#define MYMCOPY(a, b) MCOPY_DOW((const REAL_D *)(a), (b))
DEFUN_GET_EL_DOF_VEC_WALL_BUBBLE(real_dd, REAL_DD, MYMCOPY);

/*******************************************************************************
 * functions for interpolation:
 ******************************************************************************/

static void wall_bubbles_interpol_dow(EL_REAL_VEC_D *vec,
				      const EL_INFO *el_info, int wall,
				      int no, const int *b_no,
				      LOC_FCT_D_AT_QP f, void *f_data,
				      const BAS_FCTS *thisptr)
{
  int i, iq;
  WB_DATA              *data       = (WB_DATA *)thisptr->ext_data;
  const WALL_QUAD_FAST *wquad_fast;
  const QUAD_FAST      *qfast;
  REAL_D f_res;
  REAL_D uh_res;
  REAL_D diff;
  REAL   accu;

  if (data->wqfast->bas_fcts != thisptr) {
    data->wqfast = get_wall_quad_fast(thisptr, data->wquad, INIT_PHI);
    INIT_ELEMENT(el_info, thisptr);
  }
  wquad_fast = data->wqfast;

  if (wall >= 0) {
    if (b_no != NULL && no == 0) {
      return;
    }
    vec->vec[wall] = 0.0;
    qfast = wquad_fast->quad_fast[wall];
    INIT_ELEMENT(el_info, qfast);
    accu = 0.0;
    for (iq = 0; iq < qfast->n_points; iq++) {
      eval_uh_dow_fast(uh_res, vec, qfast, iq);
      f(f_res, el_info, qfast->quad, iq, f_data);
      AXPBY_DOW(1.0, f_res, -1.0, uh_res, diff);
      accu += qfast->w[iq] * SCP_DOW(diff, data->wall_normals[wall]);
    }
    vec->vec[wall] = accu;
  } else if (b_no) {
    for (i = 0; i < no; i++) {
      wall = b_no[i];
      vec->vec[wall] = 0.0;
      qfast = wquad_fast->quad_fast[wall];
      INIT_ELEMENT(el_info, qfast);
      accu = 0.0;
      for (iq = 0; iq < qfast->n_points; iq++) {
	eval_uh_dow_fast(uh_res, vec, qfast, iq);
	f(f_res, el_info, qfast->quad, iq, f_data);
	/*
	 * (int_ei(v*nu_i)-(I_h v)*nu_i)/int_ei(b)
	 */
	AXPBY_DOW(1.0, f_res, -1.0, uh_res, diff);
	accu += qfast->w[iq] * SCP_DOW(diff, data->wall_normals[wall]);
      }
      vec->vec[wall] = accu;
    }
  } else {
    for (wall = 0; wall < N_WALLS(thisptr->dim); wall++) {
      vec->vec[wall] = 0.0;
      qfast = wquad_fast->quad_fast[wall];
      INIT_ELEMENT(el_info, qfast);
      accu = 0.0;
      for (iq = 0; iq < qfast->n_points; iq++) {
	eval_uh_dow_fast(uh_res, vec, qfast, iq);
	f(f_res, el_info, qfast->quad, iq, f_data);
	/*
	 * (int_ei(v*nu_i)-(I_h v)*nu_i)/int_ei(b)
	 */
	AXPBY_DOW(1.0, f_res, -1.0, uh_res, diff);
	accu += qfast->w[iq] * SCP_DOW(diff, data->wall_normals[wall]);
      }
      vec->vec[wall] = accu;
    }
  }
}

static void wall_bubbles_interpol(EL_REAL_VEC *vec,
				  const EL_INFO *el_info, int wall,
				  int no, const int *b_no,
				  LOC_FCT_AT_QP f, void *f_data,
				  const BAS_FCTS *thisptr)
{
  int i, iq;
  WB_DATA              *data       = (WB_DATA *)thisptr->ext_data;
  const WALL_QUAD_FAST *wquad_fast;
  const QUAD_FAST      *qfast;
  REAL f_res;
  REAL uh_res;
  REAL accu;

  if (data->wqfast->bas_fcts != thisptr) {
    data->wqfast = get_wall_quad_fast(thisptr, data->wquad, INIT_PHI);
    INIT_ELEMENT(el_info, thisptr);
  }
  wquad_fast = data->wqfast;

  if (wall >= 0) {
    if (b_no != NULL && no == 0) {
      return;
    }
    vec->vec[wall] = 0.0;
    qfast = wquad_fast->quad_fast[wall];
    INIT_ELEMENT(el_info, qfast);
    accu = 0.0;
    for (iq = 0; iq < qfast->n_points; iq++) {
      uh_res = eval_uh_fast(vec, qfast, iq);
      f_res = f(el_info, qfast->quad, iq, f_data);
      accu += qfast->w[iq] * (f_res - uh_res);
    }
    vec->vec[wall] = accu;
  } else if (b_no) {
    for (i = 0; i < no; i++) {
      wall = b_no[i];
      vec->vec[wall] = 0.0;
      qfast = wquad_fast->quad_fast[wall];
      INIT_ELEMENT(el_info, qfast);
      accu = 0.0;
      for (iq = 0; iq < qfast->n_points; iq++) {
	uh_res = eval_uh_fast(vec, qfast, iq);
	f_res = f(el_info, qfast->quad, iq, f_data);
	/*
	 * (int_ei(v*nu_i)-(I_h v)*nu_i)/int_ei(b)
	 */
	accu += qfast->w[iq] * (f_res - uh_res);
      }
      vec->vec[wall] = accu;
    }
  } else {
    for (wall = 0; wall < N_WALLS(thisptr->dim); wall++) {
      vec->vec[wall] = 0.0;
      qfast = wquad_fast->quad_fast[wall];
      INIT_ELEMENT(el_info, qfast);
      accu = 0.0;
      for (iq = 0; iq < qfast->n_points; iq++) {
	uh_res = eval_uh_fast(vec, qfast, iq);
	f_res = f(el_info, qfast->quad, iq, f_data);
	/*
	 * (int_ei(v*nu_i)-(I_h v)*nu_i)/int_ei(b)
	 */
	accu += qfast->w[iq] * (f_res - uh_res);
      }
      vec->vec[wall] = accu;
    }
  }
}

static void wall_bubbles_real_refine_inter_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  const BAS_FCTS *self = drv->fe_space->bas_fcts;
  int node, n0, ichild;
  DOF cdof, pdof;
  EL *el;

  switch (self->dim) {
  case 1:
    node = drv->fe_space->admin->mesh->node[VERTEX];
    n0   = drv->fe_space->admin->n0_dof[VERTEX];
    el = rclist->el_info.el;
      
    for (ichild = 0; ichild < 2; ichild++) {
      pdof = el->dof[node+ichild][n0];
      cdof = el->child[ichild]->dof[node+ichild][n0];
      drv->vec[cdof] = drv->vec[pdof];
      cdof = el->child[ichild]->dof[node+1-ichild][n0];
      drv->vec[cdof] = 0.0;
    }
    break;
#if DIM_MAX > 1
  case 2: {
    REAL value;

    node = drv->fe_space->admin->mesh->node[EDGE];
    n0   = drv->fe_space->admin->n0_dof[EDGE];

    el = rclist->el_info.el;
    cdof = el->child[0]->dof[node+1][n0];
    drv->vec[cdof] = 0.0; /* interior edge */
    pdof = el->dof[node+2][n0];
    value = 0.5 * drv->vec[pdof];
    for (ichild = 0; ichild < 2; ichild++) {
      cdof = el->child[ichild]->dof[node+ichild][n0];
      drv->vec[cdof] = value;
    }
    if (n > 1) {
      el = rclist[1].el_info.el;
      cdof = el->child[0]->dof[node+1][n0];
      drv->vec[cdof] = 0.0; /* interior edge */
    }
    break;
  }
#endif
#if DIM_MAX > 2
  case 3: {
    REAL value;
    int i, j;
    
    node = drv->fe_space->admin->mesh->node[FACE];
    n0   = drv->fe_space->admin->n0_dof[FACE];
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      /* Interior wall */
      cdof = el->child[0]->dof[node+0][n0];
      drv->vec[cdof] = 0.0;
      /* all types, child 0:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       *
       * type 0, child 1:
       * wall 1 of child is on wall 3 of parent
       * wall 2 of child is on wall 2 of parent
       *
       * type 1, 2, child 1:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       */
      for (j = 2; j < 4; j++) {
	pdof = el->dof[node+j][n0];
	value = 0.5 * drv->vec[pdof];
	cdof = el->child[0]->dof[node+j-1][n0];
	drv->vec[cdof] = value;
	if (rclist[i].el_info.el_type > 0) {
	  cdof = el->child[1]->dof[node+j-1][n0];
	  drv->vec[cdof] = value;
	} else {
	  cdof = el->child[1]->dof[node+3-j+1][n0];
	  drv->vec[cdof] = value;
	}
      }
    }
    break;
  }
#endif
  default:
    break;
  }
}

static void wall_bubbles_real_coarse_inter_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  const BAS_FCTS *self = drv->fe_space->bas_fcts;
  int node, n0, ichild;
  DOF cdof, pdof;
  EL *el;

  switch (self->dim) {
  case 1:
    node = drv->fe_space->admin->mesh->node[VERTEX];
    n0   = drv->fe_space->admin->n0_dof[VERTEX];
    el = rclist->el_info.el;
      
    for (ichild = 0; ichild < 2; ichild++) {
      pdof = el->dof[node+ichild][n0];
      cdof = el->child[ichild]->dof[node+ichild][n0];
      drv->vec[pdof] = drv->vec[cdof];
    }
    break;
#if DIM_MAX > 1
  case 2: {
    REAL value;

    node = drv->fe_space->admin->mesh->node[EDGE];
    n0   = drv->fe_space->admin->n0_dof[EDGE];

    el = rclist->el_info.el;
    pdof = el->dof[node+2][n0];
    value = 0.0;
    for (ichild = 0; ichild < 2; ichild++) {
      cdof = el->child[ichild]->dof[node+ichild][n0];
      value += drv->vec[cdof];
    }
    drv->vec[pdof] = value;
    break;
  }
#endif
#if DIM_MAX > 2
  case 3: {
    REAL value;
    int i, j;
    
    node = drv->fe_space->admin->mesh->node[FACE];
    n0   = drv->fe_space->admin->n0_dof[FACE];
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;
      /* all types, child 0:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       *
       * type 0, child 1:
       * wall 1 of child is on wall 3 of parent
       * wall 2 of child is on wall 2 of parent
       *
       * type 1, 2, child 1:
       * wall 1 of child is on wall 2 of parent
       * wall 2 of child is on wall 3 of parent
       */
      for (j = 2; j < 4; j++) {
	pdof = el->dof[node+j][n0];
	value = 0.0;
	cdof = el->child[0]->dof[node+j-1][n0];
	value += drv->vec[cdof];
	if (rclist[i].el_info.el_type > 0) {
	  cdof = el->child[1]->dof[node+j-1][n0];
	  value += drv->vec[cdof];
	} else {
	  cdof = el->child[1]->dof[node+3-j+1][n0];
	  value += drv->vec[cdof];
	}
	drv->vec[pdof] = value;
      }
    }
    break;
  }
#endif
  default:
    break;
  }
}

static void wall_bubbles_real_coarse_restr_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  FUNCNAME("wall_bubbles_real_coarse_restr_d");

  WARNING("Not implemented.\n");
}

/*******************************************************************************
 * constructor for the beast:
 ******************************************************************************/

static const BAS_FCT wall_bubbles_phi[DIM_MAX+1][N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { wb_phi_w0_1d, wb_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { wb_phi_w0_2d, wb_phi_w1_2d, wb_phi_w2_2d, },
#endif
#if DIM_MAX >= 3
  { wb_phi_w0_3d, wb_phi_w1_3d, wb_phi_w2_3d, wb_phi_w3_3d, },
#endif
};

static const GRD_BAS_FCT wall_bubbles_grd_phi[DIM_MAX+1][N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { wb_grd_phi_w0_1d, wb_grd_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { wb_grd_phi_w0_2d, wb_grd_phi_w1_2d, wb_grd_phi_w2_2d, },
#endif
#if DIM_MAX >= 3
  { wb_grd_phi_w0_3d, wb_grd_phi_w1_3d, wb_grd_phi_w2_3d, wb_grd_phi_w3_3d, },
#endif
};

static const D2_BAS_FCT wall_bubbles_D2_phi[DIM_MAX+1][N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { wb_D2_phi_w0_1d, wb_D2_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { wb_D2_phi_w0_2d, wb_D2_phi_w1_2d, wb_D2_phi_w2_2d, },
#endif
#if DIM_MAX >= 3
  { wb_D2_phi_w0_3d, wb_D2_phi_w1_3d, wb_D2_phi_w2_3d, wb_D2_phi_w3_3d, },
#endif
};

static const BAS_FCT_D wall_bubbles_phi_d[N_WALLS_MAX] = {
#if DIM_MAX >= 1
  wb_phi_d_w0, wb_phi_d_w1,
#endif
#if DIM_MAX >= 2
 wb_phi_d_w2,
#endif
#if DIM_MAX >= 3
 wb_phi_d_w3,
#endif
};

static const int trace_mapping_wall_bubbles[N_WALLS_MAX][1] = {
  { 0 },
#if DIM_MAX > 0
  { 1 },
#endif
#if DIM_MAX > 1
  { 2 },
#endif
#if DIM_MAX > 2
  { 3 },
#endif
};

#define MAX_BUBBLE_INTER_DEG 20 /* Overkill */

const BAS_FCTS *get_wall_bubbles(unsigned int dim, unsigned int inter_deg)
{
  FUNCNAME("get_wall_bubbles");
  static BAS_FCTS *wb_bfcts[DIM_MAX+1][MAX_BUBBLE_INTER_DEG+1];
  WB_DATA *data;
  
  TEST_EXIT(dim <= DIM_MAX, "dim = %d > DIM_MAX = %d.\n", dim, DIM_MAX);
 
  if (inter_deg > MAX_BUBBLE_INTER_DEG) {
    WARNING("Truncating quad-degree from %d to %d.\n",
	    inter_deg, MAX_BUBBLE_INTER_DEG);
    inter_deg = MAX_BUBBLE_INTER_DEG;
  }
  
  if (wb_bfcts[inter_deg][dim] == NULL) {
    char name[sizeof("WallBubbles_IXX_Xd")];

    sprintf(name, "WallBubbles_I%02d_%dd", inter_deg, dim);

    wb_bfcts[dim][inter_deg] = MEM_CALLOC(1, BAS_FCTS);
    wb_bfcts[dim][inter_deg]->name   = strdup(name);
    wb_bfcts[dim][inter_deg]->dim    = dim;
    wb_bfcts[dim][inter_deg]->rdim   = DIM_OF_WORLD;
    wb_bfcts[dim][inter_deg]->degree = N_LAMBDA(dim-1);
    wb_bfcts[dim][inter_deg]->n_bas_fcts =
      wb_bfcts[dim][inter_deg]->n_bas_fcts_max = N_WALLS(dim);
    switch (dim) {
    case 1: wb_bfcts[dim][inter_deg]->n_dof[VERTEX] = 1; break;
    case 2: wb_bfcts[dim][inter_deg]->n_dof[EDGE] = 1; break;
    case 3: wb_bfcts[dim][inter_deg]->n_dof[FACE] = 1; break;
    }
    wb_bfcts[dim][inter_deg]->trace_admin = -1;
    CHAIN_INIT(wb_bfcts[dim][inter_deg]);
    wb_bfcts[dim][inter_deg]->unchained = wb_bfcts[dim][inter_deg];
    wb_bfcts[dim][inter_deg]->phi     = wall_bubbles_phi[dim];
    wb_bfcts[dim][inter_deg]->grd_phi = wall_bubbles_grd_phi[dim];
    wb_bfcts[dim][inter_deg]->D2_phi  = wall_bubbles_D2_phi[dim];
    wb_bfcts[dim][inter_deg]->phi_d   = wall_bubbles_phi_d;
    if (dim > 0) {
      int o, t, w;
      wb_bfcts[dim][inter_deg]->trace_bas_fcts =
	get_trace_bubble(dim-1, inter_deg);
      for (w = 0; w < N_WALLS(dim); w++) {
	wb_bfcts[dim][inter_deg]->n_trace_bas_fcts[w] = 1;
	for (t = 0; t < 2; t++) {
	  for (o = 0; o < 2; o++) {
	    wb_bfcts[dim][inter_deg]->trace_dof_map[t][o][w] =
	      trace_mapping_wall_bubbles[w];
	  }
	}
      }
    } else {
      wb_bfcts[dim][inter_deg]->trace_bas_fcts = get_null_bfcts(0);
    }
    wb_bfcts[dim][inter_deg]->get_dof_indices = wall_bubbles_get_dof_indices;
    switch (dim) {
    case 1:
      wb_bfcts[dim][inter_deg]->get_bound = wall_bubbles_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      wb_bfcts[dim][inter_deg]->get_bound = wall_bubbles_get_bound_2d;
      break;
#endif
#if DIM_MAX > 2
    case 3:
      wb_bfcts[dim][inter_deg]->get_bound = wall_bubbles_get_bound_3d;
      break;
#endif
    }
    wb_bfcts[dim][inter_deg]->interpol = wall_bubbles_interpol;
    wb_bfcts[dim][inter_deg]->interpol_d = NULL;
    wb_bfcts[dim][inter_deg]->interpol_dow = wall_bubbles_interpol_dow;
    wb_bfcts[dim][inter_deg]->dir_pw_const = true;

    wb_bfcts[dim][inter_deg]->get_int_vec     = wall_bubbles_get_int_vec;
    wb_bfcts[dim][inter_deg]->get_real_vec    = wall_bubbles_get_real_vec;
    wb_bfcts[dim][inter_deg]->get_real_d_vec  = wall_bubbles_get_real_d_vec;
    wb_bfcts[dim][inter_deg]->get_real_dd_vec = wall_bubbles_get_real_dd_vec;
    wb_bfcts[dim][inter_deg]->get_real_vec_d  = wall_bubbles_get_real_vec_d;
    wb_bfcts[dim][inter_deg]->get_uchar_vec   = wall_bubbles_get_uchar_vec;
    wb_bfcts[dim][inter_deg]->get_schar_vec   = wall_bubbles_get_schar_vec;
    wb_bfcts[dim][inter_deg]->get_ptr_vec     = wall_bubbles_get_ptr_vec;

    wb_bfcts[dim][inter_deg]->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      wall_bubbles_real_refine_inter_d;
    wb_bfcts[dim][inter_deg]->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      wall_bubbles_real_coarse_inter_d;
    wb_bfcts[dim][inter_deg]->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      wall_bubbles_real_coarse_restr_d;

    wb_bfcts[dim][inter_deg]->real_refine_inter_d =
      wall_bubbles_real_refine_inter_d;
    wb_bfcts[dim][inter_deg]->real_coarse_inter_d =
      wall_bubbles_real_coarse_inter_d;
    wb_bfcts[dim][inter_deg]->real_coarse_restr_d =
      wall_bubbles_real_coarse_restr_d;

    data = wb_bfcts[dim][inter_deg]->ext_data = MEM_CALLOC(1, WB_DATA);

    INIT_ELEMENT_DEFUN(wb_bfcts[dim][inter_deg], wall_bubbles_init_element,
		       FILL_NEIGH|FILL_COORDS);
    INIT_OBJECT(wb_bfcts[dim][inter_deg]);

    data->wquad  = get_wall_quad(dim, inter_deg);
    data->inter_deg = inter_deg;
    data->wqfast =
      get_wall_quad_fast(wb_bfcts[dim][inter_deg], data->wquad, INIT_PHI);
  }
  
  return wb_bfcts[dim][inter_deg];
}

