/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     element.c                                                      */
/*                                                                          */
/*                                                                          */
/* description:  routines on elements that depend on the dimension in 3d    */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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                         */
/*             Universitaet Freiburg                                        */
/*             Hermann-Herder-Str. 10                                       */
/*             79104 Freiburg, Germany                                      */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003),                         */
/*         C.-J. Heine (2006).                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/

#ifdef HAVE_CONFIG_H
# include "config.h" /* probably a good idea to pull this one in here ... */
#endif

#include "alberta.h"

#define MESH_DIM 3

/*--------------------------------------------------------------------------*/
/*  world_to_coord_3d():  return -1 if inside, otherwise index of lambda<0  */
/*--------------------------------------------------------------------------*/

int world_to_coord_3d(const EL_INFO *el_info, const REAL *xy, REAL_B lambda)
{
  FUNCNAME("world_to_coord_3d");
  REAL  edge[3][DIM_OF_WORLD], x[DIM_OF_WORLD];
  REAL  x0, det, det0, det1, det2, adet, lmin;
  int   i, j, k;

#if DIM_OF_WORLD != 3
  ERROR_EXIT("Not yet implemented for DIM_OF_WORLD != 3\n");
#endif

  DEBUG_TEST_EXIT((el_info->fill_flag & FILL_COORDS) ||
		  !el_info->mesh->parametric ||
		  el_info->mesh->parametric->use_reference_mesh,
		  "You must enable the use_reference_mesh entry in the "
		  "PARAMETRIC structure to use this function on the "
		  "reference mesh. Use parametric->coord_to_world() "
		  "to access the parametric mesh\n");

  /*  wir haben das gleichungssystem zu loesen: */
  /*       ( q1x q2x q3x)  (lambda1)     (qx)      */
  /*       ( q1y q2y q3y)  (lambda2)  =  (qy)      */
  /*       ( q1z q2z q3z)  (lambda3)     (qz)      */
  /*      mit qi=pi-p3, q=xy-p3                 */

  for (j=0; j<DIM_OF_WORLD; j++) {
    x0 = el_info->coord[3][j];
    x[j] = xy[j] - x0;
    for (i=0; i<3; i++)
      edge[i][j] = el_info->coord[i][j] - x0;
  }

  det =  edge[0][0] * edge[1][1] * edge[2][2]
       + edge[0][1] * edge[1][2] * edge[2][0]
       + edge[0][2] * edge[1][0] * edge[2][1]
       - edge[0][2] * edge[1][1] * edge[2][0]
       - edge[0][0] * edge[1][2] * edge[2][1]
       - edge[0][1] * edge[1][0] * edge[2][2];
  det0 =       x[0] * edge[1][1] * edge[2][2]
       +       x[1] * edge[1][2] * edge[2][0]
       +       x[2] * edge[1][0] * edge[2][1]
       -       x[2] * edge[1][1] * edge[2][0]
       -       x[0] * edge[1][2] * edge[2][1]
       -       x[1] * edge[1][0] * edge[2][2];
  det1 = edge[0][0] *       x[1] * edge[2][2]
       + edge[0][1] *       x[2] * edge[2][0]
       + edge[0][2] *       x[0] * edge[2][1]
       - edge[0][2] *       x[1] * edge[2][0]
       - edge[0][0] *       x[2] * edge[2][1]
       - edge[0][1] *       x[0] * edge[2][2];
  det2 = edge[0][0] * edge[1][1] *       x[2]
       + edge[0][1] * edge[1][2] *       x[0]
       + edge[0][2] * edge[1][0] *       x[1]
       - edge[0][2] * edge[1][1] *       x[0]
       - edge[0][0] * edge[1][2] *       x[1]
       - edge[0][1] * edge[1][0] *       x[2];

  adet = ABS(det);

  if (adet < 1.E-20) {
    ERROR_EXIT("det = %le; abort\n", det);
    return(-2);
  }

  lambda[0] = det0 / det;
  lambda[1] = det1 / det;
  lambda[2] = det2 / det;
  lambda[3] = 1.0 - lambda[0] - lambda[1] - lambda[2];

  k = -1;
  lmin = 0.0;
  j = 0;
  for (i = 0; i <= 3; i++) {
    if ((lambda[i]*adet) < -1.E-15) {
      if (lambda[i] < lmin) {
	k = i;
	lmin = lambda[i];
      }
      j++;
    }
  }

  return(k);
}

/*--------------------------------------------------------------------------*/
/*  transform local coordinates l to world coordinates; if w is non NULL     */
/*  store them at w otherwise return a pointer to some local static         */
/*  area containing world coordintes                                        */
/*--------------------------------------------------------------------------*/

const REAL *coord_to_world_3d(const EL_INFO *el_info, const REAL *l, REAL_D w)
{
  FUNCNAME("coord_to_world_3d");
  static REAL_D world;
  REAL         *ret;
  int           i;

#if DIM_OF_WORLD < 3
  ERROR_EXIT("Does not make sense for DIM_OF_WORLD < 3!\n");
#endif

  DEBUG_TEST_EXIT((el_info->fill_flag & FILL_COORDS) ||
		  !el_info->mesh->parametric ||
		  el_info->mesh->parametric->use_reference_mesh,
		  "You must enable the use_reference_mesh entry in the "
		  "PARAMETRIC structure to use this function on the "
		  "reference mesh. Use parametric->coord_to_world() "
		  "to access the parametric mesh\n");

  ret = w ? w : world;

  for(i = 0; i < DIM_OF_WORLD; i++)
    ret[i] = el_info->coord[0][i] * l[0]
           + el_info->coord[1][i] * l[1]
           + el_info->coord[2][i] * l[2]
           + el_info->coord[3][i] * l[3];

  return((const REAL *) ret);
}

/*--------------------------------------------------------------------------*/
/*  compute volume of an element                                            */
/*--------------------------------------------------------------------------*/

REAL el_det_3d(const EL_INFO *el_info)
{
  FUNCNAME("el_det_3d");
  REAL_D      e[3];
  REAL        det;
  int         i;
  const REAL  *v0;

#if DIM_OF_WORLD < 3
  ERROR_EXIT("Does not make sense for DIM_OF_WORLD < 3!\n");
#endif

  DEBUG_TEST_EXIT((el_info->fill_flag & FILL_COORDS) ||
		  !el_info->mesh->parametric ||
		  el_info->mesh->parametric->use_reference_mesh,
		  "You must enable the use_reference_mesh entry in the "
		  "PARAMETRIC structure to use this function on the "
		  "reference mesh. Use parametric->coord_to_world() "
		  "to access the parametric mesh\n");

  v0 = el_info->coord[0];

  for (i = 0; i < 3; i++)  {
    AXPBY_DOW(1.0, el_info->coord[i+1], -1.0, v0, e[i]);
  }

#if DIM_OF_WORLD == 3
  det =   e[0][0] * (e[1][1]*e[2][2] - e[1][2]*e[2][1])
        - e[0][1] * (e[1][0]*e[2][2] - e[1][2]*e[2][0])
        + e[0][2] * (e[1][0]*e[2][1] - e[1][1]*e[2][0]);
  det = ABS(det);
#else
  {
    REAL g[MESH_DIM][MESH_DIM];
    int j;    

    /* compute sqrt(det(g)) where g is the first fundamental form */
    for (i = 0; i < MESH_DIM; i++) {
      g[i][i] = SCP_DOW(e[i], e[i]);
      for (j = 0; j < i; j++) {
	g[i][j] = g[j][i] = SCP_DOW(e[i], e[j]);
      }
    }
    det = g[0][0]*g[1][1]*g[2][2]
      + g[1][0]*g[2][1]*g[0][2]
      + g[2][0]*g[0][1]*g[1][2]
      - g[2][0]*g[1][1]*g[0][2]
      - g[2][1]*g[1][2]*g[0][0]
      - g[2][2]*g[1][0]*g[0][1];
    det = sqrt(det); 
  }
#endif

  return det;
}

REAL el_volume_3d(const EL_INFO *el_info)
{
  return el_det_3d(el_info) / 6.0;
}

/*--------------------------------------------------------------------------*/
/*  compute gradients of basis functions on element                         */
/*  - returns abs(determinant)                                              */
/*--------------------------------------------------------------------------*/

REAL el_grd_lambda_3d(const EL_INFO *el_info, REAL_BD grd_lam)
{
  FUNCNAME("el_grd_lambda_3d");
  int         i, j;
  REAL_D      e[3];  
  const REAL  *v0;
  REAL        det, adet;
#if DIM_OF_WORLD == 3
  REAL        a11, a12, a13, a21, a22, a23, a31, a32, a33;
#endif

#if DIM_OF_WORLD < 3
# error negative co-dimensions are at least questionable ...
#endif

  DEBUG_TEST_EXIT((el_info->fill_flag & FILL_COORDS) ||
		  !el_info->mesh->parametric ||
		  el_info->mesh->parametric->use_reference_mesh,
		  "You must enable the use_reference_mesh entry in the "
		  "PARAMETRIC structure to use this function on the "
		  "reference mesh. Use parametric->coord_to_world() "
		  "to access the parametric mesh\n");

  v0 = el_info->coord[0];

  for (i = 0; i < 3; i++)  {
    AXPBY_DOW(1.0, el_info->coord[i+1], -1.0, v0, e[i]);
  }

#if DIM_OF_WORLD == 3
  det =   e[0][0] * (e[1][1]*e[2][2] - e[1][2]*e[2][1])
        - e[0][1] * (e[1][0]*e[2][2] - e[1][2]*e[2][0])
        + e[0][2] * (e[1][0]*e[2][1] - e[1][1]*e[2][0]);
  adet = ABS(det);
  if (adet < 1.0E-25)
  {
    MSG("abs(det) = %lf\n",adet);
    for (i = 0; i < N_VERTICES_3D; i++)
      for (j = 0; j < DIM_OF_WORLD; j++)
	grd_lam[i][j] = 0.0;
  }
  else 
  {
    det = 1.0 / det;
    a11 = (e[1][1]*e[2][2] - e[1][2]*e[2][1]) * det;    /* (a_ij) = A^{-T} */
    a12 = (e[1][2]*e[2][0] - e[1][0]*e[2][2]) * det;
    a13 = (e[1][0]*e[2][1] - e[1][1]*e[2][0]) * det;
    a21 = (e[0][2]*e[2][1] - e[0][1]*e[2][2]) * det;
    a22 = (e[0][0]*e[2][2] - e[0][2]*e[2][0]) * det;
    a23 = (e[0][1]*e[2][0] - e[0][0]*e[2][1]) * det;
    a31 = (e[0][1]*e[1][2] - e[0][2]*e[1][1]) * det;
    a32 = (e[0][2]*e[1][0] - e[0][0]*e[1][2]) * det;
    a33 = (e[0][0]*e[1][1] - e[0][1]*e[1][0]) * det;

    grd_lam[1][0] = a11;
    grd_lam[1][1] = a12;
    grd_lam[1][2] = a13;
    grd_lam[2][0] = a21;
    grd_lam[2][1] = a22;
    grd_lam[2][2] = a23;
    grd_lam[3][0] = a31;
    grd_lam[3][1] = a32;
    grd_lam[3][2] = a33;

    grd_lam[0][0] = -grd_lam[1][0] -grd_lam[2][0] -grd_lam[3][0];
    grd_lam[0][1] = -grd_lam[1][1] -grd_lam[2][1] -grd_lam[3][1];
    grd_lam[0][2] = -grd_lam[1][2] -grd_lam[2][2] -grd_lam[3][2];
  }

#else
  {
    REAL g[MESH_DIM][MESH_DIM], g_1[MESH_DIM][MESH_DIM];
    REAL det_1;
    int k;
    
    /* compute sqrt(det(g)) where g is the first fundamental form */
    for (i = 0; i < MESH_DIM; i++) {
      g[i][i] = SCP_DOW(e[i], e[i]);
      for (j = 0; j < i; j++) {
	g[i][j] = g[j][i] = SCP_DOW(e[i], e[j]);
      }
    }
    det = g[0][0]*g[1][1]*g[2][2]
      + g[1][0]*g[2][1]*g[0][2]
      + g[2][0]*g[0][1]*g[1][2]
      - g[2][0]*g[1][1]*g[0][2]
      - g[2][1]*g[1][2]*g[0][0]
      - g[2][2]*g[1][0]*g[0][1];
    det_1 = 1.0/det;
    adet = sqrt(det);

    /* invert the metric tensor */
    g_1[0][0] = ( g[1][1]*g[2][2] - g[1][2]*g[2][1])*det_1;
    g_1[1][0] = (-g[0][1]*g[2][2] + g[0][2]*g[2][1])*det_1;
    g_1[2][0] = ( g[0][1]*g[1][2] - g[0][2]*g[1][1])*det_1;
    g_1[1][1] = ( g[0][0]*g[2][2] - g[0][2]*g[2][0])*det_1;
    g_1[2][1] = (-g[0][0]*g[1][2] + g[0][2]*g[1][0])*det_1;
    g_1[2][2] = ( g[0][0]*g[1][1] - g[0][1]*g[1][0])*det_1;
    
    g_1[0][1] = g_1[1][0];
    g_1[0][2] = g_1[2][0];
    g_1[1][2] = g_1[2][1];

    /* Now compute \nabla_x\lambda which is
     *
     * DF_S^{-1} = g^{-1} \nabla_\lambda x
     */
    for (i = 0; i < MESH_DIM; i++) {
      for (j = 0; j < DIM_OF_WORLD; j++) {
	grd_lam[i+1][j] = 0.0;
	for (k = 0; k < MESH_DIM; k++) {
	  grd_lam[i+1][j] += g_1[i][k]*e[k][j];
	}
      }
    }
    
    /* now fill in the remaining row for \lambda_0 */
    for (j = 0; j < DIM_OF_WORLD; j++) {
      grd_lam[0][j] = - grd_lam[1][j] - grd_lam[2][j] - grd_lam[3][j];
    }
  }
#endif

  return adet;
}


/*--------------------------------------------------------------------------*/
/*  calculate a normal to a triangular face side of a tetrahedron with      */
/*  coordinates coord; return the absolute value of the determinant from    */
/*  the transformation to the reference element                             */
/*--------------------------------------------------------------------------*/

REAL get_wall_normal_3d(const EL_INFO *el_info, int i0, REAL *normal)
{
  FUNCNAME("get_wall_normal_3d");
  REAL_D       tmp_normal;
#if DIM_OF_WORLD == 3
  static const int ind[7] = {0, 1, 2, 3, 0, 1, 2};
  REAL         det, scale;
  int          n, i1 = ind[i0+1], i2 = ind[i0+2], i3 = ind[i0+3];
  const REAL_D *coord = (const REAL_D *)el_info->coord;
  REAL_D       e0, e1, e2;

  DEBUG_TEST_EXIT((el_info->fill_flag & FILL_COORDS) ||
		  !el_info->mesh->parametric ||
		  el_info->mesh->parametric->use_reference_mesh,
		  "You must enable the use_reference_mesh entry in the "
		  "PARAMETRIC structure to use this function on the "
		  "reference mesh. Use parametric->coord_to_world() "
		  "to access the parametric mesh\n");

  if (!normal) {
    normal = tmp_normal;
  }

  for (n = 0; n < DIM_OF_WORLD; n++) {
    e0[n] = coord[i2][n] - coord[i1][n];
    e1[n] = coord[i3][n] - coord[i1][n];
    e2[n] = coord[i0][n] - coord[i1][n];
  }

  WEDGE_DOW(e0, e1, normal);

  det = NORM_DOW(normal);
  TEST_EXIT(det > 1.e-30, "det = 0 on face %d\n", i0);

  scale = SCP_DOW(e2,normal) < 0.0 ? 1.0/det : -1.0/det;
  AX_DOW(scale, normal);

  return(det);
#else
  /* The following is in principle Cramer's rule, used to extract one
   * line of grd_lambda (which of course just holds the wall-normal)
   */
  static const int ind[7] = {0, 1, 2, 3, 0, 1, 2};
  int idx[MESH_DIM] = { ind[i0+1], ind[i0+2], ind[i0+3] }, i, j;
  const REAL_D *coord = (const REAL_D *)el_info->coord;
  REAL_D e[MESH_DIM];
  REAL g[MESH_DIM][MESH_DIM];
  REAL ncoeff[MESH_DIM], w1[MESH_DIM], w2[MESH_DIM];
  REAL detg, det, scale;
  
  DEBUG_TEST_EXIT((el_info->fill_flag & FILL_COORDS) ||
		  !el_info->mesh->parametric ||
		  el_info->mesh->parametric->use_reference_mesh,
		  "You must enable the use_reference_mesh entry in the "
		  "PARAMETRIC structure to use this function on the "
		  "reference mesh. Use parametric->coord_to_world() "
		  "to access the parametric mesh\n");

  if (!normal) {
    normal = tmp_normal;
  }

  for (i = 0; i < MESH_DIM; i++) {
    AXPBY_DOW(1.0, coord[idx[i]], -1.0, coord[i0], e[i]);
  }
  for (i = 0; i < MESH_DIM; i++) {
    g[i][i] = SCP_DOW(e[i], e[i]);
    for (j = i+1; j < MESH_DIM; j++) {
      g[i][j] = g[j][i] = SCP_DOW(e[i], e[j]);
    }
  }

  detg =
    + g[0][0]*(g[1][1]*g[2][2]-g[2][1]*g[1][2])
    - g[1][0]*(g[0][1]*g[2][2]-g[2][1]*g[0][2])
    + g[2][0]*(g[0][1]*g[1][2]-g[1][1]*g[0][2]);

  for (i = 0; i < MESH_DIM; i++) {
    w1[i] = g[i][0] - g[i][2];
    w2[i] = g[i][1] - g[i][2];
  }
  
  /* The coefficient vector of the normal must be orthogonal to the
   * coefficient vectors w_i of (e_i - e_2) w.r.t. to g, so we can use
   * the cross-product here.
   */
  ncoeff[0] = w1[1]*w2[2] - w1[2]*w2[1];
  ncoeff[1] = w1[2]*w2[0] - w1[0]*w2[2];
  ncoeff[2] = w1[0]*w2[1] - w1[1]*w2[0];

  /* The normal has the "correct" length up to scaling with 1.0/sqrt(detg) */
  detg = 1.0/sqrt(detg);
  AXEY_DOW(ncoeff[0], e[0], normal);
  for (j = 1; j < MESH_DIM; j++) {
    AXPY_DOW(ncoeff[j], e[j], normal);
  }
  SCAL_DOW(detg, normal);
  
  det = NORM_DOW(normal);
  /* cH: shouldn't the following test the relative size of det w.r.t.,
   * e.g. (some power of ...) the edge length?
   */
  TEST_EXIT(det > 1.e-30, "det = 0 on face %d\n", i0);

  /* FIXME: in principle, the orientation is fixed ... */
  scale = SCP_DOW(e[2], normal) < 0.0 ? 1.0/det : -1.0/det;
  SCAL_DOW(scale, normal);

  return det;
#endif
}

/*--------------------------------------------------------------------------*/
/* orient the vertices of walls                                             */
/* used by estimator for the jumps => same quadrature nodes from both sides!*/
/*--------------------------------------------------------------------------*/

const int sorted_wall_vertices_3d[N_WALLS_3D][DIM_FAC_3D][2*N_VERTICES_2D-1] = {
  {{1,3,2,1,3},{2,1,3,2,1},{1,2,3,1,2},{3,2,1,3,2},{3,1,2,3,1},{2,3,1,2,3}},
  {{0,3,2,0,3},{2,0,3,2,0},{0,2,3,0,2},{3,2,0,3,2},{3,0,2,3,0},{2,3,0,2,3}},
  {{0,3,1,0,3},{1,0,3,1,0},{0,1,3,0,1},{3,1,0,3,1},{3,0,1,3,0},{1,3,0,1,3}},
  {{0,2,1,0,2},{1,0,2,1,0},{0,1,2,0,1},{2,1,0,2,1},{2,0,1,2,0},{1,2,0,1,2}}
};

/* The return value "perm" is the index into sorted_wall_vertices_3d
 * such that
 *
 * nv = sorted_wall_vertices_3d[oppv][perm][i]
 *
 * matches
 *
 * v = vertex_of_wall_3d[wall][i]
 *
 * I.e.:  el->dof[v][0] == neigh->dof[nv][0] is true
 *
 * The benefit over wall_orientation_3d() is that we do not need to
 * keep two permutations into account, but just one. This simplifies
 * the definition of wall-quadratures.
 */
int wall_rel_orientation_3d(const EL *el, const EL *neigh, int wall, int oppv)
{
  int   no;
  const int *vow = vertex_of_wall_3d[wall];
  const int *vow_n = vertex_of_wall_3d[oppv];
  DOF   **dof = el->dof, **dof_n = neigh->dof;

  /* worst case: 4 comparisons */
  if (dof[vow[0]][0] == dof_n[vow_n[0]][0]) {
    if (dof[vow[1]][0] == dof_n[vow_n[1]][0]) {
      no = 2; /* identity mapping */
    } else {
      no = 0; /* transposition of (face) vertex 1 and 2 */
    }
  } else if (dof[vow[1]][0] == dof_n[vow_n[1]][0]) {
    /* we already know that dof[vow[0]][0] != dof_n[vow_n[0]][0]), so
     * the identity case cannot happen here, we have the transposition
     * of the (face) vertex 0 with (face) vertex 2
     */
    no = 3;
  } else if (dof[vow[2]][0] == dof_n[vow_n[2]][0]) {
    /* same reasoning, this is not the identity and hence must be the
     * transposition of (face) vertex 0 and (face) vertex 1, as vertex
     * two is a fixed-point.
     */
    no = 1;
  } else if (dof[vow[0]][0] == dof_n[vow_n[1]][0]) {
    /* forward 3 cycle (0 1 2) */
    no = 5;
  } else {
    /* inverse of 3 cycle (0 2 1) */
    no = 4;
  }
  return no;
}

int wall_orientation_3d(const EL *el, int wall)
{
  FUNCNAME("wall_orientation_3d");
  int    no;
  const int *vow = vertex_of_wall_3d[wall];
  DOF    **dof = el->dof;

  no = -1;
  if (dof[vow[0]][0] < dof[vow[1]][0])
    no++;
  if (dof[vow[1]][0] < dof[vow[2]][0])
    no += 2;
  if (dof[vow[2]][0] < dof[vow[0]][0])
    no += 4;

  if (no < 0 || no > 5) {
    ERROR_EXIT("cannot sort wall indices of element %d at wall %d\n", 
	       INDEX(el), wall);
  }

  return no;
}
