/*******************************************************************************
 * ALBERTA:  an Adaptive multi Level finite element toolbox using
 *           Bisectioning refinement and Error control by Residual
 *           Techniques for scientific Applications
 *
 * file:     wall_quad.c
 *
 * description:  Fast (i.e. caching) quadrature over walls
 *
 *******************************************************************************
 *
 *  authors:   Alfred Schmidt
 *             Zentrum fuer Technomathematik
 *             Fachbereich 3 Mathematik/Informatik
 *             Universitaet Bremen
 *             Bibliothekstr. 2
 *             D-28359 Bremen, Germany
 *
 *             Kunibert G. Siebert
 *             Institut fuer Mathematik
 *             Universitaet Augsburg
 *             Universitaetsstr. 14
 *             D-86159 Augsburg, Germany
 *
 *             Claus-Justus Heine
 *             Abteilung fuer Angewandte Mathematik
 *             Albert-Ludwigs-Universitaet Freiburg
 *             Hermann-Herder-Str. 10
 *             D-79104 Freiburg im Breisgau, Germany
 *
 *  http://www.alberta-fem.de
 *
 *  (c) by A. Schmidt and K.G. Siebert (1996-2003),
 *         C.-J. Heine (1998-2007)
 *
 ******************************************************************************/

#include "alberta.h"

/* For integration from both sides of a co-dimension 1 simplex. */
struct slow_neigh_quad {
  QUAD        quad;
  INIT_EL_TAG wall_quad_tag;
  EL          *cur_el;
};

typedef struct _AI_biwall_quad 
{
  const WALL_QUAD *wall_quad;
  const QUAD      *neigh_quad[N_WALLS_MAX];
  QUAD            dflt_neigh_quad[N_WALLS_MAX][N_WALLS_MAX][DIM_FAC_MAX];
  struct slow_neigh_quad slow_neigh_quad[N_WALLS_MAX];
} _AI_BI_WALL_QUAD;

#define MAX_QUAD_DEGREE 19

typedef struct _AI_wall_quad_fast {
  WALL_QUAD_FAST  quad_fast;
  INIT_EL_TAG_CTX tag_ctx;
  INIT_EL_TAG     bfct_tag;
  INIT_EL_TAG     quad_tag;
  /* integration across walls */
  const QUAD_FAST *neigh_qfast[N_WALLS_MAX];
  const QUAD_FAST *dflt_neigh_qfast[N_WALLS_MAX][N_WALLS_MAX][DIM_FAC_MAX];
  const QUAD_FAST *slow_neigh_qfast[N_WALLS_MAX];
  INIT_EL_TAG     wall_quad_tag[N_WALLS_MAX];
  EL              *cur_el[N_WALLS_MAX];
  struct _AI_wall_quad_fast *next;
} _AI_WALL_QUAD_FAST;

typedef struct _AI_wall_quad_metadata {
  _AI_BI_WALL_QUAD    *biwall_quad; /* initialized by register_wall_quad() */
  _AI_WALL_QUAD_FAST *quad_fast;   /* initialized by
				    * get_wall_quad_fast(), also
				    * contains QUAD_FAST for
				    * intergrating across walls
				    * (i.e. with barycentric
				    * co-ordinates relative to the
				    * neighbour element.
				    */
  int n_points; /* number of points storage has been allocated for */
} _AI_WALL_QUAD_METADATA;

const WALL_QUAD *wall_quad_from_quad(const QUAD *quad)
{
  WALL_QUAD *bquad;
  int dim = quad->dim+1, wall, qp, d;
  char *name;

  /* construct a new bndry quadrature. */
  bquad = MEM_CALLOC(1, WALL_QUAD);
  bquad->name = name = MEM_ALLOC(strlen(quad->name)+sizeof("Wall "), char);
  sprintf(name, "Wall %s", (char *)quad->name);
  bquad->degree       = quad->degree;
  bquad->dim          = dim; /* this is the dimension of the mesh */
  bquad->n_points_max = quad->n_points_max;
  
  /* Now fill in all the quadrature points, for all walls */
  for (wall = 0; wall < N_WALLS(dim); wall++) {
    QUAD   *quadb = &bquad->quad[wall];
    REAL_B *lambda;
    const int *vow;
    char *name;
    
    quadb->name = name = MEM_ALLOC(strlen(quad->name)+sizeof("Wall 0 "), char);
    sprintf(name, "Wall %d %s", wall, quad->name);
    quadb->degree       = quad->degree;
    quadb->dim          = dim;
    quadb->codim        = 1;
    quadb->subsplx      = wall;
    quadb->n_points     = quad->n_points;
    quadb->n_points_max = quad->n_points_max;
    quadb->w            = quad->w;
    quadb->lambda       =
      (const REAL_B *)(lambda = MEM_ALLOC(quadb->n_points_max, REAL_B));

    /* Initialize the barycentric coordinates, there is no need to
     * care about orientation etc. 'cause there is no neighbour.
     */
    vow  = vertex_of_wall(dim, wall);
    for (qp = 0; qp < quad->n_points; qp++) {
      lambda[qp][wall] = 0.0;
      for (d = 0; d < N_LAMBDA(dim-1); d++) {
	lambda[qp][vow[d]] = quad->lambda[qp][d];
      }
      /* Make sure "overflow" values are initialized with 0 */
      for (++d; d < N_LAMBDA_MAX; d++) {
	lambda[qp][d] = 0.0;
      }
    }
  }

  register_wall_quadrature(bquad); /* initialize meta-data etc */
  
  bquad->init_element = NULL;

  return bquad;
}

/* Construct a quadrature rule for the integration over co-dimension 1
 * sub-simplices.
 */
const WALL_QUAD *get_wall_quad(int dim, int degree)
{
  static WALL_QUAD **wquads[DIM_MAX+1];
  static int       max_degree[DIM_MAX+1]; /* actually one higher */
  const QUAD *quad;
  int i;

  /* Check whether we already have a suitable quadrature for
   * exactly the wanted degree
   */
  if (degree < max_degree[dim] && wquads[dim][degree]) {
    return wquads[dim][degree];
  }

  /* The actual degree might be smaller or larger, so retry with
   * whatever degree is returned by get_quadrature.
   */
  quad = get_quadrature(dim-1, degree);
  degree = quad->degree;

  if (degree < max_degree[dim] && wquads[dim][degree]) {
    return wquads[dim][degree];
  }

  /* possibly resize our bndry-quad array */
  if (degree >= max_degree[dim]) {
    wquads[dim] = MEM_REALLOC(wquads[dim],
			      max_degree[dim], degree+1,
			      WALL_QUAD *);
    for (i = max_degree[dim]; i < degree+1; i++) {
      wquads[dim][i] = NULL;
    }
    max_degree[dim] = degree + 1;
  }

  wquads[dim][degree] = (WALL_QUAD *)wall_quad_from_quad(quad);

  return wquads[dim][degree];
}

/* Construct quadrature rules w.r.t. to the barycentric co-ordinates
 * of the neighbour elements, such that the new rules match the ones
 * on the original element.
 */
static inline const QUAD *dflt_get_neigh_quad(const EL_INFO *el_info,
					      const WALL_QUAD *wall_quad,
					      int wall);
static inline const QUAD *slow_get_neigh_quad(const EL_INFO *el_info,
					      const WALL_QUAD *wall_quad,
					      int wall);

const QUAD *get_neigh_quad(const EL_INFO *el_info,
			   const WALL_QUAD *wall_quad,
			   int wall)
{
  if (!INIT_ELEMENT_NEEDED(wall_quad)) {
    return dflt_get_neigh_quad(el_info, wall_quad, wall);
  } else {
    return slow_get_neigh_quad(el_info, wall_quad, wall);
  }
}

static inline const QUAD *dflt_get_neigh_quad(const EL_INFO *el_info,
					      const WALL_QUAD *wall_quad,
					      int wall)
{
  _AI_WALL_QUAD_METADATA *md = (_AI_WALL_QUAD_METADATA *)wall_quad->metadata;
  _AI_BI_WALL_QUAD *aiwquad = md->biwall_quad;
  const EL_GEOM_CACHE *elgc;

  /* Determine the relative orientation to the neighbour ... */
  elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_REL_ORIENTATION(wall));

  /* ... and return the corresponding quadrature rule */
  return &aiwquad->dflt_neigh_quad[
    wall][
      el_info->opp_vertex[wall]][
	elgc->rel_orientation[wall]];
}

static INIT_EL_TAG slow_neigh_quad_init_element(const EL_INFO *neigh_info,
						void *thisptr)
{
  struct slow_neigh_quad *snquad = (struct slow_neigh_quad *)thisptr;

  return neigh_info ? snquad->wall_quad_tag : INIT_EL_TAG_DFLT;
}

static inline const QUAD *slow_get_neigh_quad(const EL_INFO *el_info,
					      const WALL_QUAD *wall_quad,
					      int wall)
{
  _AI_WALL_QUAD_METADATA *md = (_AI_WALL_QUAD_METADATA *)wall_quad->metadata;
  _AI_BI_WALL_QUAD *aiwquad = md->biwall_quad;
  const QUAD *bquad;
  QUAD *nquad;
  const EL_GEOM_CACHE *elgc;
  INIT_EL_TAG bquad_tag;
  int iq, oppv, i, dim;
  const int *vow, *vmap;
  REAL_B *lambda;
  
  /* maybe insert a magic-number check here */

  bquad = &wall_quad->quad[wall];
  bquad_tag = INIT_ELEMENT(el_info, bquad);

  if (el_info->el == aiwquad->slow_neigh_quad[wall].cur_el &&
      bquad_tag == aiwquad->slow_neigh_quad[wall].wall_quad_tag) {
    /* nothing new */
    return aiwquad->neigh_quad[wall];
  }

  aiwquad->slow_neigh_quad[wall].wall_quad_tag = bquad_tag;
  aiwquad->slow_neigh_quad[wall].cur_el        = el_info->el;

  if (bquad_tag == INIT_EL_TAG_NULL) {
    return NULL; /* caller should check this */
  }

  if (bquad_tag == INIT_EL_TAG_DFLT) {
    aiwquad->neigh_quad[wall] = dflt_get_neigh_quad(el_info, wall_quad, wall);
    return aiwquad->neigh_quad[wall];
  }
  
  /* Slow-path: none-standard quad-rule. */

  /* now we need the relative orientation of the neighbour and the
   * current element
   */
  elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_REL_ORIENTATION(wall));

  aiwquad->neigh_quad[wall] = nquad = &aiwquad->slow_neigh_quad[wall].quad;

  oppv = el_info->opp_vertex[wall];
  dim  = bquad->dim;

  nquad->subsplx  = oppv;
  nquad->n_points = bquad->n_points;
  nquad->w        = bquad->w;

  if (nquad->n_points_max != bquad->n_points_max) {
    MEM_FREE(nquad->lambda, nquad->n_points_max, REAL_B);
    nquad->lambda = MEM_ALLOC(bquad->n_points_max, const REAL_B);
    nquad->n_points_max = bquad->n_points_max;
    register_quadrature(nquad);
  }

  vmap = sorted_wall_vertices(dim, oppv, elgc->rel_orientation[wall]);
  vow  = vertex_of_wall(dim, wall);

  /* compute matching quadrature points for the neighbour */
  lambda = (REAL_B *)nquad->lambda;
  for (iq = 0; iq < bquad->n_points; iq++) {
    lambda[iq][oppv] = 0.0;
    for (i = 0; i < N_LAMBDA(dim-1); i++) {
      lambda[iq][vmap[i]] = bquad->lambda[iq][vow[i]];
    }
    /* Make sure overflow values are initialized with 0 */
    for (++i; i < N_LAMBDA_MAX; i++) {
      lambda[iq][i] = 0.0;
    }
  }

  return nquad;
}

/* Generate 
 */

/* Generate the meta-data and look-up tables for the integration over
 * walls from both sides.
 */
void register_wall_quadrature(WALL_QUAD *wall_quad)
{
  _AI_BI_WALL_QUAD *aiwquad;
  _AI_WALL_QUAD_METADATA *md;
  int i, iq, wall, oppv, perm, dim = wall_quad->dim;
  QUAD *bquad;
  QUAD *nquad;
  REAL_B *lambda;
  const int *vow, *vmap;
  char *name;

  INIT_OBJECT(wall_quad);

  if ((md = (_AI_WALL_QUAD_METADATA *)wall_quad->metadata) != NULL) {
    /* If this wall_quad already carries a meta-data object, then
     * first kill that. We assume that something vital has changed,
     * e.g. n_points_max.
     */
    aiwquad = md->biwall_quad;
  } else {
    aiwquad = MEM_CALLOC(1, _AI_BI_WALL_QUAD);
    aiwquad->wall_quad = wall_quad;
    wall_quad->metadata = md = MEM_CALLOC(1, _AI_WALL_QUAD_METADATA);
    md->biwall_quad = aiwquad;
  }

  for (wall = 0; wall < N_WALLS(dim); wall++) {
    bquad = &wall_quad->quad[wall];
    register_quadrature(bquad);
    aiwquad->slow_neigh_quad[wall].wall_quad_tag = INIT_EL_TAG_NONE;
    nquad = &aiwquad->slow_neigh_quad[wall].quad;
    if (nquad->metadata == NULL) {
      *nquad          = *bquad;
      INIT_ELEMENT_DEFUN(nquad, slow_neigh_quad_init_element, FILL_NOTHING);
      nquad->metadata = NULL;
      nquad->n_points =
	nquad->n_points_max = 0;
      nquad->lambda   = NULL;
    } else {
      MEM_FREE(nquad->name, strlen(nquad->name)+1, char);
    }
    nquad->name = name = MEM_ALLOC(strlen(bquad->name)*sizeof("Neighbour "), char);
    sprintf(name, "Neighbour %s", bquad->name);
    register_quadrature(nquad);

    vow = vertex_of_wall(dim, wall);
    for (oppv = 0; oppv < N_VERTICES(dim); oppv++) {
      for (perm = 0; perm < DIM_FAC(dim); perm++) {
	nquad = &aiwquad->dflt_neigh_quad[wall][oppv][perm];
	vmap = sorted_wall_vertices(dim, oppv, perm);
	if (nquad->metadata == NULL) {
	  *nquad          = *bquad;
	  nquad->metadata = NULL;
	  INIT_ELEMENT_DEFUN(nquad, NULL, FILL_NOTHING);
	  nquad->subsplx  = oppv;
	} else {
	  MEM_FREE(nquad->name, strlen(nquad->name)+1, char);
	  MEM_FREE(nquad->lambda, md->n_points, REAL_B);
	}
	nquad->name = name =
	  MEM_ALLOC(strlen(bquad->name)*sizeof("Neighbour "), char);
	sprintf(name, "Neighbour %s", bquad->name);
	nquad->lambda =
	  (const REAL_B *)(lambda = MEM_ALLOC(nquad->n_points_max, REAL_B));
	register_quadrature(nquad);

	for(iq = 0; iq < nquad->n_points; iq++) {
	  lambda[iq][oppv] = 0.0;
	  for (i = 0; i < N_LAMBDA(dim-1); i++) {
	    lambda[iq][vmap[i]] = bquad->lambda[iq][vow[i]];
	  }
	  /* Make sure overflow values are initialized with 0 */
	  for (++i; i < N_LAMBDA_MAX; i++) {
	    lambda[iq][i] = 0.0;
	  }
	}
      }
    }
  }
  md->n_points = wall_quad->n_points_max;
}

/* Call all initializers for all walls. This is pure convenience;
 * applications may at their choice simply call the initializers of
 * the sub-ordinate QUAD_FAST objects.
 */
static INIT_EL_TAG wall_qfast_init_element(const EL_INFO *el_info, void *thisptr)
{
  _AI_WALL_QUAD_FAST   *qf       = (_AI_WALL_QUAD_FAST *)thisptr;
  const WALL_QUAD_FAST *wqfast   = &qf->quad_fast;
  const BAS_FCTS       *bas_fcts = wqfast->bas_fcts;
  const WALL_QUAD      *bquad    = wqfast->wall_quad;
  INIT_EL_TAG          bfct_tag, quad_tag;
  int                  dim, wall;

  quad_tag = INIT_ELEMENT(el_info, bquad);
  bfct_tag = INIT_ELEMENT(el_info, bas_fcts);

  dim = bquad->dim;

  if (el_info == NULL) {

    for (wall = 0; wall < N_WALLS(dim); wall++) {
      INIT_OBJECT(wqfast->quad_fast[wall]);
    }

    if (INIT_EL_TAG_CTX_TAG(&qf->tag_ctx) != INIT_EL_TAG_DFLT) {
      qf->quad_tag = INIT_EL_TAG_DFLT;
      qf->bfct_tag = INIT_EL_TAG_DFLT;

      INIT_EL_TAG_CTX_DFLT(&qf->tag_ctx);
    }

    return INIT_EL_TAG_CTX_TAG(&qf->tag_ctx);
  }

  if (quad_tag == INIT_EL_TAG_NULL || bfct_tag == INIT_EL_TAG_NULL) {

    for (wall = 0; wall < N_WALLS(dim); wall++) {
      (void)INIT_ELEMENT(el_info, wqfast->quad_fast[wall]);
    }
      
    if (INIT_EL_TAG_CTX_TAG(&qf->tag_ctx) != INIT_EL_TAG_NULL) {

      qf->quad_tag = quad_tag;
      qf->bfct_tag = bfct_tag;

      INIT_EL_TAG_CTX_NULL(&qf->tag_ctx);
    }
    return INIT_EL_TAG_CTX_TAG(&qf->tag_ctx);
  }

  /* Non-scalar basis functions still need the INIT_ELEMENT() call to
   * the quad-fast structure.
   */
  if ((bas_fcts->rdim == 1 || !bas_fcts->dir_pw_const) &&
      quad_tag == qf->quad_tag && bfct_tag == qf->bfct_tag) {
    return INIT_EL_TAG_CTX_TAG(&qf->tag_ctx);
  }

  if (quad_tag == INIT_EL_TAG_DFLT && bfct_tag == INIT_EL_TAG_DFLT) {

    for (wall = 0; wall < N_WALLS(dim); wall++) {
      (void)INIT_ELEMENT(el_info, wqfast->quad_fast[wall]);
    }

    if (INIT_EL_TAG_CTX_TAG(&qf->tag_ctx) != INIT_EL_TAG_DFLT) {

      qf->quad_tag           = INIT_EL_TAG_DFLT;
      qf->bfct_tag           = INIT_EL_TAG_DFLT;
    
      INIT_EL_TAG_CTX_DFLT(&qf->tag_ctx);
    }
    
    return INIT_EL_TAG_CTX_TAG(&qf->tag_ctx);
  }

  qf->quad_tag = quad_tag;
  qf->bfct_tag = bfct_tag;

  dim = el_info->mesh->dim;

  /* simply update all necessary values, we know that quad_tag !=
   * INIT_EL_TAG_DFLT/NULL at this point.
   */
  for (wall = 0; wall < N_WALLS(dim); wall++) {
    (void)INIT_ELEMENT(el_info, wqfast->quad_fast[wall]);
  }

  INIT_EL_TAG_CTX_UNIQ(&qf->tag_ctx);

  return INIT_EL_TAG_CTX_TAG(&qf->tag_ctx);
}

const WALL_QUAD_FAST *get_wall_quad_fast(const BAS_FCTS *bas_fcts, 
					   const WALL_QUAD *bquad,
					   FLAGS init_flag)
{
  FUNCNAME("get_wall_quad_fast");
  _AI_WALL_QUAD_METADATA *md = (_AI_WALL_QUAD_METADATA *)bquad->metadata;
  _AI_BI_WALL_QUAD        *aiwquad = md->biwall_quad;
  _AI_WALL_QUAD_FAST     *aiwqfast;
  WALL_QUAD_FAST         *quad_fast = NULL;
  QUAD *nquad;
  int dim, wall, oppv, perm;

  TEST_EXIT(bquad->dim == bas_fcts->dim,
	    "Dimensions of BAS_FCTS (%d) and BI_WALL_QUAD (%d) do not match.\n",
	    bas_fcts->dim, bquad->dim);

  if (INIT_ELEMENT_NEEDED(bquad) || INIT_ELEMENT_NEEDED(bas_fcts)) {
    /* In the presence of per-element initialization avoid
     * initialising more components than requested by the caller,
     * otherwise the element initializers have too much work to do.
     */
    for (aiwqfast = md->quad_fast;
	 aiwqfast;
	 aiwqfast = aiwqfast->next){
      quad_fast = &aiwqfast->quad_fast;
      if (quad_fast->wall_quad == bquad &&
	  quad_fast->bas_fcts == bas_fcts &&
	  quad_fast->init_flag == init_flag) {
	INIT_OBJECT(&aiwqfast->quad_fast);
	return &aiwqfast->quad_fast;
      }
    }
  } else {
    /* Re-use any quadrature which has all needed bits set; add data
     * to already existing QUAD_FAST objects to keep the number of
     * QUAD_FAST objects as small as possible.
     */
    for (aiwqfast = md->quad_fast;
	 aiwqfast; aiwqfast = aiwqfast->next){
      quad_fast = &aiwqfast->quad_fast;
      if (quad_fast->wall_quad == bquad &&
	  quad_fast->bas_fcts == bas_fcts &&
	  ((quad_fast->init_flag & INIT_TANGENTIAL)
	   ==
	   (init_flag & INIT_TANGENTIAL))) {
	break;
      }
    }
  }

  if (aiwqfast && ((quad_fast->init_flag & init_flag) == init_flag)) {
    return (const WALL_QUAD_FAST *)quad_fast;
  }

  dim = bquad->dim;

  /* Reset to standard case. */
  INIT_OBJECT(bquad);
  INIT_OBJECT(bas_fcts);

  if (!aiwqfast) {
    aiwqfast = MEM_CALLOC(1, _AI_WALL_QUAD_FAST);
    aiwqfast->next = md->quad_fast;
    md->quad_fast = aiwqfast;

    quad_fast    = &aiwqfast->quad_fast;

    quad_fast->wall_quad = bquad;
    quad_fast->bas_fcts   = (BAS_FCTS *)bas_fcts;
    quad_fast->init_flag  = init_flag;

    if (INIT_ELEMENT_NEEDED(bquad) || INIT_ELEMENT_NEEDED(bas_fcts)) {
      INIT_ELEMENT_DEFUN(quad_fast, wall_qfast_init_element,
			 bquad->fill_flags|bas_fcts->fill_flags);
      INIT_EL_TAG_CTX_INIT(&aiwqfast->tag_ctx);
    } else {
      quad_fast->init_element = NULL;
    }
  }
  
  for (wall = 0; wall < N_WALLS(dim); wall++) {
    quad_fast->quad_fast[wall] =
      get_quad_fast(bas_fcts, &bquad->quad[wall], init_flag);
    quad_fast->fill_flags |= quad_fast->quad_fast[wall]->fill_flags;
  }

  /* Initialize caches for the integration across walls from both sides 
   */
  for (wall = 0; wall < N_WALLS(dim); wall++) {
    nquad  = &aiwquad->slow_neigh_quad[wall].quad;
    aiwqfast->slow_neigh_qfast[wall] =
      get_quad_fast(bas_fcts, nquad, init_flag);
    for (oppv = 0; oppv < N_VERTICES(dim); oppv++) {
      for (perm = 0; perm < DIM_FAC(dim); perm++) {
	nquad = &aiwquad->dflt_neigh_quad[wall][oppv][perm];
	aiwqfast->dflt_neigh_qfast[wall][oppv][perm] =
	  get_quad_fast(bas_fcts, nquad, init_flag);
      }
    }
  }
  
  INIT_OBJECT(quad_fast);

  return (const WALL_QUAD_FAST *)quad_fast;
}

/* Construct quadrature caches w.r.t. to the barycentric co-ordinates
 * of the neighbour elements, such that the new rules match the ones
 * on the original element.
 */
static inline const QUAD_FAST *
dflt_get_neigh_quad_fast(const EL_INFO *el_info,
			 const WALL_QUAD_FAST *wall_quad_fast,
			 int wall);
static inline const QUAD_FAST *
slow_get_neigh_quad_fast(const EL_INFO *el_info,
			 const WALL_QUAD_FAST *wall_quad_fast,
			 int wall);

const QUAD_FAST *get_neigh_quad_fast(const EL_INFO *el_info,
				     const WALL_QUAD_FAST *wall_qfast,
				     int wall)
{
  if (!INIT_ELEMENT_NEEDED(wall_qfast->wall_quad)) {
    /* Still the returned QUAD_FAST may need per-element
     * initialization if so required by the associated basis
     * functions.
     */
    return dflt_get_neigh_quad_fast(el_info, wall_qfast, wall);
  } else {
    return slow_get_neigh_quad_fast(el_info, wall_qfast, wall);
  }
}

/* "fast" default case when we know that
 * INIT_ELEMENT_METHOD(wall_quad_fast->quad) == NULL.
 */
static inline const QUAD_FAST *
dflt_get_neigh_quad_fast(const EL_INFO *el_info,
			 const WALL_QUAD_FAST *wall_quad_fast,
			 int wall)
{
  _AI_WALL_QUAD_FAST *aiwqfast = (_AI_WALL_QUAD_FAST *)wall_quad_fast;
  const EL_GEOM_CACHE *elgc;

  /* Determine the relative orientation to the neighbour ... */
  elgc = fill_el_geom_cache(el_info, FILL_EL_WALL_REL_ORIENTATION(wall));

  /* ... and return the corresponding quadrature rule */
  return aiwqfast->dflt_neigh_qfast[
    wall][
      el_info->opp_vertex[wall]][
	elgc->rel_orientation[wall]];
}

/* This function is called when
 *
 * INIT_ELEMENT_METHOD(wall_quad_fast->quad) != NULL.
 *
 * We duplicate the logic of slow_get_neigh_quad() here; if the
 * initializer-tag of the corresponding boundary quadrature is
 * INIT_EL_TAG_DFLT, then we can just return the standard QUAD_FAST,
 * otherwise we enter a slow code path.
 */
static inline const QUAD_FAST *
slow_get_neigh_quad_fast(const EL_INFO *el_info,
			 const WALL_QUAD_FAST *wall_quad_fast,
			 int wall)
{
  _AI_WALL_QUAD_FAST *aiwqfast = (_AI_WALL_QUAD_FAST *)wall_quad_fast;
  _AI_WALL_QUAD_METADATA *md =
    (_AI_WALL_QUAD_METADATA *)wall_quad_fast->wall_quad->metadata;
  _AI_BI_WALL_QUAD *aiwquad = md->biwall_quad;
  const QUAD *bquad;
  INIT_EL_TAG bquad_tag;

  bquad = &wall_quad_fast->wall_quad->quad[wall];
  bquad_tag = INIT_ELEMENT(el_info, bquad);

  if (el_info->el == aiwqfast->cur_el[wall] &&
      bquad_tag == aiwqfast->wall_quad_tag[wall]) {
    /* nothing new */
    return aiwqfast->neigh_qfast[wall];
  }
  
  aiwqfast->wall_quad_tag[wall] = bquad_tag;
  
  if (bquad_tag == INIT_EL_TAG_NULL) {
    return NULL; /* caller should check this */
  }
  
  if (bquad_tag == INIT_EL_TAG_DFLT) {
    /* Caller still needs to call INIT_ELEMENT(neigh_info, RETVAL) in
     * case the basis functions need a per-element initializer.
     */
    aiwquad->slow_neigh_quad[wall].wall_quad_tag = bquad_tag;
    aiwqfast->neigh_qfast[wall] =
      dflt_get_neigh_quad_fast(el_info, wall_quad_fast, wall);
    return aiwqfast->neigh_qfast[wall];
  }
  
  /* Slow-path: non-standard quad-rule. */
  
  /* Compute the new quadrature nodes */
  slow_get_neigh_quad(el_info, wall_quad_fast->wall_quad, wall);

  /* Leave it to the caller to call INIT_ELEMENT(neigh_info,
   * RETVAL)
   */
  aiwqfast->neigh_qfast[wall] = aiwqfast->slow_neigh_qfast[wall];

  return aiwqfast->neigh_qfast[wall];
}
