/**@file 
 *
 * Mini-element implementation.
 *
 *      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 (2005).
 *
 *
 * Linear element with an additional interior bubble. The
 * N_VERTICES+1th basis function is
 *
 * @f[
 * \phi_{bubble}=\prod_{i=0}^{dim}\lambda_i.
 * @f]
 *
 * This implementation reuses part of the linear Lagrange elements
 * already implemented in ALBERTA in order to reuse code and prevent
 * bugs. This implementation is dimension independent (anybody wants
 * to implement hyper-simplexes?).
 */
#include <alberta/alberta.h>

/**@addtogroup FOREIGN
 * Stuff that does not belong here.
 * @{
 */

/*--------------------------------------------------------------------------*/
/*---  Mini element                                                      ---*/
/*--------------------------------------------------------------------------*/

#define MINI_NAME "P1+bubble"

#define N_BFCTS(dim) (N_VERTICES(dim) + 1)
#define N_BFCTS_MAX  N_BFCTS(DIM_MAX)

static const REAL_B bary_mini[DIM_MAX+1][N_BFCTS_MAX] = {
  /* DIM == 0 */
  {
    { 0.0 },
  },
  /* DIM == 1 */
  {
    { 1.0, 0.0 },
    { 0.0, 1.0 },
    { 1.0/2.0, 1.0/2.0 }
  },
#if DIM_OF_WORLD > 1
  {
    { 1.0, 0.0, 0.0 },
    { 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 1.0 },
    { 1.0/3.0, 1.0/3.0, 1.0/3.0 }
  },
#endif
#if DIM_OF_WORLD > 2
  {
    { 1.0, 0.0, 0.0, 0.0 },
    { 0.0, 1.0, 0.0, 0.0 },
    { 0.0, 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 0.0, 1.0 },
    { 1.0/4.0, 1.0/4.0, 1.0/4.0, 1.0/4.0 }
  }
#endif
};

#define MINI_MAGIC "MINI"
#define TEST_MAGIC(mdata)						\
  TEST_EXIT(memcmp((mdata)->magic, MINI_MAGIC, 4) == 0,		\
	    "Data inconsistency detected, magic should be \"%s\", "	\
	    "but got \"%c%c%c%c\"\n",					\
	    MINI_MAGIC,							\
	    (mdata)->magic[0],						\
	    (mdata)->magic[1],						\
	    (mdata)->magic[2],						\
	    (mdata)->magic[3])

typedef struct mini_data
{
  const REAL_B   *nodes;
  char           magic[4];
  const BAS_FCTS *lagrange1;
} MINI_DATA;

/*--------------------------------------------------------------------------*/
/*---  basis function at center                                          ---*/
/*--------------------------------------------------------------------------*/

static REAL phic(const REAL_B lambda, const BAS_FCTS *bfcts)
{
  switch (bfcts->dim) {
  case 1:
    return 4.0*lambda[0]*lambda[1];
  case 2:
    return 27.0*lambda[0]*lambda[1]*lambda[2];
  case 3:
    return 128.0*lambda[0]*lambda[1]*lambda[2]*lambda[3];
  default:
    return 0.0;
  }
}

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

  switch (bfcts->dim) {
  case 1:
    grd[0] = 4.0*lambda[1];
    grd[1] = 4.0*lambda[2];
    break;
#if DIM_MAX > 1
  case 2:
    grd[0] = 27.0*lambda[1]*lambda[2];
    grd[1] = 27.0*lambda[0]*lambda[2];
    grd[2] = 27.0*lambda[0]*lambda[1];
    break;
#endif
#if DIM_MAX > 2
  case 3:
    grd[0] = 128.0*lambda[1]*lambda[2]*lambda[3];
    grd[1] = 128.0*lambda[0]*lambda[2]*lambda[3];
    grd[2] = 128.0*lambda[0]*lambda[1]*lambda[3];
    grd[3] = 128.0*lambda[0]*lambda[1]*lambda[2];
    break;
#endif
  default:
    break;
  }
  return (const REAL *)grd;
}

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

  switch (bfcts->dim) {
  case 1:
    D2[0][1] = D2[1][0] = 4.0;
    break;
#if DIM_MAX > 1
  case 2:
    D2[0][1] = D2[1][0] = 27.0*lambda[2];
    D2[0][2] = D2[2][0] = 27.0*lambda[1];
    D2[1][2] = D2[2][1] = 27.0*lambda[0];
    break;
#endif
#if DIM_MAX > 2
  case 3:
    D2[0][1] = D2[1][0] = 128.0*lambda[2]*lambda[3];
    D2[0][2] = D2[2][0] = 128.0*lambda[1]*lambda[3];
    D2[0][3] = D2[3][0] = 128.0*lambda[1]*lambda[2];
    D2[1][2] = D2[2][1] = 128.0*lambda[0]*lambda[3];
    D2[1][3] = D2[3][1] = 128.0*lambda[0]*lambda[2];
    D2[2][3] = D2[3][2] = 128.0*lambda[0]*lambda[1];
    break;
#endif
  }
  return (const REAL_B *)D2;
}

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

static const EL_DOF_VEC *get_dof_indices_m(DOF *vec,
					   const EL *el,
					   const DOF_ADMIN *admin,
					   const BAS_FCTS *bfcts)
{
  static DEF_EL_VEC_CONST(DOF, rvec_space, N_BFCTS_MAX, N_BFCTS_MAX);
  DOF *rvec = vec ? vec : rvec_space->vec;
  MINI_DATA *mdata = (MINI_DATA *)bfcts->ext_data;

  TEST_MAGIC(mdata);

  GET_DOF_INDICES(mdata->lagrange1, el, admin, rvec);

  rvec[N_VERTICES(bfcts->dim)] =
    el->dof[admin->mesh->node[CENTER]][admin->n0_dof[CENTER]];

  if (vec) {
    return NULL;
  } else {
    rvec_space->n_components = N_BFCTS(bfcts->dim);
    return rvec_space;
  }
}

static const EL_BNDRY_VEC *get_bound_m(BNDRY_FLAGS *bound,
				       const EL_INFO *el_info,
				       const BAS_FCTS *bfcts)
{
  FUNCNAME("get_bound");
  static DEF_EL_VEC_CONST(BNDRY, rvec_space, N_BFCTS_MAX, N_BFCTS_MAX);
  BNDRY_FLAGS *rvec = bound ? bound : rvec_space->vec;
  MINI_DATA *mdata = (MINI_DATA *)bfcts->ext_data;

  TEST_MAGIC(mdata);

  GET_BOUND(mdata->lagrange1, el_info, rvec);

  switch (bfcts->dim) {
  case 1:
    BNDRY_FLAGS_CPY(rvec[N_VERTICES(bfcts->dim)], el_info->edge_bound[0]);
    break;
#if DIM_MAX > 1
  case 2:
    BNDRY_FLAGS_INIT(rvec[N_VERTICES(bfcts->dim)]);
    BNDRY_FLAGS_SET(rvec[N_VERTICES(bfcts->dim)], el_info->face_bound[0]);
    break;
#endif
#if DIM_MAX > 2
  case 3:
    BNDRY_FLAGS_INIT(rvec[N_VERTICES(bfcts->dim)]);
    break;
#endif
  }

  if (bound) {
    return NULL;
  } else {
    rvec_space->n_components = N_BFCTS(bfcts->dim);
    return rvec_space;
  }
}

#define EQ_COPY(src, dst) (dst) = (src)

#define DEFUN_GET_VEC(name, type, vectype, copy)			\
  static const EL_##vectype##_VEC *					\
  get_##name##_vec_m(type *vec,						\
		     const EL *el,					\
		     const DOF_##vectype##_VEC *dof_vec)		\
  {									\
    FUNCNAME("get_"#name"_vec");					\
    static DEF_EL_VEC_CONST(vectype, rvec_space, N_BFCTS_MAX, N_BFCTS_MAX); \
    int       n0;							\
    type      *rvec = vec ? vec : rvec_space->vec;			\
    const BAS_FCTS *bfcts = dof_vec->fe_space->bas_fcts;		\
    MINI_DATA *mdata = (MINI_DATA *)bfcts->ext_data;			\
    									\
    TEST_MAGIC(mdata);							\
									\
    mdata->lagrange1->get_##name##_vec(rvec, el, dof_vec);		\
									\
    n0 = dof_vec->fe_space->admin->n0_dof[CENTER];			\
    copy(dof_vec->vec[							\
	   el->dof[dof_vec->fe_space->admin->mesh->node[CENTER]][n0]],	\
	 rvec[N_VERTICES(bfcts->dim)]);					\
									\
    if (vec) {								\
      return NULL;							\
    } else {								\
      rvec_space->n_components = N_BFCTS(bfcts->dim);			\
      return rvec_space;						\
    }									\
  }									\
  struct _FI_semicolon_dummy

DEFUN_GET_VEC(int, int, INT, EQ_COPY);
DEFUN_GET_VEC(real, REAL, REAL, EQ_COPY);
DEFUN_GET_VEC(real_d, REAL_D, REAL_D, COPY_DOW);
#define MYMCOPY(a, b) MCOPY_DOW((const REAL_D *)(a), (b))
DEFUN_GET_VEC(real_dd, REAL_DD, REAL_DD, MYMCOPY);
DEFUN_GET_VEC(uchar, U_CHAR, UCHAR, EQ_COPY);
DEFUN_GET_VEC(schar, S_CHAR, SCHAR, EQ_COPY);
DEFUN_GET_VEC(ptr, void *, PTR, EQ_COPY);

static void interpol_m(EL_REAL_VEC *el_vec,
		       const EL_INFO *el_info, int wall,
		       int no, const int *b_no,
		       LOC_FCT_AT_QP f, void *ud,
		       const BAS_FCTS *bfcts)
{
  FUNCNAME("interpol_m");
  static DEF_EL_VEC_CONST(REAL, l1_inter, N_BFCTS_MAX, N_BFCTS_MAX);
  REAL *rvec = el_vec->vec;
  int i, dim, l1_b_no[N_BFCTS_MAX], l1_no;
  MINI_DATA *mdata = (MINI_DATA *)bfcts->ext_data;

  dim = bfcts->dim;

  TEST_MAGIC(mdata);

  if (b_no) {
    if (no <= 0 || no > bfcts->n_bas_fcts) {
      ERROR("something is wrong, doing nothing\n");
      rvec[0] = 0.0;
      return;
    }

    l1_no = 0;
    for (i = 0; i < no; i++) {
      if (b_no[i] == N_VERTICES(dim)) {
	rvec[i] = 0.0;
      } else {
	l1_b_no[l1_no++] = b_no[i];
      }
    }
    if (l1_no) {
      INTERPOL(mdata->lagrange1, l1_inter,
	       el_info, wall, l1_no, l1_b_no, f, ud);
    }
    l1_no = 0;
    for (i = 0; i < no; i++) {
      if (b_no[i] != N_VERTICES(dim)) {
	rvec[i] = l1_inter->vec[l1_no++];
      } else {
	rvec[i] = 0.0;
      }
    }
  } else {
    INTERPOL(mdata->lagrange1, el_vec, el_info, wall, 0, NULL, f, ud);
    rvec[N_VERTICES(dim)] = 0.0;
  }
}

static void interpol_d_m(EL_REAL_D_VEC *el_vec,
			 const EL_INFO *el_info, int wall,
			 int no,  const int *b_no,
			 LOC_FCT_D_AT_QP f, void *ud,
			 const BAS_FCTS *bfcts)
{
  FUNCNAME("interpol_d_m");
  static DEF_EL_VEC_CONST(REAL_D, l1_inter, N_BFCTS_MAX, N_BFCTS_MAX);
  REAL_D *rvec = el_vec->vec;
  int    i, dim, l1_b_no[N_BFCTS_MAX], l1_no;
  MINI_DATA *mdata = (MINI_DATA *)bfcts->ext_data;

  dim = bfcts->dim;

  TEST_MAGIC(mdata);

  if (b_no) {
    if (no <= 0 || no > bfcts->n_bas_fcts) {
      ERROR("something is wrong, doing nothing\n");
      SET_DOW(0.0, rvec[0]);
      return;
    }

    l1_no = 0;
    for (i = 0; i < no; i++) {
      if (b_no[i] == N_VERTICES(dim)) {
	SET_DOW(0.0, rvec[i]);
      } else {
	l1_b_no[l1_no++] = b_no[i];
      }
    }
    if (l1_no) {
      INTERPOL_D(mdata->lagrange1, l1_inter,
		 el_info, wall, l1_no, l1_b_no, f, ud);
    }
    l1_no = 0;
    for (i = 0; i < no; i++) {
      if (b_no[i] != N_VERTICES(dim)) {
	COPY_DOW(l1_inter->vec[l1_no++], rvec[i]);
      } else {
	SET_DOW(0.0, rvec[i]);
      }
    }
  } else {
    INTERPOL_D(mdata->lagrange1, el_vec, el_info, wall, 0, NULL, f, ud);
    SET_DOW(0.0, rvec[N_VERTICES(dim)]);
  }
}

#if DIM_MAX == 1
# define N_MINI_TRACE_FCTS(dim) { N_VERTICES(dim-1), N_VERTICES(dim-1) }
#elif DIM_MAX == 2
# define N_MINI_TRACE_FCTS(dim) {				\
    N_VERTICES(dim-1), N_VERTICES(dim-1), N_VERTICES(dim-1)	\
      }
#elif DIM_MAX == 3
# define N_MINI_TRACE_FCTS(dim) {		\
    N_VERTICES(dim-1), N_VERTICES(dim-1),	\
      N_VERTICES(dim-1), N_VERTICES(dim-1)	\
      }
#endif

#define MINI_INITIALIZER(dim)					\
  {								\
    MINI_NAME,							\
      (dim),							\
      1, /* rdim */						\
      N_BFCTS(dim),						\
      N_BFCTS(dim),						\
      (dim)+1, /* degree */					\
      {1, 1, 0, 0},  /* VERTEX|CENTER|EDGE|FACE */		\
      -1,							\
      CHAIN_INITIALIZER(mini[dim]), &mini[dim],			\
	INIT_ELEMENT_INITIALIZER(NULL, FILL_NOTHING),		\
	(const BAS_FCT *)phi[dim],				\
	(const GRD_BAS_FCT *)grd_phi[dim],			\
	(const D2_BAS_FCT *)D2_phi[dim],			\
	NULL, NULL, /* 2nd, 3rd derivatives */			\
	NULL, NULL, NULL, false, /* phi_d etc. */		\
	/********************/					\
	NULL, /* trace space, filled in below */		\
	{ { { 0, }, }, }, /* trace_dof_map */			\
	N_MINI_TRACE_FCTS(dim),					\
	  get_dof_indices_m,					\
	  get_bound_m,						\
	  interpol_m,						\
	  interpol_d_m,						\
	  (void (*)(EL_REAL_VEC_D *coeff,			\
		    const EL_INFO *el_info, int wall,		\
		    int n, const int *indices,			\
		    LOC_FCT_D_AT_QP f, void *ud,		\
		    const BAS_FCTS *thisptr))			\
	  interpol_d_m,						\
	  get_int_vec_m,					\
	  get_real_vec_m,					\
	  get_real_d_vec_m,					\
	  (const EL_REAL_VEC_D *(*)(REAL result[],		\
				    const EL *,			\
				    const DOF_REAL_VEC_D *))	\
	  get_real_d_vec_m,					\
	  get_uchar_vec_m,					\
	  get_schar_vec_m,					\
	  get_ptr_vec_m,					\
	  get_real_dd_vec_m,					\
	  NULL, /* real_refine_inter */				\
	  NULL, /* real_coarse_inter */				\
	  NULL, /* real_coars_restr */				\
	  NULL, /* real_d_refince_inter */			\
	  NULL, /* real_d_coarse_inter */			\
	  NULL, /* real_d_coarse_restr */			\
	  NULL, /* real_refine_inter_d */			\
	  NULL, /* real_coarse_inter_d */			\
	  NULL, /* real_coarse_restr_d */			\
	  (void *)&mini_data[dim],				\
	  }

/** Return the mini-element handle. 
 */
const BAS_FCTS *get_old_mini_element(int dim)
{
  static int           init_done[DIM_MAX+1];
  static BAS_FCT       phi[DIM_MAX+1][N_BFCTS_MAX];
  static GRD_BAS_FCT   grd_phi[DIM_MAX+1][N_BFCTS_MAX];
  static D2_BAS_FCT    D2_phi[DIM_MAX+1][N_BFCTS_MAX];
  static MINI_DATA     mini_data[DIM_MAX+1];
  static BAS_FCTS      mini[DIM_MAX+1] = {
    {NULL, }, /* DIM == 0 */
    MINI_INITIALIZER(1),
#if DIM_OF_WORLD > 1
    MINI_INITIALIZER(2),
#endif
#if DIM_OF_WORLD > 2
    MINI_INITIALIZER(3),
#endif
   };

  TEST_EXIT(dim >= 1 && dim <= DIM_MAX,
	    "Only for 1 <= dim <= %d\n", DIM_MAX);

  if (!init_done[dim]) {
    BAS_FCTS *bfcts = &mini[dim];
    MINI_DATA *mdata = &mini_data[dim];

    /* Get linera bas_fcts first ... */
    mdata->lagrange1 = get_lagrange(dim, 1);
    memcpy(mdata->magic, MINI_MAGIC, sizeof(mdata->magic));
    mdata->nodes = bary_mini[dim];

    /* Inherit some Lagrange-1 fields. */
    bfcts->real_refine_inter = mdata->lagrange1->real_refine_inter;
    bfcts->real_coarse_inter = mdata->lagrange1->real_coarse_inter;
    bfcts->real_coarse_restr = mdata->lagrange1->real_coarse_restr;

    bfcts->real_d_refine_inter = mdata->lagrange1->real_d_refine_inter;
    bfcts->real_d_coarse_inter = mdata->lagrange1->real_d_coarse_inter;
    bfcts->real_d_coarse_restr = mdata->lagrange1->real_d_coarse_restr;

    /* insert the basis function hooks */
    memcpy(phi[dim],
	   mdata->lagrange1->phi,
	   N_VERTICES(dim)*sizeof(BAS_FCT));
    memcpy(grd_phi[dim],
	   mdata->lagrange1->grd_phi,
	   N_VERTICES(dim)*sizeof(GRD_BAS_FCT));
    memcpy(D2_phi[dim],
	   mdata->lagrange1->D2_phi,
	   N_VERTICES(dim)*sizeof(D2_BAS_FCT));

    /* clone trace information, we are just linear on the walls */
    bfcts->trace_bas_fcts = mdata->lagrange1->trace_bas_fcts;
    memcpy(&bfcts->trace_dof_map,
	   &mdata->lagrange1->trace_dof_map,
	   sizeof(bfcts->trace_dof_map));

    phi[dim][N_VERTICES(dim)]     = phic;
    grd_phi[dim][N_VERTICES(dim)] = grd_phic;
    D2_phi[dim][N_VERTICES(dim)]  = D2_phic;

    /* That's it, IMHO */
    init_done[dim] = 1;
  }
  
  return &mini[dim];
}

/**@} FOREIGN */

/*
 * Local Variables: ***
 * c-basic-offset: 2 ***
 * End: ***
 */
