/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     element_2d.c                                                   */
/*                                                                          */
/*                                                                          */
/* description:  routines on elements that depend on the dimension in 2d    */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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 für 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-2007).                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/

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

#include "alberta.h"

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

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

#if DIM_OF_WORLD != 2
  ERROR_EXIT("Not yet implemented for DIM_OF_WORLD != 2\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 )  (lambda1)     (qx)      */
  /*       ( q1y q2y )  (lambda2)  =  (qy)      */
  /*      mit qi=pi-p3, q=xy-p3                 */

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

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

  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] = 1.0 - lambda[0] - lambda[1];

  k = -1;
  lmin = 0.0;
  j = 0;
  for (i = 0; i <= 2; i++) {
    if ((lambda[i]*adet) < -10*REAL_EPSILON) {
      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_2d(const EL_INFO *el_info, const REAL *l, REAL_D w)
{
  FUNCNAME("coord_to_world_2d");
  static REAL_D world;
  REAL         *ret;
  int           i;

#if DIM_OF_WORLD < 2
# error Does not make sense for DIM_OF_WORLD < 2!
#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];


  return((const REAL *) ret);
}

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

REAL el_det_2d(const EL_INFO *el_info)
{
  FUNCNAME("el_det_2d");
  REAL_D      e1, e2;
  REAL        det;
  const REAL *v0;
  int         i;
#if DIM_OF_WORLD == 3
  REAL_D      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");

#if DIM_OF_WORLD < 2
# error Does not make sense for DIM_OF_WORLD < 2!
#endif
  
  v0 = el_info->coord[0];
  for (i = 0; i < DIM_OF_WORLD; i++) {
    e1[i] = el_info->coord[1][i] - v0[i];
    e2[i] = el_info->coord[2][i] - v0[i];
  }

#if DIM_OF_WORLD==2
  det = WEDGE_DOW(e1, e2);
  det = ABS(det);
#elif DIM_OF_WORLD == 3
  WEDGE_DOW(e1, e2, n);
  det = NORM_DOW(n);
#else
  /* In general: just the square-root of the det of the metric tensor. */
  det = NRM2_DOW(e1)*NRM2_DOW(e2) - SQR(SCP_DOW(e1,e2));
  det = sqrt(det);
#endif

  return det;
}

REAL el_volume_2d(const EL_INFO *el_info)
{
  return 0.5 * el_det_2d(el_info);
}

/*--------------------------------------------------------------------------*/
/*  compute gradients of basis functions on element; return the absolute    */
/*  value of the determinant from the transformation to the reference       */
/*  element                                                                 */
/*                                                                          */
/*  Notice: for dim < DIM_OF_WORLD grd_lam will contain tangential          */
/*  derivatives of the barycentric coordinates!                             */
/*--------------------------------------------------------------------------*/

REAL el_grd_lambda_2d(const EL_INFO *el_info, REAL_BD grd_lam)
{
  FUNCNAME("el_grd_lambda_2d");
  int         i, j;
  REAL_D      e1, e2;
  REAL        det, adet;
  const REAL  *v0;
#if DIM_OF_WORLD <= 3
  REAL        a11, a12, a21, a22;
#endif
#if DIM_OF_WORLD == 3
  REAL_D      n;
  REAL        a13, a23;
#elif DIM_OF_WORLD > 3
  REAL        nrm2e1, nrm2e2, scpe12;
#endif

#if DIM_OF_WORLD < 2
# error Does not make sense for DIM_OF_WORLD < 2!
#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 < DIM_OF_WORLD; i++) {
    e1[i] = el_info->coord[1][i] - v0[i];
    e2[i] = el_info->coord[2][i] - v0[i];
  }

#if DIM_OF_WORLD == 2
  det = WEDGE_DOW(e1, e2);
  adet = ABS(det);
#elif DIM_OF_WORLD == 3
  WEDGE_DOW(e1, e2, n);
  det = NRM2_DOW(n);
  adet = sqrt(det);
#else
  nrm2e1 = NRM2_DOW(e1);
  nrm2e2 = NRM2_DOW(e2);
  scpe12 = SCP_DOW(e1,e2);
  det = nrm2e1*nrm2e2 - SQR(scpe12);
  adet = sqrt(det);
#endif

  if (adet < 1.0E-25) {
    /* cH: is this _really_ a good test? Shouldn't the test relate to
     * the reference triangle (i.e. shouldn't we just catch degenerate
     * triangles)
     */
    MSG("abs(det) = %lf\n", adet);

    for (i = 0; i < N_LAMBDA_MAX; i++)
      for (j = 0; j < DIM_OF_WORLD; j++)
	grd_lam[i][j] = 0.0;
  } else {
    det = 1.0 / det;
#if DIM_OF_WORLD == 2
    a11 =  e2[1] * det;
    a21 = -e2[0] * det;
    a12 = -e1[1] * det;
    a22 =  e1[0] * det;

    grd_lam[1][0] = a11;
    grd_lam[1][1] = a21;
    grd_lam[2][0] = a12;
    grd_lam[2][1] = a22;
    grd_lam[0][0] = - grd_lam[1][0] - grd_lam[2][0];
    grd_lam[0][1] = - grd_lam[1][1] - grd_lam[2][1];
#elif DIM_OF_WORLD == 3
    a11 = (e2[1]* n[2] - e2[2]* n[1]) * det;
    a12 = (e2[2]* n[0] - e2[0]* n[2]) * det;
    a13 = (e2[0]* n[1] - e2[1]* n[0]) * det;
    a21 = (e1[2]* n[1] - e1[1]* n[2]) * det;
    a22 = (e1[0]* n[2] - e1[2]* n[0]) * det;
    a23 = (e1[1]* n[0] - e1[0]* n[1]) * 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[0][0] = -grd_lam[1][0] - grd_lam[2][0];
    grd_lam[0][1] = -grd_lam[1][1] - grd_lam[2][1];
    grd_lam[0][2] = -grd_lam[1][2] - grd_lam[2][2];
#else
    /* So what is \nabla_x \lambda_i? It is the vector orthogonal to
     * edge number i (pointing to vertex i) with length 1/|h_i|, where
     * h_i is the height vector between egde i and vertex i. This is
     * most easily computed as:
     */
    AXPBY_DOW(nrm2e2, e1, -scpe12, e2, grd_lam[1]);
    SCAL_DOW(det, grd_lam[1]);
    AXPBY_DOW(nrm2e1, e2, -scpe12, e1, grd_lam[2]);
    SCAL_DOW(det, grd_lam[2]);
    AXPBY_DOW(-1.0, grd_lam[1], -1.0, grd_lam[2], grd_lam[0]);    
#endif
  }

  /* Note: we have to clear any excess entries to 0, otherwise
   * eval_grd_uh_fast() and friends will not work.
   */
  for (i = N_LAMBDA_2D; i < N_LAMBDA_MAX; i++) {
    SET_DOW(0.0, grd_lam[i]);
  }

  return adet;
}


/*--------------------------------------------------------------------------*/
/*  calculate a normal of an edge of a triangle with coordinates            */
/*  coord; return the absolute value of the determinant from the            */
/*  transformation to the reference element                                 */
/*--------------------------------------------------------------------------*/

REAL get_wall_normal_2d(const EL_INFO *el_info, int i0, REAL *normal)
{
#if ALBERTA_DEBUG || DIM_OF_WORLD == 2
  FUNCNAME("get_face_normal_2d");
#endif
  static const int ind[5] = {0, 1, 2, 0, 1};
  REAL         det;
  int          i1 = ind[i0+1], i2 = ind[i0+2];
  const REAL_D *coord = el_info->coord;
  REAL_D       tmp_normal, e2;
#if DIM_OF_WORLD > 2
  REAL_D       w;
#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");

  if (normal == NULL) {
    normal = tmp_normal;
  }

#if DIM_OF_WORLD == 2
  normal[0] = coord[i2][1] - coord[i1][1];
  normal[1] = coord[i1][0] - coord[i2][0];

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

  SCAL_DOW(1.0/det, normal);

  /* The mesh is not necessarily oriented, so: */
  AXPBY_DOW(1.0, coord[i2], -1.0, coord[i0], e2);
  if (SCP_DOW(e2, normal) < 0.0) {
    SCAL_DOW(-1.0, normal);
  }

#else
  AXPBY_DOW(1.0, coord[i2], -1.0, coord[i0], e2);
  AXPBY_DOW(1.0, coord[i2], -1.0, coord[i1], w);
  
  det = NRM2_DOW(w);
  AXPBY_DOW(det, e2, -SCP_DOW(w, e2), w, normal);
  SCAL_DOW(1.0/NORM_DOW(normal), normal);
  det = sqrt(det);
#endif

  return det;
}

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

const int sorted_wall_vertices_2d[N_WALLS_2D][DIM_FAC_2D][2*N_VERTICES_1D-1] = {
  {{1, 2, 1}, {2, 1, 2}},
  {{2, 0, 2}, {0, 2, 0}},
  {{0, 1, 0}, {1, 0, 1}}
};

/* The return value "perm" is the index into sorted_wall_vertices_3d
 * such that
 *
 * nv = sorted_wall_vertices_2d[oppv][perm][i]
 *
 * matches
 *
 * v = vertex_of_wall_2d[wall][i]
 *
 * I.e.:  el->dof[v][0] == neigh->dof[nv][0] is true
 *
 * The benefit over wall_orientation_2d() 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_2d(const EL *el, const EL *neigh, int wall, int ov)
{
  const int *vow = vertex_of_wall_2d[wall];
  const int *vow_n = vertex_of_wall_2d[ov];
  DOF **dof = el->dof;
  DOF **dof_n = neigh->dof;

  return dof[vow[0]][0] != dof_n[vow_n[0]][0];
}

int wall_orientation_2d(const EL *el, int wall)
{
  int no;
  const int *vow = vertex_of_wall_2d[wall];
  DOF **dof = el->dof;

  no = dof[vow[0]][0] > dof[vow[1]][0];

  return no;
}

