/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* www.alberta-fem.de                                                       */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     traverse_r_3d.c                                                */
/*                                                                          */
/* description:                                                             */
/*           recursive mesh traversal - 3d routines:                        */
/*           fill_macro_info_3d(), fill_elinfo_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                                    */
/*                                                                          */
/*             Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung fuer Angewandte Mathematik                         */
/*             Universitaet Freiburg                                        */
/*             Hermann-Herder-Straße 10                                     */
/*             79104 Freiburg, Germany                                      */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*         C.-J. Heine (2006-2013), D. Koester (2004-2006).                 */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/*   Refinement/Coarsening vertex numbering             		    */
/*   like Baensch / Kossaczky                                               */
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/*   fill_macro_info_3d:                              		            */
/*   ----------------                                   		    */
/*   fill EL_INFO structure for a macro element         		    */
/*--------------------------------------------------------------------------*/

static void fill_macro_info_3d(MESH *mesh, const MACRO_EL *mel,
			       EL_INFO *el_info)
{
  FUNCNAME("fill_macro_info_3d");
  EL       *nb;
  MACRO_EL *mnb;
  int      i, j, k;
  FLAGS    fill_opp_coords;

  el_info->mesh     = mesh;
  el_info->macro_el = mel;
  el_info->el       = mel->el;
  el_info->parent   = NULL;
  el_info->level    = 0;
  el_info->el_type  = mel->el_type;

  /* Set FILL_NON_PERIODIC in fill-flags s.t. we do not need to check
   * mesh->is_periodic _and_ (fill_flag & FILL_NON_PERIODIC) all the
   * time. Afterwards it suffices to look at el_info->fill_flag.
   *
   * But beware: MACRO_el->neigh also includes the non-periodic
   * neighbourhood relations, MACRO_el->wall_bound also includes the
   * non-periodic boundary types. However, when looking at the EL_INFO
   * structure it suffices to have a look a FILL_NON_PERIODIC. For
   * aesthetical reasons we should convert to FILL_PERIODIC instead of
   * ..._NON_...
   */
  if (!mesh->is_periodic) {
    el_info->fill_flag |= FILL_NON_PERIODIC;
  }

  {
    MESH_MEM_INFO *minfo = (MESH_MEM_INFO *)mesh->mem_info;

    if (minfo->n_slaves > 0) {
      el_info->fill_flag |= FILL_ORIENTATION;
    }
  }

  /* Always fill the macro-wall mapping for now */
  for (i = 0; i < N_WALLS_3D; i++) {
    el_info->macro_wall[i] = i;
  }
  el_info->fill_flag |= FILL_MACRO_WALLS;
  
  if (el_info->fill_flag & FILL_COORDS) {

    for (i=0; i<N_VERTICES_3D; i++) {
      DEBUG_TEST_EXIT(mel->coord[i], "no mel->coord[%d]\n",i);
      
      for (j = 0; j < DIM_OF_WORLD; j++) {
	el_info->coord[i][j] = (*mel->coord[i])[j];
      }
    }
  }

  if (el_info->fill_flag & (FILL_OPP_COORDS | FILL_NEIGH)) {

    fill_opp_coords = (el_info->fill_flag & FILL_OPP_COORDS);
    for (i = 0; i < N_NEIGH_3D; i++) {
      if (mesh->is_periodic
	  && (el_info->fill_flag & FILL_NON_PERIODIC)
	  && mel->neigh_vertices[i][0] >= 0) {
	/* periodic boundary, non-periodic traversal requested, so
	 * fake a real boundary.
	 */
	el_info->neigh[i] = NULL;

      } else if ((mnb = mel->neigh[i])) {

	nb = el_info->neigh[i]      = mel->neigh[i]->el;
	k  = el_info->opp_vertex[i] = mel->opp_vertex[i];

	if (nb->child[0] && (k < 2)) {   /*make nb nearest el.*/
	  if (k==1) {
	    nb = el_info->neigh[i]      = nb->child[0];
	  } else {
	    nb = el_info->neigh[i]      = nb->child[1];
	  }
	  k  = el_info->opp_vertex[i] = 3;
	  if (fill_opp_coords) {
	    /* always edge between vertices 0 and 1 is bisected! */
	    if (mesh->is_periodic && mel->wall_trafo[i]) {
	      REAL *nbcp;
	      REAL_D tmp;
	      if (mnb->el->new_coord) {
		nbcp = mnb->el->new_coord;
	      } else {
		AXPBY_DOW(0.5, *mnb->coord[0], 0.5, *mnb->coord[1], tmp);
		nbcp = tmp;
	      }
	      AFFINE_DOW(mel->wall_trafo[i], nbcp, el_info->opp_coord[i]);
	    } else {
	      if (mnb->el->new_coord) {
		COPY_DOW(mnb->el->new_coord, el_info->opp_coord[i]);
	      } else {
		AXPBY_DOW(0.5, *mnb->coord[0], 0.5, *mnb->coord[1],
			  el_info->opp_coord[i]);
	      }
	    }
	  }
	} else {
	  if (fill_opp_coords) {
	    if (mesh->is_periodic && mel->wall_trafo[i])
	      AFFINE_DOW(mel->wall_trafo[i], *mnb->coord[k],
			 el_info->opp_coord[i]);
	    else
	      COPY_DOW(*mnb->coord[k], el_info->opp_coord[i]);
	  }
	}
      } else {
	el_info->neigh[i] = NULL;
      }
    }
  }

  if (el_info->fill_flag & FILL_BOUND) {

    if (!mesh->is_periodic) {
      for (i = 0; i < N_VERTICES_3D; i++) {
	BNDRY_FLAGS_CPY(el_info->vertex_bound[i], mel->vertex_bound[i]);
      }

      for (i = 0; i < N_EDGES_3D; i++) {
	BNDRY_FLAGS_CPY(el_info->edge_bound[i], mel->edge_bound[i]);
      }
      for (i = 0; i < N_WALLS_3D; i++) {
	el_info->face_bound[i] = el_info->wall_bound[i] = mel->wall_bound[i];
      }
    } else {
      /* Support periodic and non-periodic neighbourhood and boundary
       * information on periodic meshes.
       */
      if (el_info->fill_flag & FILL_NON_PERIODIC) {
	/* use non-periodic boundary information */
	for (i = 0; i < N_VERTICES_3D; i++) {
	  BNDRY_FLAGS_CPY(el_info->vertex_bound[i], mel->np_vertex_bound[i]);
	}
	for (i = 0; i < N_EDGES_3D; i++) {
	  BNDRY_FLAGS_CPY(el_info->edge_bound[i], mel->np_edge_bound[i]);
	}
	for (i = 0; i < N_WALLS_3D; i++) {
	  el_info->face_bound[i] = el_info->wall_bound[i] = mel->wall_bound[i];
	}
      } else {
	for (i = 0; i < N_VERTICES_3D; i++) {
	  BNDRY_FLAGS_CPY(el_info->vertex_bound[i], mel->vertex_bound[i]);
	}

	for (i = 0; i < N_EDGES_3D; i++) {
	  BNDRY_FLAGS_CPY(el_info->edge_bound[i], mel->edge_bound[i]);
	}
	for (i = 0; i < N_WALLS_3D; i++) {
	  if (mel->neigh_vertices[i][0] == -1) {
	    el_info->face_bound[i] =
	      el_info->wall_bound[i] = mel->wall_bound[i];
	  } else {
	    el_info->face_bound[i] =
	      el_info->wall_bound[i] = INTERIOR; /* periodic boundary */
	  }
	}

      }
    }
  }

  if(el_info->fill_flag & FILL_PROJECTION) {
    el_info->active_projection = mel->projection[0];

    /* If we have projection functions for faces, these take precedence! */
    if (mel->projection[3])
      el_info->active_projection = mel->projection[3];
    else if (mel->projection[4])
      el_info->active_projection = mel->projection[4];
  }

  if (el_info->fill_flag & FILL_ORIENTATION) {
    el_info->orientation = mel->orientation;
  }
}

/*--------------------------------------------------------------------------*/
/*   fill_elinfo_3d:                                   		            */
/*   ------------                                       		    */
/*   fill EL_INFO structure for one child of an element   		    */
/*--------------------------------------------------------------------------*/

static void fill_elinfo_3d(int ichild, FLAGS mask,
			   const EL_INFO *el_info_old, EL_INFO *el_info)
{
  FUNCNAME("fill_el_info_3d");
  int    i, k;
  int    el_type = 0;             /* el_type in {0,1,2}    */
  int    ochild = 0;              /* index of other child = 1-ichild    */
  const int *cv = NULL;           /* cv = child_vertex_3d[el_type][ichild] */
  const int (*cvg)[4] = {NULL};   /* cvg = child_vertex_3d[el_type] */
  const int *ce;                  /* ce = child_edge_3d[el_type][ichild] */
  int    iedge;
  EL     *nb, *nbk, **neigh_old;
  EL     *el_old = el_info_old->el;
  FLAGS  fill_flag = el_info_old->fill_flag & mask;
  DOF    *dof;
  int    ov;
  EL     **neigh;
  FLAGS  fill_opp_coords;
  S_CHAR *opp_vertex;

  DEBUG_TEST_EXIT(el_old->child[0], "missing child?\n");  /* Kuni 22.08.96 */

  el_info->el        = el_old->child[ichild];
  el_info->macro_el  = el_info_old->macro_el;
  el_info->fill_flag = fill_flag;
  el_info->mesh      = el_info_old->mesh;
  el_info->parent    = el_info_old;
  el_info->level     = el_info_old->level + 1;
  el_info->el_type   = (el_info_old->el_type + 1) % 3;

  DEBUG_TEST_EXIT(el_info->el, "missing child %d?\n", ichild);

  if (fill_flag) {
    el_type = el_info_old->el_type;
    cvg = child_vertex_3d[el_type];
    cv = cvg[ichild];
    ochild = 1-ichild;
    /* Fill the boundary mapping to the macro element in any case, it
     * is cheap enough. Maybe introduce a new fill-flag later
     * on. FILL_MACRO_WALLS or so.
     */
    el_info->macro_wall[0] = -1;
    el_info->macro_wall[1] = el_info_old->macro_wall[cv[1]];
    el_info->macro_wall[2] = el_info_old->macro_wall[cv[2]];
    el_info->macro_wall[3] = el_info_old->macro_wall[ochild];
  }

  if (fill_flag & FILL_COORDS) {
    for (i=0; i<3; i++) {
      COPY_DOW(el_info_old->coord[cv[i]], el_info->coord[i]);
    }
    if (el_old->new_coord)
      COPY_DOW(el_old->new_coord, el_info->coord[3]);
    else
      AXPBY_DOW(0.5, el_info_old->coord[0], 0.5, el_info_old->coord[1],
		el_info->coord[3]);
  }

  if (fill_flag & (FILL_NEIGH | FILL_OPP_COORDS)) {

    neigh      = el_info->neigh;
    neigh_old  = (EL **)el_info_old->neigh;
    opp_vertex = el_info->opp_vertex;
    fill_opp_coords = (fill_flag & FILL_OPP_COORDS);

    /*----- nb[0] is other child --------------------------------------------*/

    /*    if (nb = el_old->child[ochild])   old version  */
    if (el_old->child[0] && (nb = el_old->child[ochild])) { /*Kuni 22.08.96*/

      if (nb->child[0]) {     /* go down one level for direct neighbour */
	if (fill_opp_coords) {
	  if (nb->new_coord) {
	    /* no wall-trafo, nb is child of parent */
	    COPY_DOW(nb->new_coord, el_info->opp_coord[0]);
	  } else {
	    k = cvg[ochild][1];
	    AXPBY_DOW(0.5, el_info_old->coord[ochild],
		      0.5, el_info_old->coord[k],
		      el_info->opp_coord[0]);
	  }
	}
	neigh[0]      = nb->child[1];
	opp_vertex[0] = 3;
      } else {
	if (fill_opp_coords) {
	  COPY_DOW(el_info_old->coord[ochild], el_info->opp_coord[0]);
	}
	neigh[0]      = nb;
	opp_vertex[0] = 0;
      }
    } else {
      ERROR_EXIT("no other child");
      neigh[0] = NULL;
    }

    /*----- nb[1],nb[2] are childs of old neighbours nb_old[cv[i]] ----------*/

    for (i = 1; i < 3; i++) { 
      if ((nb = neigh_old[cv[i]])) {
	DEBUG_TEST_EXIT(nb->child[0], "nonconforming triangulation\n");

	for (k = 0; k < 2; k++) { /* look at both childs of old neighbour */
	  nbk = nb->child[k];
	  if (nbk->dof[0][0] == el_old->dof[ichild][0]) {
	    dof = nb->dof[el_info_old->opp_vertex[cv[i]]]; /* opp. vertex */
	    if (dof[0] == nbk->dof[1][0]) {
	      ov = 1;
	      if (nbk->child[0]) {
		/* go down one level */
		if (fill_opp_coords) {
		  if (nbk->new_coord) {
		    const AFF_TRAFO *wt;
		    if ((wt = wall_trafo(el_info_old, cv[i])) != NULL) {
		      AFFINE_DOW(wt, nbk->new_coord, el_info->opp_coord[i]);
		    } else {
		      COPY_DOW(nbk->new_coord, el_info->opp_coord[i]);
		    }
		  } else {
		    AXPBY_DOW(0.5, el_info_old->opp_coord[cv[i]],
			      0.5, el_info_old->coord[ichild],
			      el_info->opp_coord[i]);
		  }
		}
		neigh[i]      = nbk->child[0];
		opp_vertex[i] = 3;
		break;
	      }
	    } else {
	      DEBUG_TEST_EXIT(dof[0] == nbk->dof[2][0],
			      "opp_vertex not found\n");
	      ov = 2;
	    }

	    if (fill_opp_coords) {
	      COPY_DOW(el_info_old->opp_coord[cv[i]], el_info->opp_coord[i]);
	    }
	    neigh[i]      = nbk;
	    opp_vertex[i] = ov;
	    break;
	  }
	  
	} /* end for k */
	DEBUG_TEST_EXIT(k<2, "child not found with vertex\n");
      } else {
	neigh[i] = NULL;
      }
    }  /* end for i */


    /*----- nb[3] is old neighbour neigh_old[ochild] ------------------------*/

    if ((neigh[3] = neigh_old[ochild])) {
      opp_vertex[3] = el_info_old->opp_vertex[ochild];
      if (fill_opp_coords) {
	COPY_DOW(el_info_old->opp_coord[ochild], el_info->opp_coord[3]);
      }
    }
  }

  if (fill_flag & FILL_BOUND) {
    for (i = 0; i < 3; i++) {
      BNDRY_FLAGS_CPY(el_info->vertex_bound[i],
		      el_info_old->vertex_bound[cv[i]]);
    }
    BNDRY_FLAGS_CPY(el_info->vertex_bound[3], el_info_old->edge_bound[0]);

    el_info->face_bound[0] =
      el_info->wall_bound[0] = INTERIOR;
    el_info->face_bound[1] =
      el_info->wall_bound[1] = el_info_old->wall_bound[cv[1]];
    el_info->face_bound[2] =
      el_info->wall_bound[2] = el_info_old->wall_bound[cv[2]];
    el_info->face_bound[3] = 
      el_info->wall_bound[3] = el_info_old->wall_bound[ochild];

    ce = child_edge_3d[el_type][ichild];
    for (iedge=0; iedge<4; iedge++) {
      BNDRY_FLAGS_CPY(el_info->edge_bound[iedge],
		      el_info_old->edge_bound[ce[iedge]]);
    }
    for (iedge = 4; iedge < 6; iedge++) {
      i = 5 - cv[iedge-3];                /* old vertex opposite new edge */
      BNDRY_FLAGS_INIT(el_info->edge_bound[iedge]);
      BNDRY_FLAGS_SET(el_info->edge_bound[iedge], el_info_old->wall_bound[i]);
    }
  }

  if(fill_flag & FILL_PROJECTION) {
    if (wall_proj(el_info, 2))
      el_info->active_projection = wall_proj(el_info, 2);
    else if (wall_proj(el_info, 3))
      el_info->active_projection = wall_proj(el_info, 3);
    else
      el_info->active_projection = wall_proj(el_info, -1);
  }

  if (el_info->fill_flag & FILL_ORIENTATION) {
    el_info->orientation =
      el_info_old->orientation * child_orientation_3d[el_type][ichild];
  }
}

/*--------------------------------------------------------------------------*/
/*   AI_update_elinfo_3d:                             		            */
/*   --------------                                       		    */
/*   update EL_INFO structure after refinement (of some neighbours)	    */
/*--------------------------------------------------------------------------*/

void AI_update_elinfo_3d(EL_INFO *el_info)
{
  FUNCNAME("AI_update_el_info_3d");
  EL      *el = el_info->el;

  DEBUG_TEST_EXIT(el, "missing element?\n");

  if (el_info->fill_flag & (FILL_NEIGH | FILL_OPP_COORDS))
  {
    EL    *nb;
    int   ineigh, ov, j, k;
    FLAGS fill_opp_coords = (el_info->fill_flag & FILL_OPP_COORDS);
    
    for (ineigh=0; ineigh < N_NEIGH_3D; ineigh++) {
      if ((nb = el_info->neigh[ineigh])) {
	ov = el_info->opp_vertex[ineigh];
	if (ov < 2 && nb->child[0]) {
	  if (fill_opp_coords) {
	    k = -1;
	    for (j=0; j<N_VERTICES_3D; j++)
	      if (el->dof[j][0] == nb->dof[1-ov][0]) k = j;
	    DEBUG_TEST_EXIT(k >= 0, "neighbour dof not found\n");

	    if (nb->new_coord)
	      for (j = 0; j < DIM_OF_WORLD; j++)
		el_info->opp_coord[ineigh][j] = nb->new_coord[j];
	    else
	      for (j = 0; j < DIM_OF_WORLD; j++)
		el_info->opp_coord[ineigh][j] = 
		  (el_info->opp_coord[ineigh][j] + el_info->coord[k][j]) / 2;
	  }
	  el_info->neigh[ineigh]      = nb->child[1-ov];
	  el_info->opp_vertex[ineigh] = 3;
	}
      }
    }
  }
}
