/**@file 
 *
 * Face-Bubble basis functions, meant to be chained with some standard
 * Lagrange space, optionally with divergence preserving interpolation
 * routine. This variant is a fancy space with DOFs defined on an
 * admin belonging to a trace mesh. We assume that the mapping between
 * bulk-elements and trace-elements is 1-1, i.e. at most one face of a
 * bulk element belongs to the trace mesh.
 *
 *      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-2009).
 *
 */

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

#include <alberta.h>

#include "albas.h"

/* The weights are chosen s.t. the integral over the faces of the
 * reference simplex is 1.
 */
#define WEIGHT_1D 1.0
#define WEIGHT_2D 6.0
#define WEIGHT_3D 120.0

typedef struct btb_data
{
  const EL_INFO        *cur_el_info;
  const EL             *cur_el;
  const EL             *cur_trace_el[N_WALLS_MAX];
  int                  cur_wall[N_WALLS_MAX];
  MESH                 *trace_mesh;
  int                  trace_id;
  REAL_D               wall_normal[N_WALLS_MAX];
  BAS_FCT              phi[N_WALLS_MAX];
  GRD_BAS_FCT          grd_phi[N_WALLS_MAX];
  D2_BAS_FCT           D2_phi[N_WALLS_MAX];
  BAS_FCT_D            phi_d[N_WALLS_MAX];
  int                  trace_dof_map[N_WALLS_MAX];
  const WALL_QUAD      *wquad;
  const WALL_QUAD_FAST *wqfast;
  int                  inter_deg;
} BTB_DATA;

/* scalar factors */

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

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

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

static const REAL *btb_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 *btb_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 *btb_D2_phi_w0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static const REAL_BB D2;
  return D2;
}

static const REAL_B *btb_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 btb_phi_w0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_2D*lambda[1]*lambda[2];
}

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

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

static const REAL *btb_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 *btb_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 *btb_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 *btb_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 *btb_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 *btb_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 btb_phi_w0_3d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_3D*lambda[1]*lambda[2]*lambda[3];
}

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

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

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

static const REAL *btb_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 *btb_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 *btb_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 *btb_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 *btb_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 *btb_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 *btb_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 *btb_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

static const REAL *
btb_phi_d_0(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  BTB_DATA *data = (BTB_DATA *)bfcts->ext_data;

  return data->wall_normal[0];
}

static const REAL *
btb_phi_d_1(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  BTB_DATA *data = (BTB_DATA *)bfcts->ext_data;

  return data->wall_normal[1];
}

#if DIM_MAX >= 2
static const REAL *
btb_phi_d_2(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  BTB_DATA *data = (BTB_DATA *)bfcts->ext_data;

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

#if DIM_MAX >= 3
static const REAL *
btb_phi_d_3(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  BTB_DATA *data = (BTB_DATA *)bfcts->ext_data;

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

static const BAS_FCT btb_phi[DIM_MAX+1][N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { btb_phi_w0_1d, btb_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { btb_phi_w0_2d, btb_phi_w1_2d, btb_phi_w2_2d, },
#endif
#if DIM_MAX >= 3
  { btb_phi_w0_3d, btb_phi_w1_3d, btb_phi_w2_3d, btb_phi_w3_3d, },
#endif
};

static const GRD_BAS_FCT btb_grd_phi[DIM_MAX+1][N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { btb_grd_phi_w0_1d, btb_grd_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { btb_grd_phi_w0_2d, btb_grd_phi_w1_2d, btb_grd_phi_w2_2d, },
#endif
#if DIM_MAX >= 3
  { btb_grd_phi_w0_3d, btb_grd_phi_w1_3d, btb_grd_phi_w2_3d, btb_grd_phi_w3_3d, },
#endif
};

static const D2_BAS_FCT btb_D2_phi[DIM_MAX+1][N_WALLS_MAX] = {
  { NULL, /* 0 dim */ },
#if DIM_MAX >= 1
  { btb_D2_phi_w0_1d, btb_D2_phi_w1_1d, },
#endif
#if DIM_MAX >= 2
  { btb_D2_phi_w0_2d, btb_D2_phi_w1_2d, btb_D2_phi_w2_2d, },
#endif
#if DIM_MAX >= 3
  { btb_D2_phi_w0_3d, btb_D2_phi_w1_3d, btb_D2_phi_w2_3d, btb_D2_phi_w3_3d, },
#endif
};

static const BAS_FCT_D btb_phi_d[] = {
  btb_phi_d_0, btb_phi_d_1,
#if DIM_MAX >= 2
  btb_phi_d_2,
# if DIM_MAX >= 3
  btb_phi_d_3
# endif
#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 btb_init_element(const EL_INFO *el_info, void *vself)
{
  FUNCNAME("btb_init_element");
  BAS_FCTS *self = (BAS_FCTS *)vself;
  BTB_DATA *data = (BTB_DATA *)self->ext_data;
  MESH *mesh;
  int w, dim, n_bas;
  const EL_GEOM_CACHE *elgc;
  static bool coord_warning = false;

  if (el_info == NULL) {
    data->cur_el = NULL;
    data->cur_el_info = NULL;

    self->dir_pw_const = true;
    self->n_bas_fcts = 0;
    for (w = 0; w < N_WALLS_MAX; w++) {
      self->n_trace_bas_fcts[w] =
	((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 0;
    }
    memset(data->cur_wall, -1, sizeof(data->cur_wall));
    memset(data->cur_trace_el, 0, sizeof(data->cur_trace_el));
    self->n_bas_fcts =
      ((BAS_FCTS *)self->unchained)->n_bas_fcts = 0;

    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 (data->trace_mesh == NULL) {
    data->trace_mesh = lookup_submesh_by_id(mesh, data->trace_id);
    TEST_EXIT(data->trace_mesh != NULL,
	      "No trace-mesh with id %d\n", data->trace_id);
  }

  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);    
  }

  /* Determine whether one of the faces belongs to our dedicated
   * trace-mesh.
   */
  for (n_bas = w = 0; w < N_WALLS(dim); w++) {
    const EL *tr_el;

    if ((tr_el = get_slave_el(el_info->el, w, data->trace_mesh)) == NULL) {
      self->n_trace_bas_fcts[w] =
	((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 0;
      data->cur_wall[n_bas] = -1;
      continue;
    }
    data->cur_trace_el[n_bas] = tr_el;
    data->cur_wall[n_bas]     = w;
    elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_NORMAL(w));
    COPY_DOW(elgc->wall_normal[w], data->wall_normal[n_bas]);

    data->phi[n_bas]     = btb_phi[dim][w];
    data->grd_phi[n_bas] = btb_grd_phi[dim][w];
    data->D2_phi[n_bas]  = btb_D2_phi[dim][w];
    data->phi_d[n_bas]   = btb_phi_d[n_bas];

    self->n_trace_bas_fcts[w] =
      ((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 1;

    data->trace_dof_map[w] = n_bas;
    
    ++n_bas;
  }

  if (n_bas == 0) {
    /* The default (not NULL) case, actually */
    if (INIT_EL_TAG_CTX_TAG(&self->tag_ctx) != INIT_EL_TAG_DFLT) {
      for (w = 0; w < N_WALLS(dim); w++) {
	self->n_trace_bas_fcts[w] =
	  ((BAS_FCTS *)self->unchained)->n_trace_bas_fcts[w] = 0;
      }
      memset(data->cur_wall, -1, sizeof(data->cur_wall));
      memset(data->cur_trace_el, 0, sizeof(data->cur_trace_el));
      self->n_bas_fcts =
	((BAS_FCTS *)self->unchained)->n_bas_fcts = 0;
    }
    INIT_EL_TAG_CTX_DFLT(&self->tag_ctx);
  } else {
    self->n_bas_fcts =
      ((BAS_FCTS *)self->unchained)->n_bas_fcts = n_bas;

    INIT_EL_TAG_CTX_UNIQ(&self->tag_ctx);
  }

  return INIT_EL_TAG_CTX_TAG(&self->tag_ctx);    
}

#undef DEF_EL_VEC_BTB
#define DEF_EL_VEC_BTB(type, name)		\
  DEF_EL_VEC_CONST(type, name, 1, N_WALLS_MAX)

#undef DEFUN_GET_EL_VEC_BTB
#define DEFUN_GET_EL_VEC_BTB(dim, name, type, admin, self, body, ...)	\
  static const EL_##type##_VEC *					\
  btb_get_##name(type##_VEC_TYPE *vec, const EL *el, __VA_ARGS__)	\
  {									\
    FUNCNAME("bulk_trace_bubble_get_"#name);				\
    static DEF_EL_VEC_BTB(type, rvec_space);				\
    type##_VEC_TYPE *rvec = vec ? vec : rvec_space->vec;		\
    BTB_DATA *data = (BTB_DATA *)self->ext_data;			\
    int n0, node, i;							\
									\
    DEBUG_TEST_EXIT(true, "");						\
									\
    node = (admin)->mesh->node[CENTER];					\
    n0   = (admin)->n0_dof[CENTER];					\
    for (i = 0; i < self->n_bas_fcts; i++) {				\
      DOF **dofptr   = data->cur_trace_el[i]->dof, dof;			\
									\
      dof  = dofptr[node][n0];						\
      body;								\
    }									\
									\
    if (vec) {								\
      return NULL;							\
    } else {								\
      rvec_space->n_components = self->n_bas_fcts;			\
      return rvec_space;						\
    }									\
  }									\
  struct _AI_semicolon_dummy

#undef DEFUN_GET_EL_DOF_VEC_BTB
#define DEFUN_GET_EL_DOF_VEC_BTB(name, type, ASSIGN)			\
  DEFUN_GET_EL_VEC_BTB(dv->fe_space->admin->mesh->dim,			\
		       _##name##_vec, type, dv->fe_space->admin,	\
		       dv->fe_space->bas_fcts,				\
		       ASSIGN(dv->vec[dof], rvec[i]),			\
		       const DOF_##type##_VEC *dv);			\
  static const EL_##type##_VEC *					\
  btb_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 btb_get__##name##_vec(vec, el, dv);			\
    } else {								\
      btb_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_BTB(self->dim, dof_indices, DOF, admin, self,
		     rvec[i] = dof,
		     const DOF_ADMIN *admin, const BAS_FCTS *self);

static const EL_BNDRY_VEC *
btb_get_bound_1d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_BTB(BNDRY, rvec_space);
  BTB_DATA    *data = (BTB_DATA *)self->ext_data;
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int i;

  for (i = 0; i < self->n_bas_fcts; i++) {
    int wall = data->cur_wall[i];
    BNDRY_FLAGS_CPY(rvec[i], el_info->vertex_bound[1-wall]);
  }

  return vec ? NULL : rvec_space;
}

#if DIM_OF_WORLD > 1
static const EL_BNDRY_VEC *
btb_get_bound_2d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_BTB(BNDRY, rvec_space);
  BTB_DATA    *data = (BTB_DATA *)self->ext_data;
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int i;

  for (i = 0; i < self->n_bas_fcts; i++) {
    int wall = data->cur_wall[i];
    BNDRY_FLAGS_CPY(rvec[i], el_info->edge_bound[wall]);
  }

  return vec ? NULL : rvec_space;
}
#endif

#if DIM_OF_WORLD > 2
static const EL_BNDRY_VEC *
btb_get_bound_3d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_BTB(BNDRY, rvec_space);
  BTB_DATA    *data = (BTB_DATA *)self->ext_data;
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;
  int i;

  for (i = 0; i < self->n_bas_fcts; i++) {
    int wall = data->cur_wall[i];
    BNDRY_FLAGS_INIT(rvec[i]);
    BNDRY_FLAGS_SET(rvec[i], el_info->face_bound[wall]);
  }

  return vec ? NULL : rvec_space;
}
#endif

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

DEFUN_GET_EL_DOF_VEC_BTB(int, INT, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTB(real, REAL, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTB(real_d, REAL_D, COPY_DOW);

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

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

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

DEFUN_GET_EL_DOF_VEC_BTB(schar, SCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTB(uchar, UCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_BTB(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_BTB(real_dd, REAL_DD, MYMCOPY);

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

static void btb_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 *self)
{
  int i, iq;
  BTB_DATA             *data = (BTB_DATA *)self->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;

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

  if (wall >= 0) {
    for (i = 0; i < self->n_bas_fcts; i++) {
      if (wall != data->cur_wall[i]) {
	continue;
      }
      if (b_no && *b_no != i) {
	continue;
      }
      vec->vec[i] = 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_normal[i]);
      }
      vec->vec[i] = accu;
    }
  } else if (b_no) {
    for (i = 0; i < no; i++) {
      int b = b_no[i];

      DEBUG_TEST_EXIT(b < self->n_bas_fcts,
		      "not so many basis functions (%d), only %d\n",
		      b, self->n_bas_fcts);

      wall = data->cur_wall[b];
      vec->vec[b] = 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_normal[b]);
      }
      vec->vec[b] = accu;
    }
  } else {
    for (i = 0; i < self->n_bas_fcts; i++) {
      wall = data->cur_wall[i];
      vec->vec[i] = 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_normal[i]);
      }
      vec->vec[i] = accu;
    }
  }
}

static void btb_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 *self)
{
  int i, iq;
  BTB_DATA             *data = (BTB_DATA *)self->ext_data;
  const WALL_QUAD_FAST *wquad_fast;
  const QUAD_FAST      *qfast;
  REAL f_res;
  REAL uh_res;
  REAL accu;

  vec->n_components = self->n_bas_fcts;

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

  if (wall >= 0) {
    for (i = 0; i < self->n_bas_fcts; i++) {
      if (wall != data->cur_wall[i]) {
	continue;
      }
      if (b_no && *b_no != i) {
	continue;
      }
      vec->vec[i] = 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[i] = accu;
    }
  } else if (b_no) {
    for (i = 0; i < no; i++) {
      int b = b_no[i];

      DEBUG_TEST_EXIT(b < self->n_bas_fcts,
		      "not so many basis functions (%d), only %d\n",
		      b, self->n_bas_fcts);

      wall = data->cur_wall[b];
      vec->vec[b] = 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[b] = accu;
    }
  } else {
    for (i = 0; i < self->n_bas_fcts; i++) {
      wall = data->cur_wall[i];
      vec->vec[i] = 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[i] = accu;
    }
  }
}

/* Called on the trace mesh */
static void btb_real_refine_inter_d(DOF_REAL_VEC_D *drv,
				    RC_LIST_EL *rclist,
				    int n)
{
  int node, n0;
  DOF cdof, pdof;
  EL *el;
  int i;

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

  for (i = 0; i < n; i++) {
    el = rclist[i].el_info.el;
    pdof = el->dof[node][n0];
    cdof = el->child[0]->dof[node][n0];
    drv->vec[cdof] = 0.5 * drv->vec[pdof];
    cdof = el->child[1]->dof[node][n0];
    drv->vec[cdof] = 0.5 * drv->vec[pdof];
  }
}

/* Called on the trace mesh */
static void btb_real_coarse_inter_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  int node, n0;
  DOF cdof, pdof;
  EL *el; 
  REAL value;
  int i;

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

  for (i = 0; i < n; i++) {
    el = rclist[i].el_info.el;
    pdof = el->dof[node][n0];
    cdof = el->child[0]->dof[node][n0];
    value = drv->vec[cdof];
    cdof = el->child[1]->dof[node][n0];
    value += drv->vec[cdof];
    drv->vec[pdof] = value;
  }
}

/* Called on the trace mesh */
static void btb_real_coarse_restr_d(DOF_REAL_VEC_D *drv,
					     RC_LIST_EL *rclist,
					     int n)
{
  FUNCNAME("btb_real_coarse_restr_d");

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

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


#define MAX_BUBBLE_INTER_DEG 20 /* Overkill */

const BAS_FCTS *get_bulk_trace_bubble(unsigned int dim,
				      unsigned int inter_deg,
				      int trace_id)
{
  FUNCNAME("get_bulk_trace_bubble");
  static BAS_FCTS *btb_bfcts[DIM_MAX+1][MAX_BUBBLE_INTER_DEG+1];
  BTB_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 (btb_bfcts[inter_deg][dim] == NULL) {
    char name[sizeof("BulkTraceBubble@XX_IXX_Xd")];

    sprintf(name, "BulkTraceBubble@%02d_I%02d_%dd",
	    trace_id, inter_deg, dim);

    btb_bfcts[dim][inter_deg] = MEM_CALLOC(1, BAS_FCTS);

    data = btb_bfcts[dim][inter_deg]->ext_data = MEM_CALLOC(1, BTB_DATA);

    btb_bfcts[dim][inter_deg]->name   = strdup(name);
    btb_bfcts[dim][inter_deg]->dim    = dim;
    btb_bfcts[dim][inter_deg]->rdim   = DIM_OF_WORLD;
    btb_bfcts[dim][inter_deg]->degree = N_LAMBDA(dim-1);
    btb_bfcts[dim][inter_deg]->n_bas_fcts = 0;
    btb_bfcts[dim][inter_deg]->n_bas_fcts_max = N_WALLS(dim);
    btb_bfcts[dim][inter_deg]->n_dof[CENTER] = 1; /* trace mesh admin */
    btb_bfcts[dim][inter_deg]->trace_admin = trace_id;
    CHAIN_INIT(btb_bfcts[dim][inter_deg]);
    btb_bfcts[dim][inter_deg]->unchained = btb_bfcts[dim][inter_deg];
    btb_bfcts[dim][inter_deg]->phi     = data->phi;
    btb_bfcts[dim][inter_deg]->grd_phi = data->grd_phi;
    btb_bfcts[dim][inter_deg]->D2_phi  = data->D2_phi;
    btb_bfcts[dim][inter_deg]->phi_d   = data->phi_d;
    if (dim > 0) {
      int o, t, w;
      btb_bfcts[dim][inter_deg]->trace_bas_fcts =
	get_trace_bubble(dim-1, inter_deg);
      for (w = 0; w < N_WALLS(dim); w++) {
	btb_bfcts[dim][inter_deg]->n_trace_bas_fcts[w] = 1;
	for (t = 0; t < 2; t++) {
	  for (o = 0; o < 2; o++) {
	    btb_bfcts[dim][inter_deg]->trace_dof_map[t][o][w] =
	      data->trace_dof_map + w;
	  }
	}
      }
    } else {
      btb_bfcts[dim][inter_deg]->trace_bas_fcts = get_null_bfcts(0);
    }
    btb_bfcts[dim][inter_deg]->get_dof_indices = btb_get_dof_indices;
    switch (dim) {
    case 1:
      btb_bfcts[dim][inter_deg]->get_bound = btb_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      btb_bfcts[dim][inter_deg]->get_bound = btb_get_bound_2d;
      break;
#endif
#if DIM_MAX > 2
    case 3:
      btb_bfcts[dim][inter_deg]->get_bound = btb_get_bound_3d;
      break;
#endif
    }
    btb_bfcts[dim][inter_deg]->interpol = btb_interpol;
    btb_bfcts[dim][inter_deg]->interpol_d = NULL;
    btb_bfcts[dim][inter_deg]->interpol_dow = btb_interpol_dow;
    btb_bfcts[dim][inter_deg]->dir_pw_const = true;

    btb_bfcts[dim][inter_deg]->get_int_vec     = btb_get_int_vec;
    btb_bfcts[dim][inter_deg]->get_real_vec    = btb_get_real_vec;
    btb_bfcts[dim][inter_deg]->get_real_d_vec  = btb_get_real_d_vec;
    btb_bfcts[dim][inter_deg]->get_real_dd_vec = btb_get_real_dd_vec;
    btb_bfcts[dim][inter_deg]->get_real_vec_d  = btb_get_real_vec_d;
    btb_bfcts[dim][inter_deg]->get_uchar_vec   = btb_get_uchar_vec;
    btb_bfcts[dim][inter_deg]->get_schar_vec   = btb_get_schar_vec;
    btb_bfcts[dim][inter_deg]->get_ptr_vec     = btb_get_ptr_vec;

    btb_bfcts[dim][inter_deg]->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      btb_real_refine_inter_d;
    btb_bfcts[dim][inter_deg]->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      btb_real_coarse_inter_d;
    btb_bfcts[dim][inter_deg]->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      btb_real_coarse_restr_d;

    btb_bfcts[dim][inter_deg]->real_refine_inter_d =
      btb_real_refine_inter_d;
    btb_bfcts[dim][inter_deg]->real_coarse_inter_d =
      btb_real_coarse_inter_d;
    btb_bfcts[dim][inter_deg]->real_coarse_restr_d =
      btb_real_coarse_restr_d;

    INIT_ELEMENT_DEFUN(btb_bfcts[dim][inter_deg], btb_init_element,
		       FILL_NEIGH|FILL_COORDS);
    INIT_OBJECT(btb_bfcts[dim][inter_deg]);

    data->trace_mesh = NULL;
    data->trace_id = trace_id;
    data->wquad  = get_wall_quad(dim, inter_deg);
    data->inter_deg = inter_deg;
    data->wqfast =
      get_wall_quad_fast(btb_bfcts[dim][inter_deg], data->wquad, INIT_PHI);
  }
  
  return btb_bfcts[dim][inter_deg];
}

