/*--------------------------------------------------------------------------*/
/* 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:     refine_3d.c                                                    */
/*                                                                          */
/* description:  recursive refinement of 3 dim. hierarchical meshes;        */
/*               implementation of the newest vertex bisection              */
/*               file contains all routines depending on DIM == 3;          */
/*                                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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                                      */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003),                         */
/*         C.-J. Heine (2006-2007).                                         */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/* Compute new coordinates for affine parametric meshes                     */
/*--------------------------------------------------------------------------*/

static void new_coords_3d(RC_LIST_EL ref_list[], int n_neigh)
{
  static const REAL_B mid_bary = { 0.5, 0.5, 0.0, 0.0};
  EL_INFO *el_info = &ref_list->el_info; /* first element in the list */
  EL      *el = el_info->el;
#if ALBERTA_DEBUG
  EL      *old_el = el;
#endif
  REAL    *new_coord;
  DOF     *v0_dof;
  int     i;

  if (el->new_coord) {
    return; /* cH: can this happen? */
  }

  /* It can be that only some elements of the patch define a
   * projection for the refinement edge, so we really need to loop
   * around the entire patch to be sure that we don't miss a
   * projection.
   */
  for (i = 0; i < n_neigh; i++) {
    EL_INFO *el_info = &ref_list[i].el_info;
    if (el_info->active_projection && el_info->active_projection->func) {
      break;
    }
  }
  
  if (i < n_neigh) { /* need new co-ordinates */

    v0_dof = el->dof[0];

    el->new_coord = new_coord = get_real_d(el_info->mesh);
    AXPBY_DOW(0.5, el_info->coord[0], 0.5,  el_info->coord[1], el->new_coord);
    if (el_info->active_projection && el_info->active_projection->func) {
      el_info->active_projection->func(el->new_coord, el_info, mid_bary);
      _AI_refine_update_bbox(el_info->mesh, new_coord);
    }

/*--------------------------------------------------------------------------*/
/*  now, information should be passed on to patch neighbours...             */
/*--------------------------------------------------------------------------*/

    for (i = 1; i < n_neigh; i++) { /* start with 1, as list[0] == el */

      el_info = &ref_list[i].el_info;
      el = el_info->el;

      DEBUG_TEST(
	el->new_coord == NULL,
	"non-NULL new_coord in el %d ref_list[%d] el %d (n_neigh=%d)\n",
	INDEX(old_el), i, INDEX(el), n_neigh);

      if (el->dof[0] != v0_dof && el->dof[1] != v0_dof) {
	/* periodic neighbour */

	v0_dof = el->dof[0];

	new_coord = get_real_d(el_info->mesh);
	AXPBY_DOW(0.5, el_info->coord[0], 0.5,  el_info->coord[1], new_coord);
      }
      el->new_coord = new_coord;
      if (el_info->active_projection && el_info->active_projection->func) {
	el_info->active_projection->func(el->new_coord, el_info, mid_bary);
	_AI_refine_update_bbox(el_info->mesh, new_coord);
      }
    }
  }
}

/*--------------------------------------------------------------------------*/
/*  sets neighbour connectivity inside the refinement/coarsening patch      */
/*--------------------------------------------------------------------------*/

void AI_set_neighs_on_patch_3d(RC_LIST_EL ref_list[], int n_neigh, int bound)
{
  FUNCNAME("AI_set_neighs_on_patch_3d");
  int      i, j, k, dir;
  EL       *el, *neigh;

  for (i = 0; i < n_neigh; i++)
  {
    el = ref_list[i].el_info.el;
    ref_list[i].no = i;

    for (dir = 0; dir < 2; dir++)
    {
      for (j = 0; j < n_neigh; j++)
      {
	if ((neigh = ref_list[j].el_info.el) == el)  continue;

	for (k = 0; k < 2; k++)
	{
	  if (neigh->dof[2+k][0] == el->dof[3-dir][0])
	  {
	    ref_list[i].neigh[dir] = ref_list+j;
	    ref_list[i].opp_vertex[dir] = 3-k;
	    break;
	  }
	}

	if (k < 2) break;
      }

      if (j >= n_neigh)
      {
	DEBUG_TEST_EXIT(
	  bound, "neighbour of element %d in list not found\n", INDEX(el));
	ref_list[i].neigh[dir] = NULL;
	ref_list[i].opp_vertex[dir] = -1;
      }
    }
  }
  return;
}

static void  fill_patch_connectivity_3d(MESH *mesh, RC_LIST_EL *ref_list)
{
  FUNCNAME("fill_patch_connectivity_3d");
  EL      *el = ref_list->el_info.el, *neigh;
  int     dir, n_type = 0;
  int     el_type = ref_list->el_info.el_type;
  int     adjc, i, j, i_neigh, j_neigh;
  int     node0, node1, opp_v = 0;
  int     is_periodic;

  for (dir = 0; dir < 2; dir++) {
    if (ref_list->neigh[dir]) {
      neigh = ref_list->neigh[dir]->el_info.el;
      n_type = ref_list->neigh[dir]->el_info.el_type;
      opp_v = ref_list->opp_vertex[dir];
    } else {
      neigh = NULL;
    }

    if (!neigh || !neigh->child[0]) {
/*--------------------------------------------------------------------------*/
/*  get new dof's in the midedge of the face of el and for the two midpoints*/
/*  of the sub-faces. If face is an interior face those pointers have to be */
/*  adjusted by the neighbour element also (see below)                      */
/*--------------------------------------------------------------------------*/

      if (mesh->n_dof[EDGE]) {
	node0 = node1 = mesh->node[EDGE];
	node0 += n_child_edge_3d[el_type][0][dir];
	node1 += n_child_edge_3d[el_type][1][dir];
	el->child[0]->dof[node0] = el->child[1]->dof[node1] =
	  get_dof(mesh, EDGE);
      }
      if (mesh->n_dof[FACE]) {
	node0 = mesh->node[FACE] + n_child_face_3d[el_type][0][dir];
	el->child[0]->dof[node0] = get_dof(mesh, FACE);
	node1 = mesh->node[FACE] + n_child_face_3d[el_type][1][dir];
	el->child[1]->dof[node1] = get_dof(mesh, FACE);
      }
    } else { /*   if (!neigh  ||  !neigh->child[0]) */

/*--------------------------------------------------------------------------*/
/*  interior face and neighbour has been refined, look for position at the  */
/*  refinement edge                                                         */
/*--------------------------------------------------------------------------*/

      is_periodic = false;
      if (el->dof[0] == neigh->dof[0]) {
	/* same position at refinement edge */
	adjc = 0;
      } else if (el->dof[0] == neigh->dof[1]) {
	/* different position at refinement edge */
	adjc = 1;
      } else {
	is_periodic = true;

	if (el->dof[0][0] == neigh->dof[0][0]) {
	  /* same position at refinement edge */
	  adjc = 0;
	} else {
	  /* different position at refinement edge */
	  adjc = 1;
	}
      }

      if (is_periodic) {
	++mesh->n_faces;
	++mesh->n_edges; 
      }

      for (i = 0; i < 2; i++) {
	j = adjacent_child_3d[adjc][i];

	i_neigh = n_child_face_3d[el_type][i][dir];
	j_neigh = n_child_face_3d[n_type][j][opp_v-2];

/*--------------------------------------------------------------------------*/
/*  adjust dof pointer in the edge in the common face of el and neigh and   */
/*  the dof pointer in the sub-face child_i-child_j (allocated by neigh!)   */
/*--------------------------------------------------------------------------*/

	if (mesh->n_dof[EDGE]) {
	  node0 = mesh->node[EDGE] + n_child_edge_3d[el_type][i][dir];

	  if (!is_periodic) {
	    node1 = mesh->node[EDGE] + n_child_edge_3d[n_type][j][opp_v-2];

	    DEBUG_TEST_EXIT(neigh->child[j]->dof[node1],
			    "no dof on neighbour %d at node %d\n",
			    INDEX(neigh->child[j]), node1);

	    el->child[i]->dof[node0] = neigh->child[j]->dof[node1];
	  } else {
	    if (i == 0) {
	      node1 = mesh->node[EDGE] + n_child_edge_3d[n_type][j][opp_v-2];
	      
	      DEBUG_TEST_EXIT(neigh->child[j]->dof[node1],
			      "no dof on neighbour %d at node %d\n",
			      INDEX(neigh->child[j]), node1);

	      el->child[0]->dof[node0] =
		get_periodic_dof(mesh, EDGE, neigh->child[j]->dof[node1]);
	    } else {
	      node1 = mesh->node[EDGE] + n_child_edge_3d[el_type][0][dir];

	      DEBUG_TEST_EXIT(el->child[0]->dof[node1],
			      "no dof on child 0 (%d) at node %d\n",
			      INDEX(el->child[0]), node1);

	      el->child[1]->dof[node0] = el->child[0]->dof[node1];
	    }
	  }
	}

	if (mesh->n_dof[FACE]) {

	  node0 = mesh->node[FACE] + i_neigh;
	  node1 = mesh->node[FACE] + j_neigh;

	  DEBUG_TEST_EXIT(neigh->child[j]->dof[node1],
		      "no dof on neighbour %d at node %d\n",
		      INDEX(neigh->child[j]), node1);

	  if (!is_periodic) {
	    el->child[i]->dof[node0] = neigh->child[j]->dof[node1];
	  } else {
	    el->child[i]->dof[node0] =
	      get_periodic_dof(mesh, FACE, neigh->child[j]->dof[node1]);
	  }
	}

      }  /*   for (i = 0; i < 2; i++)                                       */
    }    /*   else of   if (!neigh  ||  !neigh->child[0])                   */
  }      /*   for (dir = 0; dir < 2; dir++)                                 */

  return;
}

static void bisect_element_3d(MESH *mesh, RC_LIST_EL *ref_list,
			      DOF *dof[3], DOF *edge[2])
{
  EL   *el = ref_list->el_info.el, *child[3];
  int  i, node;
  int  el_type = ref_list->el_info.el_type;
  
  child[0] = get_element(mesh);
  child[1] = get_element(mesh);
  child[2] = el;

  child[0]->mark = child[1]->mark = MAX(0, el->mark-1);
  el->mark = 0;

/*--------------------------------------------------------------------------*/
/*  transfer user's leaf-data from parent to children                       */
/*--------------------------------------------------------------------------*/

  if (el->child[1]) {
    void (*refine_leaf_data)(EL *parent, EL *child[2]) =
      ((MESH_MEM_INFO *)mesh->mem_info)->leaf_data_info->refine_leaf_data;
    if (refine_leaf_data) {
      refine_leaf_data(el, child);
    }
    AI_free_leaf_data((void *) el->child[1], mesh);
  }

  el->child[0] = child[0];
  el->child[1] = child[1];

  if (child[0]->mark > 0)  do_more_refine_3d = true;

  child[0]->dof[N_VERTICES_3D-1] = child[1]->dof[N_VERTICES_3D-1] = dof[0];
  for (i = 0; i < N_VERTICES_3D-1; i++) {
    child[0]->dof[i] = el->dof[child_vertex_3d[el_type][0][i]];
    child[1]->dof[i] = el->dof[child_vertex_3d[el_type][1][i]];
  }
/*--------------------------------------------------------------------------*/
/*  there is one more leaf element and two more hierachical elements        */
/*--------------------------------------------------------------------------*/

  mesh->n_elements++;
  mesh->n_hier_elements +=2;

/*--------------------------------------------------------------------------*/
/* first set those dof pointers for higher order without neighbour          */
/* information                                                              */
/*--------------------------------------------------------------------------*/

  if (mesh->n_dof[EDGE]) {
    node = mesh->node[EDGE];

/*--------------------------------------------------------------------------*/
/*  set pointers to those dof's that are handed on from the parant          */
/*--------------------------------------------------------------------------*/

    child[0]->dof[node] = el->dof[node+child_edge_3d[el_type][0][0]];
    child[1]->dof[node] = el->dof[node+child_edge_3d[el_type][1][0]];
    child[0]->dof[node+1] = el->dof[node+child_edge_3d[el_type][0][1]];
    child[1]->dof[node+1] = el->dof[node+child_edge_3d[el_type][1][1]];
    child[0]->dof[node+3] = el->dof[node+child_edge_3d[el_type][0][3]];
    child[1]->dof[node+3] = el->dof[node+child_edge_3d[el_type][1][3]];

/*--------------------------------------------------------------------------*/
/*  adjust pointers to the dof's in the refinement edge                     */
/*--------------------------------------------------------------------------*/

    if (el->dof[0][0] == edge[0][0])
    {
      child[0]->dof[node+2] = dof[1];
      child[1]->dof[node+2] = dof[2];
    } else {
      child[0]->dof[node+2] = dof[2];
      child[1]->dof[node+2] = dof[1];
    }
  }

  if (mesh->n_dof[FACE]) {
    node = mesh->node[FACE];

/*--------------------------------------------------------------------------*/
/*  set pointers to those dof's that are handed on from the parent          */
/*--------------------------------------------------------------------------*/

    child[0]->dof[node+3] = el->dof[node+1];
    child[1]->dof[node+3] = el->dof[node+0];

/*--------------------------------------------------------------------------*/
/*  get new dof for the common face of child0 and child1                    */
/*--------------------------------------------------------------------------*/

    child[0]->dof[node] = child[1]->dof[node] = get_dof(mesh, FACE);
  }

  if (mesh->n_dof[CENTER]) {
    node = mesh->node[CENTER];
    child[0]->dof[node] = get_dof(mesh, CENTER);
    child[1]->dof[node] = get_dof(mesh, CENTER);
  }

  if (mesh->is_periodic || mesh->n_dof[EDGE] || mesh->n_dof[FACE]) {
    fill_patch_connectivity_3d(mesh, ref_list);
  }
  
  return;
}

/*--------------------------------------------------------------------------*/
/*  remove dof pointers from parent                                         */
/*--------------------------------------------------------------------------*/

static void remove_dof_parent_3d(MESH *mesh, RC_LIST_EL *ref_list)
{
  EL         *el = ref_list->el_info.el;
  RC_LIST_EL *neigh;
  int        node, dir;

  if (mesh->n_dof[FACE]) {
    node = mesh->node[FACE];

    for (dir = 0; dir < 2; dir++) {
      neigh = ref_list->neigh[dir];
      if (!neigh || (neigh > ref_list)) {
	/* face  2+dir */
	free_dof(el->dof[node+2+dir], mesh, FACE, ADM_PRESERVE_COARSE_DOFS);
      } else if (neigh && _AI_rc_list_periodic_neigh_p(ref_list, neigh)) {
	/* periodic boundary */
	free_dof(el->dof[node+2+dir], mesh, FACE,
		 ADM_PRESERVE_COARSE_DOFS|(neigh < ref_list ? ADM_PERIODIC:0));
      }
    }
  }

  if (mesh->n_dof[CENTER]) {
    node = mesh->node[CENTER];

    free_dof(el->dof[node], mesh, CENTER, ADM_PRESERVE_COARSE_DOFS);
  }
  return;
}

/* If we have non-periodic DOF_ADMINs on a periodic mesh
 * (e.g. coordinate vectors for parametric meshes) we have to split
 * the ref-list into its connectivity components.
 *
 * On the first call "orig" is non-NULL, is copied over to
 * "copy". Successive calls should pass "NULL" as orig. "copy" will
 * always hold the next connectivity component.
 */
int AI_split_rc_list_3d(RC_LIST_EL *orig, RC_LIST_EL *copy, int n_left)
{
  int n = 0, i, dir;
  RC_LIST_EL *ptr;

  if (orig) {
    RC_LIST_EL *last;
    char *offset = (char *)((char *)orig - (char *)copy);

    for (i = 0; i < n_left; i++) {
      copy[i] = orig[i];
      /* adjust neighbour pointers */
      for (dir = 0; dir < 2; dir++) {
	if (copy[i].neigh[dir]) {
	  copy[i].neigh[dir] =
	    (RC_LIST_EL *)((char *)copy[i].neigh[dir] - offset);
	}
      }
    }
    /* One neighbour of the first element is either NULL or points to
     * the last element of the patch.
     */
    last = copy + n_left - 1;
    for (dir = 0; dir < 2; dir++) {
      if (copy->neigh[dir] == last) {
	copy->neigh[dir] = NULL;
      }
      if (last->neigh[dir] == copy) {
	last->neigh[dir] = NULL;
      }
    }
  } else {
    /* Terminate the patch towards the original base */
    if (copy->neigh[0] == copy-1) {
      copy->neigh[0] = NULL;
    } else if (copy->neigh[1] == copy-1) {
      copy->neigh[1] = NULL;
    }
  }

  ptr = copy;
  ptr->no = 0;
  n = 1;
  while (--n_left) {
    if (_AI_rc_list_periodic_neigh_p(ptr, ptr+1)) {
      if (ptr->neigh[0] == ptr+1) {
	ptr->neigh[0] = NULL;
      } else if (ptr->neigh[1] == ptr+1) {
	ptr->neigh[1] = NULL;
      }
      break;
    }
    (++ptr)->no = n++;
  }
  return n;
}

static void bisect_patch_3d(MESH *mesh, DOF *edge[2], RC_LIST_EL ref_list[],
			    int n_neigh, int bound, int is_periodic)
{
  int i;
  EL  *el = ref_list->el_info.el;  /*    first element in the list       */
  DOF *dof[3] = {NULL, NULL, NULL};
  int *n_dof = mesh->n_dof;

/*--------------------------------------------------------------------------*/
/*  get new dof's in the refinement edge                                    */
/*--------------------------------------------------------------------------*/

  dof[0] = get_dof(mesh, VERTEX);
  mesh->n_vertices++;
  mesh->per_n_vertices++;

  if (n_dof[EDGE]) {
    dof[1] = get_dof(mesh, EDGE);
    dof[2] = get_dof(mesh, EDGE);
  }

  if (!is_periodic) {
    for (i = 0; i < n_neigh; i++) {
      bisect_element_3d(mesh, ref_list+i, dof, edge);
    }
  } else {
    /* get_refine_patch_3d() makes sure that the refinement patch is
     * sorted and aligned to periodic walls.
     */
    bisect_element_3d(mesh, ref_list, dof, edge);
    for (i = 1; i < n_neigh; i++) {
      if (_AI_rc_list_periodic_wall_p(ref_list+i, edge)) {
	/* Allocate new DOFs pointers and new DOF indices for
	 * non-periodic DOF_ADMINS.
	 *
	 * We also update the n_vertices/edges/faces counts here;
	 * periodic sub-simplexes count as normal sub-simplexes.
	 */
	dof[0] = get_periodic_dof(mesh, VERTEX, dof[0]);
	if (mesh->n_dof[EDGE]) {
	  dof[1] = get_periodic_dof(mesh, EDGE, dof[1]);
	  dof[2] = get_periodic_dof(mesh, EDGE, dof[2]);
	  mesh->n_edges ++;
	}
	mesh->n_vertices += 1;
      }
      DEBUG_TEST_EXIT(edge[0][0] < edge[1][0],
		      "Wrong orientation of ref-edge.\n");
      bisect_element_3d(mesh, ref_list+i, dof, edge);
    }
  }

/*--------------------------------------------------------------------------*/
/*  if there are functions to interpolate data to the finer grid, do so     */
/*--------------------------------------------------------------------------*/
  if (call_refine_interpol_3d)
    refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist,
		    ref_list, n_neigh);

  if (call_refine_interpol_np_3d) {
    if (is_periodic) {
      RC_LIST_EL *ref_list_np = get_rc_list(mesh);
      int n_neigh_np, n_left = n_neigh;
    
      n_neigh_np = AI_split_rc_list_3d(ref_list, ref_list_np, n_left);
      do {
	refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
			ref_list_np + n_neigh - n_left, n_neigh_np);
	if ((n_left -= n_neigh_np)) {
	  n_neigh_np =
	    AI_split_rc_list_3d(NULL, ref_list_np + n_neigh - n_left, n_left);
	}
      } while (n_left);
    
      free_rc_list(mesh, ref_list_np);
    } else {
      refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		      ref_list, n_neigh);
    }
  }

/*--------------------------------------------------------------------------*/
/*  if there should be no dof information on interior leaf elements remove  */
/*  dofs from edges, faces and the centers of parents                       */
/*--------------------------------------------------------------------------*/
  if (n_dof[EDGE]) {
/*--------------------------------------------------------------------------*/
/*  remove dof from the midpoint of the common refinement edge              */
/*--------------------------------------------------------------------------*/
    DOF *dof;

    el  = ref_list->el_info.el;
    dof = el->dof[mesh->node[EDGE]];
    free_dof(dof, mesh, EDGE, ADM_PRESERVE_COARSE_DOFS);
    if (is_periodic) {
      for (i = 1; i < n_neigh; i++) {
	el  = ref_list[i].el_info.el;
	if (el->dof[mesh->node[EDGE]] != dof) {
	  dof = el->dof[mesh->node[EDGE]];
	  free_dof(dof, mesh, EDGE, ADM_PRESERVE_COARSE_DOFS|ADM_PERIODIC);
	}
      }
    }
  }

  if (n_dof[EDGE]  ||  n_dof[FACE]  ||  n_dof[CENTER])
    for (i = 0; i < n_neigh; i++)
      remove_dof_parent_3d(mesh, ref_list+i);

/*--------------------------------------------------------------------------*/
/*  update the number of edges and faces; depends whether refinement edge   */
/*  is a boundary edge or not                                               */
/*--------------------------------------------------------------------------*/

  mesh->n_edges += n_neigh + 1 + !!bound;
  mesh->per_n_edges += n_neigh + 1 + !!bound;
  mesh->n_faces += 2*n_neigh + !!bound;
  mesh->per_n_faces += 2*n_neigh + !!bound;

  return;
}

static const EL_INFO *
refine_function_3d(const EL_INFO *el_info, TRAVERSE_STACK *stack);

/*--------------------------------------------------------------------------*/
/*  get_refine_patch:                                                       */
/*  gets the elements around the refinement edge with vertices  node[0] and */
/*  node[1] ; refines those elements at this edge which are not compatible  */
/*  devisible;  			                                    */
/*  dir determines the direction of the first neighbour			    */
/*  We write 1 into "boundary_reached" if the domain's boundary was reached */
/*  while looping around the refinement edge, otherwise 0.                  */
/*  The return value is an EL_INFO pointer, since traverse_neighbour() could*/
/*  change the traverse stack.                                              */
/*--------------------------------------------------------------------------*/

static const EL_INFO *get_refine_patch_3d(const EL_INFO *el_info,
					  DOF *edge[2], int dir,
					  RC_LIST_EL ref_list[], int *n_neigh,
					  int *boundary_reached,
					  int *is_periodic,
					  TRAVERSE_STACK *stack)
{
  FUNCNAME("get_refine_patch_3d");
#if ALBERTA_DEBUG
  MESH          *mesh = el_info->mesh;
#endif
  const EL_INFO *neigh_info;
  EL            *el, *neigh;
  int           i, j, k, opp_v;
  int           edge_no, neigh_el_type;

  el = el_info->el;

  if (el_info->neigh[3-dir] == NULL) {
    *boundary_reached = true;
    return el_info;
  }

  opp_v         = el_info->opp_vertex[3-dir];
  neigh_info    = traverse_neighbour(stack, el_info, 3-dir);
  neigh_el_type = neigh_info->el_type;
  neigh         = neigh_info->el;

  while (neigh != el) {
    for (j = 0; j < N_VERTICES_3D; j++)
      if (neigh->dof[j][0] == edge[0][0])  break;
    for (k = 0; k < N_VERTICES_3D; k++)
      if (neigh->dof[k][0] == edge[1][0])  break;

    DEBUG_TEST_EXIT(j < N_VERTICES_3D  &&  k < N_VERTICES_3D,
       "dof %d or dof %d not found on element %d with nodes (%d %d %d %d)\n",
       edge[0][0], edge[1][0], INDEX(neigh), neigh->dof[0][0],
       neigh->dof[1][0], neigh->dof[2][0], neigh->dof[3][0]);

    if (neigh->dof[j] != edge[0]) {
      *is_periodic = true;
    }

    if ((edge_no = edge_of_vertices_3d[j][k])) {
/*--------------------------------------------------------------------------*/
/*  neigh has not a compatible refinement edge                              */
/*--------------------------------------------------------------------------*/
      neigh->mark = MAX(neigh->mark, 1);
      neigh_info = refine_function_3d(neigh_info, stack);

/*--------------------------------------------------------------------------*/
/*  now, go to a child at the edge and determine the opposite vertex for    */
/*  this child; continue the looping around the edge with this element      */
/*--------------------------------------------------------------------------*/

      neigh_info = traverse_next(stack, neigh_info);
      neigh_el_type = neigh_info->el_type;

      switch (edge_no)
      {
      case 1:
	opp_v = opp_v == 1 ? 3 : 2;
	break;
      case 2:
	opp_v = opp_v == 2 ? 1 : 3;
	break;
      case 3:
	neigh_info = traverse_next(stack, neigh_info);
	neigh_el_type = neigh_info->el_type;

	if (neigh_el_type != 1)
	  opp_v = opp_v == 0 ? 3 : 2;
	else
	  opp_v = opp_v == 0 ? 3 : 1;
	break;
      case 4:
	neigh_info = traverse_next(stack, neigh_info);
	neigh_el_type = neigh_info->el_type;

	if (neigh_el_type != 1)
	  opp_v = opp_v == 0 ? 3 : 1;
	else
	  opp_v = opp_v == 0 ? 3 : 2;
	break;
      case 5:
	if (neigh_el_type != 1)
	{
	  neigh_info = traverse_next(stack, neigh_info);
	  neigh_el_type = neigh_info->el_type;
	}
	opp_v = 3;
	break;
      }
      neigh = neigh_info->el;
    }
    else
    {
/*--------------------------------------------------------------------------*/
/*  neigh is compatibly divisible; put neigh to the list of patch elements; */
/*  go to next neighbour                                                    */
/*--------------------------------------------------------------------------*/
      DEBUG_TEST_EXIT(*n_neigh < mesh->max_edge_neigh,
	"too many neighbours %d in refine patch\n", *n_neigh);

      ref_list[*n_neigh].el_info = *neigh_info;
      ref_list[*n_neigh].flags   = RCLE_NONE;

/*--------------------------------------------------------------------------*/
/*  we have to go back to the starting element via opp_v values             */
/*  correct information is produced by AI_set_neighs_on_patch_3d()           */
/*--------------------------------------------------------------------------*/
      ref_list[*n_neigh].opp_vertex[0] = opp_v;
      ++*n_neigh;

      if (opp_v != 3)
	i = 3;
      else
	i = 2;

      if (neigh_info->neigh[i])
      {
	opp_v = neigh_info->opp_vertex[i];
	neigh_info = traverse_neighbour(stack, neigh_info, i);
	neigh_el_type = neigh_info->el_type;
	neigh = neigh_info->el;
      }
      else
	break;
    }
  }

  if (neigh == el)
  {
    el_info = neigh_info;
    *boundary_reached = false;
    return(el_info);
  }

/*--------------------------------------------------------------------------*/
/*  the domain's boundary is reached; loop back to the starting el          */
/*--------------------------------------------------------------------------*/

  i = *n_neigh-1;
  opp_v = ref_list[i].opp_vertex[0];
  do {
    DEBUG_TEST_EXIT(
      neigh_info->neigh[opp_v]  &&  i > 0,
      "while looping back domains boundary was reached or i == 0\n");
    opp_v = ref_list[i--].opp_vertex[0];
    neigh_info = traverse_neighbour(stack, neigh_info, opp_v);
  } while(neigh_info->el != el);
  el_info = neigh_info;

  *boundary_reached = true;
  return el_info;
}

/* To cope with periodic meshes we first invert the order of elements
 * of the first part of the ref-list if the boundary was reached; this
 * makes allocation of periodic DOFs much easier, because the "real"
 * connectivity components are grouped together in ref_list;
 * i.e. periodic walls are never crossed twice while looping around
 * the refinement edge.
 */
void AI_reverse_rc_list_3d(RC_LIST_EL ref_list[], int n_neigh, DOF *edge[2])
{
  RC_LIST_EL tmp;
  int i;

  for (i = 0; i < n_neigh/2; i++) {
    tmp                   = ref_list[i];
    ref_list[i]           = ref_list[n_neigh-i-1];
    ref_list[n_neigh-i-1] = tmp;
  }

  /* update edge to contain the DOFs of the first element in the list */
  if (ref_list[0].el_info.el->dof[0][0] == edge[0][0]) {
    edge[0] = ref_list[0].el_info.el->dof[0];
    edge[1] = ref_list[0].el_info.el->dof[1];
  } else {
    edge[0] = ref_list[0].el_info.el->dof[1];
    edge[1] = ref_list[0].el_info.el->dof[0];
  }

  return;
}

/* Rotate the given RC_LIST_EL until a periodic boundary is
 * reached. See the comment in front of reverse_rc_list().
 *
 * For periodic meshes get_rc_list() allocates enough memory such that
 * this is possible.
 *
 * The retrun value is the new base address (DO NOT PASS TO
 * free_rc_list()).
 *
 * We assume that this function is only called for periodic meshes and
 * that the patch does not hit a non-periodic boundary.
 */
RC_LIST_EL *AI_rotate_rc_list_3d(RC_LIST_EL *first, int n_neigh, DOF *edge[2])
{
  /* It can happen that accidentally the patch is already aligned to a
   * periodic boundary; check this first.
   */
  RC_LIST_EL *last = first + n_neigh - 1;
  if (_AI_rc_list_periodic_neigh_p(first, last)) {
    return first;
  }
  do {
    *(first + n_neigh) = *first;
  } while (!_AI_rc_list_periodic_wall_p(++first, edge));

  return first;
}

/*--------------------------------------------------------------------------*/
/*  refine_function_3d: gets the refinement patch via get_refine_patch_3d() */
/*  and checks whether it compatibly divisible (same refinement edge) or    */
/*  not; in the first case the patch is refined by refine_patch_3d() in the */
/*  second the incompatible neighbour(s) is (are) refined first via a       */
/*  recursive call of refine_function_3d() in get_refine_patch_3d().        */
/*--------------------------------------------------------------------------*/

static const EL_INFO *
refine_function_3d(const EL_INFO *el_info, TRAVERSE_STACK *stack)
{
  MESH       *mesh = el_info->mesh;
  int        n_neigh, bound = false, boundary_reached, is_periodic = false;
  DOF        *edge[2];
  int        n_vertices = mesh->n_vertices;
  int        n_edges = mesh->n_edges;
  int        n_faces = mesh->n_faces;  
  RC_LIST_EL *ref_list, *orig_ref_list;

  if (el_info->el->mark <= 0)
    return el_info;    /*   element may not be refined   */

/*--------------------------------------------------------------------------*/
/*  get memory for a list of all elements at the refinement edge            */
/*--------------------------------------------------------------------------*/
  ref_list = orig_ref_list = get_rc_list(mesh);
  ref_list->el_info = *el_info;
  ref_list->flags   = RCLE_NONE;
  n_neigh           = 1;

/*--------------------------------------------------------------------------*/
/*  give the refinement edge the right orientation                          */
/*--------------------------------------------------------------------------*/

  if (el_info->el->dof[0][0] < el_info->el->dof[1][0])
  {
    edge[0] = el_info->el->dof[0];
    edge[1] = el_info->el->dof[1];
  }
  else
  {
    edge[1] = el_info->el->dof[0];
    edge[0] = el_info->el->dof[1];
  }

/*--------------------------------------------------------------------------*/
/*  get the refinement patch                                                */
/*--------------------------------------------------------------------------*/
  el_info = get_refine_patch_3d(el_info, edge, 0, ref_list, &n_neigh,
				&boundary_reached, &is_periodic, stack);

  if (boundary_reached) {
    if (is_periodic) {
      AI_reverse_rc_list_3d(ref_list, n_neigh, edge);
    }
    el_info = get_refine_patch_3d(el_info, edge, 1, ref_list, &n_neigh,
				  &boundary_reached, &is_periodic, stack);
    bound = true;
  } else if (is_periodic) {
    ref_list = AI_rotate_rc_list_3d(ref_list, n_neigh, edge);
  }

/*--------------------------------------------------------------------------*/
/*  fill neighbour information inside the patch in the refinement list      */
/*--------------------------------------------------------------------------*/
  AI_set_neighs_on_patch_3d(ref_list, n_neigh, bound);

/*--------------------------------------------------------------------------*/
/* generate new co-ordinates if necessary. We do this before calling        */
/* refine_leaf_data() such that the application can use the parents leaf-   */
/* data structure while generating the new coordinates for the childs.      */
/*--------------------------------------------------------------------------*/
  if (!mesh->parametric && (el_info->fill_flag & FILL_PROJECTION)) {
    new_coords_3d(ref_list, n_neigh);
  }

/*--------------------------------------------------------------------------*/
/*  and now refine the patch                                                */
/*--------------------------------------------------------------------------*/
  bisect_patch_3d(mesh, edge, ref_list, n_neigh, bound, is_periodic);

  AI_update_elinfo_stack_3d(stack);

  free_rc_list(mesh, orig_ref_list);

  if (n_vertices < 0) {
    mesh->n_vertices =
      mesh->per_n_vertices = -1;
  }
  
  if (n_edges < 0) {
    mesh->n_edges =
      mesh->per_n_edges = -1;
  }

  if (n_faces < 0) {
    mesh->n_faces =
      mesh->per_n_faces = -1;
  }

  return el_info;
}

static U_CHAR refine_3d(MESH *mesh, FLAGS fill_flags)
{
  int         n_elements = mesh->n_elements;
  const EL_INFO *el_info;
  TRAVERSE_STACK *stack;

  fill_flags |= CALL_LEAF_EL|FILL_NEIGH|FILL_BOUND|FILL_ORIENTATION;

  /* Ensure that we have a DOF_ADMIN for vertices. */
  (void)get_vertex_admin(mesh, ADM_PERIODIC)->n0_dof[VERTEX];

  if(mesh->parametric)
    fill_flags |= FILL_PROJECTION;
  else {
    int i;

    for (i = 0; i < mesh->n_macro_el; i++) {
      MACRO_EL *mel = mesh->macro_els+i;
      if (mel->projection[0] != NULL ||
	  mel->projection[1] != NULL ||
	  mel->projection[2] != NULL ||
	  mel->projection[3] != NULL ||
	  mel->projection[4] != NULL) {
	fill_flags |= FILL_PROJECTION|FILL_COORDS;
	break;
      }
    }
  }
  fill_flags |= FILL_COORDS;
  
  call_refine_interpol_3d =
    count_refine_interpol(mesh, AI_get_dof_vec_list(mesh), false, &fill_flags);
  if (mesh->is_periodic) {
    call_refine_interpol_np_3d =
      count_refine_interpol(
	mesh, AI_get_dof_vec_list_np(mesh), true, &fill_flags);
    fill_flags &= ~FILL_NON_PERIODIC;
  }

  stack = get_traverse_stack();
  do_more_refine_3d = true;
  while (do_more_refine_3d)
  {
    do_more_refine_3d = false;
    el_info = traverse_first(stack, mesh, -1, fill_flags);

    while (el_info)
    {
      if (el_info->el->mark > 0)
      {
	do_more_refine_3d |= (el_info->el->mark > 1);
	el_info = refine_function_3d(el_info, stack);
      }
      el_info = traverse_next(stack, el_info);
    }
  }

  free_traverse_stack(stack);

  n_elements = mesh->n_elements - n_elements;

  call_refine_interpol_3d =
    call_refine_interpol_np_3d = false;

  return n_elements ? MESH_REFINED : 0;
}
