/**@file 
 *
 * The trace-space for the tensor face bubbles (aka wall
 * bubbles). This space has to live on a slave mesh in order to get
 * access to the co-normal.
 *
 *      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/alberta.h>

#include "albas.h"

#include "alberta_intern.h"

/* Define _PROPER_ weights for the basis functions, s.t. their _mass_
 * is normalized.
 */
#define WEIGHT_0D 1.0
#define WEIGHT_1D 6.0
#define WEIGHT_2D 120.0

#define N_TTB_MAX (N_VERTICES(DIM_LIMIT-1))

/* Mass-matrices and inverse on the reference element, used to define
 * the interpolation operator.
 *
 * We define the interpolation by an L2-projection w.r.t. to the
 * Lagrange-factor of the tensor space, where the mass-matrix is
 * weighted by the bubble. The effect is that the flux of the
 * interpolant across the boundary of a mesh element is the same as
 * the flux of the interpolated function across the boundary.
 */
#if 0
static
REAL mass[DIM_MAX][N_VERTICES(DIM_MAX-1)][N_VERTICES(DIM_MAX-1)] = {
  { { 1.0, } },
# if DIM_MAX > 1
  { { 3.0 / 10.0, 1.0 / 5.0, },
    { 1.0 / 5.0, 3.0 / 10.0, }, },
# endif
# if DIM_MAX > 2
  { { 1.0 / 7.0, 2.0 / 21.0, 2.0 / 21.0 },
    { 2.0 / 21.0, 1.0 / 7.0, 2.0 / 21.0 },
    { 2.0 / 21.0, 2.0 / 21.0, 1.0 / 7.0 } },
# endif
};
#endif
static
REAL mass_inv[DIM_MAX][N_VERTICES(DIM_MAX-1)][N_VERTICES(DIM_MAX-1)] = {
  { { 1.0, }, },
#if DIM_MAX > 1
  { { 6.0, -4.0, },
    { -4.0, 6.0, }, },
#endif
#if DIM_MAX > 2
  { { 15.0, -6.0, -6.0 },
    { -6.0, 15.0, -6.0 },
    { -6.0, -6.0, 15.0 }, },
#endif 
};

typedef struct ttb_data
{
  const EL        *cur_el;
  REAL_D          el_co_normal;
  INIT_EL_TAG_CTX tag_ctx; /**< Tag context for caching purposes. */
  const QUAD      *quad;   /**< For interpolation. */
  const QUAD_FAST *qfast;
  int             inter_deg;
} TTB_DATA;

/* <<< values */

/* <<< 0d */

static REAL phic_0d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  return WEIGHT_0D;
}

/* >>> */

/* <<< 1d */

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

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

/* >>> */

/* <<< 2d */

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

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

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

/* >>> */

/* >>> */

/* <<< gradients */

/* <<< 0d */

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

/* >>> */

/* <<< 1d */

static const REAL *grd_phic_0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

  grd[0] = 2.0*WEIGHT_1D*lambda[0]*lambda[1];
  grd[1] = WEIGHT_1D*SQR(lambda[0]);

  return (const REAL *)grd;
}

static const REAL *grd_phic_1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

  grd[0] = WEIGHT_1D*SQR(lambda[1]);
  grd[1] = 2.0*WEIGHT_1D*lambda[0]*lambda[1];

  return (const REAL *)grd;
}

/* >>> */

/* <<< 2d */

static const REAL *grd_phic_0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

#if DIM_MAX > 1
  grd[0] = 2.0*WEIGHT_2D*lambda[0]*lambda[1]*lambda[2];
  grd[1] = WEIGHT_2D*SQR(lambda[0])*lambda[2];
  grd[2] = WEIGHT_2D*SQR(lambda[0])*lambda[1];
#endif

  return (const REAL *)grd;
}

static const REAL *grd_phic_1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

#if DIM_MAX > 1
  grd[0] = WEIGHT_2D*SQR(lambda[1])*lambda[2];
  grd[1] = 2.0*WEIGHT_2D*lambda[0]*lambda[1]*lambda[2];
  grd[2] = WEIGHT_2D*lambda[0]*SQR(lambda[1]);
#endif

  return (const REAL *)grd;
}

static const REAL *grd_phic_2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_B grd;

#if DIM_MAX > 1
  grd[0] = WEIGHT_2D*lambda[1]*SQR(lambda[2]);
  grd[1] = WEIGHT_2D*lambda[0]*SQR(lambda[2]);
  grd[2] = 2.0*WEIGHT_2D*lambda[0]*lambda[1]*lambda[2];
#endif

  return (const REAL *)grd;
}

/* >>> */

/* >>> */

/* <<< hessians */

/* <<< 0d */

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

/* >>> */

/* <<< 1d */

static const REAL_B *D2_phic_0_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;
  
  D2[0][0] = 2.0*WEIGHT_1D*lambda[1];
  D2[0][1] = D2[1][0] = 2.0*WEIGHT_1D*lambda[0];

  return (const REAL_B *)D2;
}

static const REAL_B *D2_phic_1_1d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;

  D2[1][1] = 2.0*WEIGHT_1D*lambda[0];
  D2[0][1] = D2[1][0] = 2.0*WEIGHT_1D*lambda[1];
  
  return (const REAL_B *)D2;
}

/* >>> */

/* <<< 2d */

static const REAL_B *D2_phic_0_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;

#if DIM_MAX > 1
  D2[0][0] = 2.0*WEIGHT_2D*lambda[1]*lambda[2];
  D2[0][1] = D2[1][0] = 2.0*WEIGHT_2D*lambda[0]*lambda[2];
  D2[0][2] = D2[2][0] = 2.0*WEIGHT_2D*lambda[0]*lambda[1];
  D2[1][2] = D2[2][1] = WEIGHT_2D*SQR(lambda[0]);
#endif

  return (const REAL_B *)D2;
}

static const REAL_B *D2_phic_1_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;

#if DIM_MAX > 1
  D2[1][1] = 2.0*WEIGHT_2D*lambda[0]*lambda[2];
  D2[0][1] = D2[1][0] = 2.0*WEIGHT_2D*lambda[1]*lambda[2];
  D2[0][2] = D2[2][0] = WEIGHT_2D*SQR(lambda[1]);
  D2[1][2] = D2[2][1] = 2.0*WEIGHT_2D*lambda[0]*lambda[1];
#endif

  return (const REAL_B *)D2;
}

static const REAL_B *D2_phic_2_2d(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  static REAL_BB D2;

#if DIM_MAX > 1
  D2[2][2] = 2.0*WEIGHT_2D*lambda[0]*lambda[1];
  D2[0][1] = D2[1][0] = WEIGHT_2D*SQR(lambda[2]);
  D2[0][2] = D2[2][0] = 2.0*WEIGHT_2D*lambda[1]*lambda[2];
  D2[1][2] = D2[2][1] = 2.0*WEIGHT_2D*lambda[0]*lambda[2];
#endif

  return (const REAL_B *)D2;
}

/* >>> */

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

  return data->el_co_normal;
}

/* >>> */

/******************************************************************************/

#undef DEF_EL_VEC_TTB
#define DEF_EL_VEC_TTB(type, name)			\
  DEF_EL_VEC_CONST(type, name, N_TTB_MAX, N_TTB_MAX)

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

#undef DEFUN_GET_EL_DOF_VEC_TTB
#define DEFUN_GET_EL_DOF_VEC_TTB(name, type, ASSIGN)			\
  DEFUN_GET_EL_VEC_TTB(dv->fe_space->bas_fcts,				\
		       _##name##_vec, type, dv->fe_space->admin,	\
		       ASSIGN(dv->vec[dof], rvec[ibas]),		\
		       const DOF_##type##_VEC *dv);			\
  static const EL_##type##_VEC *					\
  ttb_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 ttb_get__##name##_vec(vec, el, dv);			\
    } else {								\
      ttb_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_TTB(self, dof_indices, DOF, admin,
		     rvec[ibas] = dof,
		     const DOF_ADMIN *admin, const BAS_FCTS *self);

static const EL_BNDRY_VEC *
ttb_get_bound_0d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_TTB(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;

  BNDRY_FLAGS_CPY(rvec[0], el_info->vertex_bound[0]);

  return vec ? NULL : rvec_space;
}

static const EL_BNDRY_VEC *
ttb_get_bound_1d(BNDRY_FLAGS *vec,
		 const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_TTB(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;

  BNDRY_FLAGS_CPY(rvec[0], el_info->edge_bound[0]);
  BNDRY_FLAGS_CPY(rvec[1], el_info->edge_bound[0]);

  return vec ? NULL : rvec_space;
}

#if DIM_MAX > 1
static const EL_BNDRY_VEC *
ttb_get_bound_2d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_TTB(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;

  BNDRY_FLAGS_INIT(rvec[0]); BNDRY_FLAGS_SET(rvec[0], el_info->face_bound[0]);
  BNDRY_FLAGS_INIT(rvec[1]); BNDRY_FLAGS_SET(rvec[1], el_info->face_bound[0]);
  BNDRY_FLAGS_INIT(rvec[2]); BNDRY_FLAGS_SET(rvec[2], el_info->face_bound[0]);

  return vec ? NULL : rvec_space;
}
#endif

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

DEFUN_GET_EL_DOF_VEC_TTB(int, INT, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TTB(real, REAL, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TTB(real_d, REAL_D, COPY_DOW);

static const EL_REAL_VEC_D *
ttb_get_real_vec_d(REAL result[], const EL *el, const DOF_REAL_VEC_D *dv)
{
  return (const EL_REAL_VEC_D *)
    ttb_get_real_d_vec((REAL_D *)result, el, (const DOF_REAL_D_VEC *)dv);
}

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

DEFUN_GET_EL_DOF_VEC_TTB(schar, SCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TTB(uchar, UCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TTB(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_TTB(real_dd, REAL_DD, MYMCOPY);

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

static void ttb_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)
{
  TTB_DATA *data = (TTB_DATA *)self->ext_data;
  int dim = self->dim;
  const QUAD_FAST *qfast;
  REAL rhs[N_LAMBDA(DIM_MAX-1)] = { 0.0, };
  REAL_D f_res, uh_res, diff;
  int iq, i, b;

  if (wall >= 0) {
    return; /* The trace space is the Null-space */
  }
  if (b_no) {
    for (i = 0; i < no; i++) {
      vec->vec[b_no[i]] = 0.0;
    }
  } else {
    for (i = 0; i < self->n_bas_fcts; i++) {
      vec->vec[i] = 0.0;
    }
  }
  if (data->qfast->bas_fcts != self) {
    data->qfast = get_quad_fast(self, data->quad, INIT_PHI);
  }
  qfast = data->qfast;
  INIT_ELEMENT(el_info, qfast);
  for (b = 0; b < self->n_bas_fcts; b++) {
    rhs[b] = 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);
      rhs[b] +=
	qfast->w[iq]
	*
	SCP_DOW(diff, data->el_co_normal)
	*
	qfast->quad->lambda[iq][b];
    }
  }
  if (b_no) {
    int n;
    for (n = 0; n < no; n++) {
      int j;
      b = b_no[n];
      vec->vec[b] = 0.0;
      for (j = 0; j < self->n_bas_fcts; j++) {
	vec->vec[b] += mass_inv[dim][b][j] * rhs[j];
      }
    }
  } else {
    for (b = 0; b < self->n_bas_fcts; b++) {
      int j;
      vec->vec[b] = 0.0;
      for (j = 0; j < self->n_bas_fcts; j++) {
	vec->vec[b] += mass_inv[dim][b][j] * rhs[j];
      }
    }
  }
}

static void ttb_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)
{
  TTB_DATA *data = (TTB_DATA *)self->ext_data;
  int dim = self->dim;
  const QUAD_FAST *qfast;
  REAL rhs[N_LAMBDA(DIM_MAX-1)] = { 0.0, };
  REAL f_res, uh_res;
  int iq, i, b;

  if (wall >= 0) {
    return; /* The trace space is the Null-space */
  }
  if (b_no) {
    for (i = 0; i < no; i++) {
      vec->vec[b_no[i]] = 0.0;
    }
  } else {
    for (i = 0; i < self->n_bas_fcts; i++) {
      vec->vec[i] = 0.0;
    }
  }
  if (data->qfast->bas_fcts != self) {
    data->qfast = get_quad_fast(self, data->quad, INIT_PHI);
  }
  qfast = data->qfast;
  INIT_ELEMENT(el_info, qfast);
  for (b = 0; b < self->n_bas_fcts; b++) {
    rhs[b] = 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);
      rhs[b] +=
	qfast->w[iq]
	*
	(f_res - uh_res)
	*
	qfast->quad->lambda[iq][b];
    }
  }
  if (b_no) {
    int n;
    for (n = 0; n < no; n++) {
      int j;
      b = b_no[n];
      vec->vec[b] = 0.0;
      for (j = 0; j < self->n_bas_fcts; j++) {
	vec->vec[b] += mass_inv[dim][b][j] * rhs[j];
      }
    }
  } else {
    for (b = 0; b < self->n_bas_fcts; b++) {
      int j;
      vec->vec[b] = 0.0;
      for (j = 0; j < self->n_bas_fcts; j++) {
	vec->vec[b] += mass_inv[dim][b][j] * rhs[j];
      }
    }
  }
}

static void ttb_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;
  EL *el;

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

  switch (self->dim) {
  case 1: {
    DOF cdof, pdof0, pdof1;

    el = rclist->el_info.el;

    pdof0 = el->dof[node][n0+0];
    pdof1 = el->dof[node][n0+1];

    cdof = el->child[0]->dof[node][n0+1];
    drv->vec[cdof] = 0.25 * (drv->vec[pdof0] + drv->vec[pdof1]);

    cdof = el->child[0]->dof[node][n0+0];
    drv->vec[cdof] = 0.5 * drv->vec[pdof0];
    cdof = el->child[1]->dof[node][n0+1];
    drv->vec[cdof] = 0.5 * drv->vec[pdof1];
    break;
  }
  case 2: {
    DOF cdof, pdof0, pdof1, pdof2;
    int i;
    
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      pdof0 = el->dof[node][n0+0];
      pdof1 = el->dof[node][n0+1];
      pdof2 = el->dof[node][n0+2];

      cdof = el->child[0]->dof[node][n0+0];
      drv->vec[cdof] = 0.5 * drv->vec[pdof2];
      cdof = el->child[0]->dof[node][n0+1];
      drv->vec[cdof] = 0.5 * drv->vec[pdof0];
      cdof = el->child[0]->dof[node][n0+2];
      drv->vec[cdof] = 0.25 * (drv->vec[pdof0] + drv->vec[pdof1]);

      cdof = el->child[1]->dof[node][n0+0];
      drv->vec[cdof] = 0.5 * drv->vec[pdof1];
      cdof = el->child[1]->dof[node][n0+1];
      drv->vec[cdof] = 0.5 * drv->vec[pdof2];
      cdof = el->child[1]->dof[node][n0+2];
      drv->vec[cdof] = 0.25 * (drv->vec[pdof0] + drv->vec[pdof1]);
    }
    break;
  }
  default:
    break;
  }
}

static void ttb_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;
  EL *el; 

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

  switch (self->dim) {
  case 1: {
    DOF cdof, pdof0, pdof1;

    el = rclist->el_info.el;

    pdof0 = el->dof[node][n0+0];
    pdof1 = el->dof[node][n0+1];

    cdof = el->child[0]->dof[node][n0+1];

    cdof = el->child[0]->dof[node][n0+0];
    drv->vec[pdof0] = 2.0 * drv->vec[cdof];
    cdof = el->child[1]->dof[node][n0+1];
    drv->vec[pdof1] = 2.0 * drv->vec[cdof];
    break;
  }
  case 2: {
    DOF cdof, pdof0, pdof1, pdof2;
    int i;
    
    for (i = 0; i < n; i++) {
      el = rclist[i].el_info.el;

      pdof0 = el->dof[node][n0+0];
      pdof1 = el->dof[node][n0+1];
      pdof2 = el->dof[node][n0+2];

      cdof = el->child[0]->dof[node][n0+0];
      drv->vec[pdof2] = drv->vec[cdof];
      cdof = el->child[0]->dof[node][n0+1];
      drv->vec[pdof0] = 2.0 * drv->vec[cdof];

      cdof = el->child[1]->dof[node][n0+0];
      drv->vec[pdof1] = 2.0 * drv->vec[pdof1];
      cdof = el->child[1]->dof[node][n0+1];
      drv->vec[pdof2] += drv->vec[cdof];
    }
    break;
  }
  default:
    break;
  }
}

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

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

/* 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_MASTER_INFO|FILL_COORDS 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 ttb_init_element(const EL_INFO *el_info, void *vself)
{
  FUNCNAME("ttb_init_element");
  BAS_FCTS *self = (BAS_FCTS *)vself;
  TTB_DATA *data = (TTB_DATA *)self->ext_data;
  MESH *mesh;
  EL_INFO mst_info[1];

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

  mesh = el_info->mesh;

  TEST_EXIT(get_master(mesh) != NULL,
	    "This trace-space only makes sense on the trace mesh which is "
	    "attached as a slave-mesh to its master in the bulk.\n");

  TEST_EXIT((el_info->fill_flag & self->fill_flags) == self->fill_flags,
	    "Sorry, currently this implementation of wall-bubbles needs the "
	    "fill-flag FILL_MASTER_INFO, FILL_MASTER_NEIGH and FILL_COORDS "
	    "all the time.\n");

  TEST_EXIT(mesh->parametric == NULL,
	    "Not yet implemented for parametric meshes.\n");

  fill_master_el_info(mst_info, el_info, FILL_COORDS);
  
  /* Compute the wall-normal of our master element, for the correct wall */
  get_wall_normal(mst_info, el_info->master.opp_vertex, data->el_co_normal);
  if (el_info->mst_neigh.el != NULL) {
    int wall  = el_info->master.opp_vertex;
    int ov    = el_info->mst_neigh.opp_vertex;
    if (el_info->master.el->dof[wall][VERTEX]
	<
	el_info->mst_neigh.el->dof[ov][VERTEX]) {
      SCAL_DOW(-1.0, data->el_co_normal);
    }
  }

  return INIT_EL_TAG_CTX_TAG(&data->tag_ctx);    
}

static const BAS_FCT ttb_phi[DIM_LIMIT][N_TTB_MAX] = {
  { phic_0d, },
  { phic_0_1d, phic_1_1d, },
  { phic_0_2d, phic_1_2d, phic_2_2d },
};

static const GRD_BAS_FCT ttb_grd_phi[DIM_LIMIT][N_TTB_MAX] = {
  { grd_phic_0d, },
  { grd_phic_0_1d, grd_phic_1_1d, },
  { grd_phic_0_2d, grd_phic_1_2d, grd_phic_2_2d, }
};

static const D2_BAS_FCT ttb_D2_phi[DIM_LIMIT][N_TTB_MAX] = {
  { D2_phic_0d, },
  { D2_phic_0_1d, D2_phic_1_1d, },
  { D2_phic_0_2d, D2_phic_1_2d, D2_phic_2_2d, }
};

static const BAS_FCT_D ttb_phi_d[N_TTB_MAX] = {
  ttb_phi_d_c, ttb_phi_d_c, ttb_phi_d_c,
};

#define MAX_TTB_INTER_DEG 20
#define MAX_TENSOR_DEG     1

const BAS_FCTS *get_trace_tensor_bubbles(unsigned int dim,
					 unsigned int tensor_deg,
					 unsigned int inter_deg)
{
  FUNCNAME("get_trace_tensor_bubble");
  static BAS_FCTS *ttb_bfcts[DIM_MAX+1][MAX_TENSOR_DEG+1][MAX_TTB_INTER_DEG+1];
  BAS_FCTS *bfcts;

  if (tensor_deg == 0) {
    return get_trace_bubble(dim, inter_deg);
  }

  TEST_EXIT(dim < DIM_MAX, "Error: dim = %d >= DIM_MAX = %d.\n", dim, DIM_MAX);
 
  if (inter_deg > MAX_TTB_INTER_DEG) {
    WARNING("Truncating quad-degree from %d to %d.\n",
	    inter_deg, MAX_TTB_INTER_DEG);
    inter_deg = MAX_TTB_INTER_DEG;
  }
  
  if ((bfcts = ttb_bfcts[inter_deg][tensor_deg][dim]) == NULL) {
    char name[sizeof("TraceTensorBubbles_TX_IXX_Xd")];
    TTB_DATA *data;

    sprintf(name, "TraceTensorBubbles_T%d_I%02d_%dd",
	    tensor_deg, inter_deg, dim);

    ttb_bfcts[dim][tensor_deg][inter_deg] = bfcts = MEM_CALLOC(1, BAS_FCTS);
    bfcts->name = strdup(name);
    bfcts->dim  = dim;
    bfcts->rdim = DIM_OF_WORLD;
    bfcts->degree = N_LAMBDA(dim) + tensor_deg;
    bfcts->n_dof[CENTER] = 1;
    bfcts->trace_admin = -1;
    bfcts->n_bas_fcts = N_BAS_LAGRANGE(tensor_deg, dim);
      bfcts->n_bas_fcts_max = N_BAS_LAGRANGE(tensor_deg, dim);
    CHAIN_INIT(bfcts);
    bfcts->unchained = bfcts;
    bfcts->phi     = ttb_phi[dim];
    bfcts->grd_phi = ttb_grd_phi[dim];
    bfcts->D2_phi  = ttb_D2_phi[dim];
    bfcts->phi_d   = ttb_phi_d;
    bfcts->trace_bas_fcts = get_null_bfcts(MAX(0, dim-1));
    bfcts->get_dof_indices = ttb_get_dof_indices;
    switch (dim) {
    case 0:
      bfcts->get_bound = ttb_get_bound_0d;
      break;
    case 1:
      bfcts->get_bound = ttb_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      bfcts->get_bound = ttb_get_bound_2d;
      break;
#endif
    }
    bfcts->interpol = ttb_interpol;
    bfcts->interpol_dow = ttb_interpol_dow;
    bfcts->get_int_vec = ttb_get_int_vec;
    bfcts->get_real_vec = ttb_get_real_vec;
    bfcts->get_real_d_vec = ttb_get_real_d_vec;
    bfcts->get_real_dd_vec = ttb_get_real_dd_vec;
    bfcts->get_real_vec_d = ttb_get_real_vec_d;
    bfcts->get_uchar_vec = ttb_get_uchar_vec;
    bfcts->get_schar_vec = ttb_get_schar_vec;
    bfcts->get_ptr_vec = ttb_get_ptr_vec;

    bfcts->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      ttb_real_refine_inter_d;
    bfcts->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      ttb_real_coarse_inter_d;
    bfcts->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      ttb_real_coarse_restr_d;

    bfcts->real_refine_inter_d = ttb_real_refine_inter_d;
    bfcts->real_coarse_inter_d = ttb_real_coarse_inter_d;
    bfcts->real_coarse_restr_d = ttb_real_coarse_restr_d;

    bfcts->ext_data = data = MEM_CALLOC(1, TTB_DATA);

    INIT_ELEMENT_DEFUN(bfcts, ttb_init_element,
		       FILL_MASTER_INFO|FILL_MASTER_NEIGH|FILL_COORDS);
    INIT_OBJECT(bfcts);

    data->quad      = get_quadrature(dim, inter_deg);
    data->inter_deg = inter_deg;
    data->qfast     = get_quad_fast(bfcts, data->quad, INIT_PHI);
  }
  
  return bfcts;
}

