/**@file 
 *
 * The trace-space for the 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"

/* 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_BAS_TRACE_BUBBLE 1

typedef struct trace_bubble_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;
} TB_DATA;

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

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

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

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

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

  grd[0] = WEIGHT_1D*lambda[1];
  grd[1] = WEIGHT_1D*lambda[0];

  return (const REAL *)grd;
}

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

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

  return (const REAL *)grd;
}

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

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

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

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

  return (const REAL_B *)D2;
}

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

  return data->el_co_normal;
}

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

#undef DEF_EL_VEC_TRACE_BUBBLE
#define DEF_EL_VEC_TRACE_BUBBLE(type, name)				\
  DEF_EL_VEC_CONST(type, name, N_BAS_TRACE_BUBBLE, N_BAS_TRACE_BUBBLE)

#undef DEFUN_GET_EL_VEC_TRACE_BUBBLE
#define DEFUN_GET_EL_VEC_TRACE_BUBBLE(name, type, admin, body, ...)	\
  static const EL_##type##_VEC *					\
  tb_get_##name(type##_VEC_TYPE *vec, const EL *el, __VA_ARGS__)	\
  {									\
    FUNCNAME("tb_get_"#name);						\
    static DEF_EL_VEC_TRACE_BUBBLE(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];					\
    dof = dofptr[node][n0]; ibas = 0;					\
    body;								\
									\
    return vec ? NULL : rvec_space;					\
  }									\
  struct _AI_semicolon_dummy

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

static const EL_BNDRY_VEC *
tb_get_bound_0d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_TRACE_BUBBLE(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 *
tb_get_bound_1d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_TRACE_BUBBLE(BNDRY, rvec_space);
  BNDRY_FLAGS *rvec = vec ? vec : rvec_space->vec;

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

  return vec ? NULL : rvec_space;
}

#if DIM_MAX > 1
static const EL_BNDRY_VEC *
tb_get_bound_2d(BNDRY_FLAGS *vec,
		const EL_INFO *el_info, const BAS_FCTS *self)
{
  static DEF_EL_VEC_TRACE_BUBBLE(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]);

  return vec ? NULL : rvec_space;
}
#endif

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

DEFUN_GET_EL_DOF_VEC_TRACE_BUBBLE(int, INT, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TRACE_BUBBLE(real, REAL, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TRACE_BUBBLE(real_d, REAL_D, COPY_DOW);

static const EL_REAL_VEC_D *
tb_get_real_vec_d(REAL result[], const EL *el, const DOF_REAL_VEC_D *dv)
{
  return (const EL_REAL_VEC_D *)
    tb_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_TRACE_BUBBLE(schar, SCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TRACE_BUBBLE(uchar, UCHAR, COPY_EQ);

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

DEFUN_GET_EL_DOF_VEC_TRACE_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_TRACE_BUBBLE(real_dd, REAL_DD, MYMCOPY);

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

static void tb_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)
{
  TB_DATA *data = (TB_DATA *)self->ext_data;
  const QUAD_FAST *qfast;
  REAL accu;
  REAL_D f_res;
  REAL_D uh_res;
  REAL_D diff;
  int iq;

  if (wall >= 0) {
    return; /* The trace space is the Null-space */
  }
  if (data->qfast->bas_fcts != self) {
    data->qfast = get_quad_fast(self, data->quad, INIT_PHI);
    INIT_ELEMENT(el_info, self);
  }
  qfast = data->qfast;
  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->el_co_normal);
  }
  vec->vec[0] = accu;
}

static void tb_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)
{
  TB_DATA *data = (TB_DATA *)self->ext_data;
  const QUAD_FAST *qfast;
  REAL accu;
  REAL f_res;
  REAL uh_res;
  int iq;

  if (wall >= 0) {
    return; /* The trace space is the Null-space */
  }
  if (data->qfast->bas_fcts != self) {
    data->qfast = get_quad_fast(self, data->quad, INIT_PHI);
    INIT_ELEMENT(el_info, self);
  }
  qfast = data->qfast;
  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[0] = accu;
}

static void tb_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];
  }
}

static void tb_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;
  }
}

static void tb_real_coarse_restr_d(DOF_REAL_VEC_D *drv,
				   RC_LIST_EL *rclist,
				   int n)
{
  FUNCNAME("tb_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 trace_bubble_init_element(const EL_INFO *el_info, void *vself)
{
  FUNCNAME("trace_bubble_init_element");
  BAS_FCTS *self = (BAS_FCTS *)vself;
  TB_DATA *data = (TB_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 tb_phi[DIM_LIMIT][1] = {
  { phic_0d },
  { phic_1d },
  { phic_2d }
};

static const GRD_BAS_FCT tb_grd_phi[DIM_LIMIT][1] = {
  { grd_phic_0d },
  { grd_phic_1d },
  { grd_phic_2d }
};

static const D2_BAS_FCT tb_D2_phi[DIM_LIMIT][1] = {
  { D2_phic_0d },
  { D2_phic_1d },
  { D2_phic_2d }
};

static const BAS_FCT_D tb_phi_d[1] = {
  tb_phi_d_c
};

#define MAX_TRACE_BUBBLE_INTER_DEG 20

const BAS_FCTS *get_trace_bubble(unsigned int dim, unsigned int inter_deg)
{
  FUNCNAME("get_bubble");
  static BAS_FCTS *tb_bfcts[DIM_MAX+1][MAX_TRACE_BUBBLE_INTER_DEG+1];

  TEST_EXIT(dim < DIM_MAX,
	    "Error: dim = %d >= DIM_MAX = %d.\n", dim, DIM_MAX);
 
  if (inter_deg > MAX_TRACE_BUBBLE_INTER_DEG) {
    WARNING("Truncating quad-degree from %d to %d.\n",
	    inter_deg, MAX_TRACE_BUBBLE_INTER_DEG);
    inter_deg = MAX_TRACE_BUBBLE_INTER_DEG;
  }
  
  if (tb_bfcts[inter_deg][dim] == NULL) {
    char name[sizeof("TraceBubble_IXX_Xd")];
    TB_DATA *data;

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

    tb_bfcts[dim][inter_deg] = MEM_CALLOC(1, BAS_FCTS);
    tb_bfcts[dim][inter_deg]->name = strdup(name);
    tb_bfcts[dim][inter_deg]->dim  = dim;
    tb_bfcts[dim][inter_deg]->rdim = DIM_OF_WORLD;
    tb_bfcts[dim][inter_deg]->degree = N_LAMBDA(dim);
    tb_bfcts[dim][inter_deg]->n_dof[CENTER] = 1;
    tb_bfcts[dim][inter_deg]->trace_admin = -1;
    tb_bfcts[dim][inter_deg]->n_bas_fcts = 
      tb_bfcts[dim][inter_deg]->n_bas_fcts_max = 1;
    CHAIN_INIT(tb_bfcts[dim][inter_deg]);
    tb_bfcts[dim][inter_deg]->unchained = tb_bfcts[dim][inter_deg];
    tb_bfcts[dim][inter_deg]->phi     = tb_phi[dim];
    tb_bfcts[dim][inter_deg]->grd_phi = tb_grd_phi[dim];
    tb_bfcts[dim][inter_deg]->D2_phi  = tb_D2_phi[dim];
    tb_bfcts[dim][inter_deg]->phi_d   = tb_phi_d;
    tb_bfcts[dim][inter_deg]->trace_bas_fcts = get_null_bfcts(MAX(0, dim-1));
    tb_bfcts[dim][inter_deg]->get_dof_indices = tb_get_dof_indices;
    switch (dim) {
    case 0:
      tb_bfcts[dim][inter_deg]->get_bound = tb_get_bound_0d;
      break;
    case 1:
      tb_bfcts[dim][inter_deg]->get_bound = tb_get_bound_1d;
      break;
#if DIM_MAX > 1
    case 2:
      tb_bfcts[dim][inter_deg]->get_bound = tb_get_bound_2d;
      break;
#endif
    }
    tb_bfcts[dim][inter_deg]->interpol = tb_interpol;
    tb_bfcts[dim][inter_deg]->interpol_dow = tb_interpol_dow;
    tb_bfcts[dim][inter_deg]->get_int_vec = tb_get_int_vec;
    tb_bfcts[dim][inter_deg]->get_real_vec = tb_get_real_vec;
    tb_bfcts[dim][inter_deg]->get_real_d_vec = tb_get_real_d_vec;
    tb_bfcts[dim][inter_deg]->get_real_dd_vec = tb_get_real_dd_vec;
    tb_bfcts[dim][inter_deg]->get_real_vec_d = tb_get_real_vec_d;
    tb_bfcts[dim][inter_deg]->get_uchar_vec = tb_get_uchar_vec;
    tb_bfcts[dim][inter_deg]->get_schar_vec = tb_get_schar_vec;
    tb_bfcts[dim][inter_deg]->get_ptr_vec = tb_get_ptr_vec;

    tb_bfcts[dim][inter_deg]->real_refine_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      tb_real_refine_inter_d;
    tb_bfcts[dim][inter_deg]->real_coarse_inter =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      tb_real_coarse_inter_d;
    tb_bfcts[dim][inter_deg]->real_coarse_restr =
      (void  (*)(DOF_REAL_VEC *, RC_LIST_EL *, int))
      tb_real_coarse_restr_d;

    tb_bfcts[dim][inter_deg]->real_refine_inter_d = tb_real_refine_inter_d;
    tb_bfcts[dim][inter_deg]->real_coarse_inter_d = tb_real_coarse_inter_d;
    tb_bfcts[dim][inter_deg]->real_coarse_restr_d = tb_real_coarse_restr_d;

    tb_bfcts[dim][inter_deg]->ext_data = data = MEM_CALLOC(1, TB_DATA);

    INIT_ELEMENT_DEFUN(tb_bfcts[dim][inter_deg], trace_bubble_init_element,
		       FILL_MASTER_INFO|FILL_MASTER_NEIGH|FILL_COORDS);
    INIT_OBJECT(tb_bfcts[dim][inter_deg]);

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

