/*--------------------------------------------------------------------------*/
/* 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_2d.c                                                    */
/*                                                                          */
/* description:  recursive refinement of 2 dim. hierarchical meshes;        */
/*               implementation of the newest vertex bisection              */
/*               file contains all routines depending on dim == 2;          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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).                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/

static const EL_INFO *refine_function_2d(const EL_INFO *,
					 TRAVERSE_STACK *stack);

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

static void new_coords_2d(RC_LIST_EL ref_list[], int n_neigh)
{
  static const REAL_B mid_lambda = {0.5, 0.5, 0.0};
  EL_INFO *el_info = &ref_list[0].el_info;
  EL  *el = el_info->el;
  EL  *neigh = ref_list[1].el_info.el;

  if (el_info->active_projection
      && el_info->active_projection->func && (el->new_coord == NULL)) {
    el->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);
    el_info->active_projection->func(el->new_coord, el_info, mid_lambda);
    
    _AI_refine_update_bbox(el_info->mesh, el->new_coord);

    /* new_coord entry must also be set on the neighbor along the
     * refinement edge, if this neighbor exists; a periodic neighbour
     * still needs its own new_coord.
     */
    if(neigh) {
      DEBUG_TEST(neigh->new_coord == NULL,
		 "non-NULL new_coord in neighbour (el-idx %d)\n", INDEX(neigh));

      if (el->dof[0] != neigh->dof[0] && el->dof[1] != neigh->dof[0]) {
	/* periodic neighbour */
	el_info = &ref_list[1].el_info;
	neigh->new_coord = get_real_d(el_info->mesh);

	AXPBY_DOW(0.5, el_info->coord[0], 0.5, el_info->coord[1],
		  neigh->new_coord);
	el_info->active_projection->func(neigh->new_coord, el_info, mid_lambda);

	_AI_refine_update_bbox(el_info->mesh, neigh->new_coord);
      } else {
	neigh->new_coord = el->new_coord;
      }
    }
  }
}

static const EL_INFO *get_refine_patch_2d(const EL_INFO *el_info, 
					  RC_LIST_EL *ref_list,
					  int *n_neighs,
					  TRAVERSE_STACK *stack)
{
  FUNCNAME("get_refine_patch_2d");
#if ALBERTA_DEBUG
  EL             *el = el_info->el;
#endif
  const EL_INFO  *neigh_info;
  int             opp_vertex;

  if (el_info->neigh[2] && el_info->opp_vertex[2] != 2) {
/*--------------------------------------------------------------------------*/
/*  neighbour is not compatible devisible; refine neighbour first, store the*/
/*  opp_vertex to traverse back to el                                       */
/*--------------------------------------------------------------------------*/
    opp_vertex = el_info->opp_vertex[2];

    neigh_info = traverse_neighbour(stack, el_info, 2);
    neigh_info->el->mark = MAX(neigh_info->el->mark, 1);
    neigh_info = refine_function_2d(neigh_info, stack);

/*--------------------------------------------------------------------------*/
/*  now go back to the original element and refine the patch                */
/*--------------------------------------------------------------------------*/
    el_info = traverse_neighbour(stack, neigh_info, opp_vertex);
    DEBUG_TEST_EXIT(el_info->el == el, "invalid traverse_neighbour1");
  }
  
  if ((ref_list[1].el_info.el = el_info->neigh[2])) {
    DEBUG_TEST_EXIT(el_info->opp_vertex[2] == 2,
	   "no compatible ref. edge after recursive refinement of neighbour");

/*--------------------------------------------------------------------------*/
/*  NEW CODE: fill the neighbour el_info structure! DK                      */
/*--------------------------------------------------------------------------*/

    neigh_info = traverse_neighbour(stack, el_info, 2);
    ref_list[1].el_info = *neigh_info;

    traverse_neighbour(stack, neigh_info, 2);

    *n_neighs = 2;
  }

  return el_info;
}

/*--------------------------------------------------------------------------*/
/* AI_bisect_element_2d: bisects a single element into child[0] and child[1]*/
/*--------------------------------------------------------------------------*/

void AI_bisect_element_2d(MESH *mesh, EL *el, DOF *dof[3])
{
  EL        *child[2];
  int        i_child, node;

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

/*--------------------------------------------------------------------------*/
/*  transfer hidden data from parent to children                            */
/*--------------------------------------------------------------------------*/

  if (el->child[1] && 
      ((MESH_MEM_INFO *)mesh->mem_info)->leaf_data_info->refine_leaf_data)
    ((MESH_MEM_INFO *)mesh->mem_info)->leaf_data_info->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_2d = true;

  if(mesh->n_dof[VERTEX]) {
/*--------------------------------------------------------------------------*/
/*  vertex 2 is the newest vertex                                           */
/*--------------------------------------------------------------------------*/

    /* cH: mesh->node[VERTEX] is always 0 */
#if 0
    node = mesh->node[VERTEX];
#else
# define node 0
#endif

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

/*--------------------------------------------------------------------------*/
/*  the other vertices are handed on from the parent                        */
/*--------------------------------------------------------------------------*/

    for (i_child = 0; i_child < 2; i_child++) {
      child[i_child]->dof[node+i_child] = el->dof[node+2];
      child[i_child]->dof[node+1-i_child] = el->dof[node+i_child];
    }
  }

#ifdef node
# undef node
#endif

/*--------------------------------------------------------------------------*/
/*  there is one more leaf element, two hierachical elements and one more   */
/*  edge                                        			    */
/*--------------------------------------------------------------------------*/

  mesh->n_edges++;
  mesh->per_n_edges++;

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

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

/*--------------------------------------------------------------------------*/
/*  there are dof's in the midpoint of the edges			    */
/*--------------------------------------------------------------------------*/
    child[0]->dof[node + 1] = child[1]->dof[node] = get_dof(mesh, EDGE);

/*--------------------------------------------------------------------------*/
/*  dofs handed on by the parent                                            */
/*--------------------------------------------------------------------------*/
    child[0]->dof[node + 2] = el->dof[node + 1];
    child[1]->dof[node + 2] = el->dof[node];

/*--------------------------------------------------------------------------*/
/*  dofs in the refinement edge                                             */
/*--------------------------------------------------------------------------*/
    child[0]->dof[node] = dof[1];
    child[1]->dof[node + 1] = dof[2];
  }

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

/*--------------------------------------------------------------------------*/
/* there are dofs at the barycenter of the triangles                        */
/*--------------------------------------------------------------------------*/
    child[0]->dof[node] = get_dof(mesh, CENTER);
    child[1]->dof[node] = get_dof(mesh, CENTER);
  }

  return;
}

/*--------------------------------------------------------------------------*/

void
AI_bisect_patch_2d(MESH *mesh, RC_LIST_EL ref_list[], int n_neighs)
{
  DOF     *dof[3] = {NULL, NULL, NULL};
  EL_INFO *el_info = &ref_list[0].el_info;
  EL_INFO *el_info_n = &ref_list[1].el_info;
  EL      *el = el_info->el, *neigh = el_info_n->el;
  int     node, i, periodic = false;
  int     n_edges = mesh->n_edges, n_vertices = mesh->n_vertices;

  /* 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_2d(ref_list, n_neighs);
  }

/*--------------------------------------------------------------------------*/
/*  there is one new vertex in the refinement edge                          */
/*--------------------------------------------------------------------------*/

  if(mesh->n_dof[VERTEX])
    dof[0] = get_dof(mesh, VERTEX);

  mesh->n_vertices++;
  mesh->per_n_vertices++;

  mesh->n_edges++;
  mesh->per_n_edges++;

  if (mesh->n_dof[EDGE]) {
/*--------------------------------------------------------------------------*/
/*  there are two additional dofs in the refinement edge                    */
/*--------------------------------------------------------------------------*/
    dof[1] = get_dof(mesh, EDGE);
    dof[2] = get_dof(mesh, EDGE);
  }

/*--------------------------------------------------------------------------*/
/*  first refine the element                                                */
/*--------------------------------------------------------------------------*/
  AI_bisect_element_2d(mesh, el, dof);

  if (neigh) {
    int parity = 0;

    /* cH: We should _NOT_ assume that the mesh is oriented. Determine
     * the relative orientation of the two elements, then do the right
     * thing. This will enable discretisation of, e.g. Moebius strips.
     * This requires slight changes to the mesh-traversal code as
     * well, of course.
     */
    if (el->dof[0] == neigh->dof[0]) {
      parity = 1;
    } else if (el->dof[1] != neigh->dof[0]) {
      /* DOF-pointers do not coincide, we have a virtual neighbour
       * (periodic mesh). We have to clone the edge DOFs for the
       * children because we allow non-periodic DOF_ADMINs on periodic
       * meshes (i.e. we only make sure that the connectivity
       * structure of the mesh allows the definition of periodic
       * fe-functions).
       *
       * Note that we do not allow wall transformations which identify
       * vertices belonging to the same element. This means that the
       * vertex DOF_ADMIN can be used to determine the relative
       * orientation of elements (which otherwise would not be
       * possible, at least not on the macro-element level).
       */
      periodic = true; /* remember for free_dof() */
      
      if(mesh->n_dof[VERTEX]) {
	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]);
      }
      
      /* Vertices and edges for periodic neighbours count as real
       * edges and vertices, and so:
       */
      mesh->n_vertices++;
      mesh->n_edges++;

      /* We have to dereference the dof index to decide about the
       * orientation. Note that the first vertex admin on a periodic
       * mesh is always a periodic vertex admin.
       */
      if (el->dof[0][0] == neigh->dof[0][0]) {
	parity = 1;
      }
    }
      
    if (mesh->n_dof[EDGE]) {
      /*  there is a neighbour; refine it also, but first exchange
       *  dof[1] and dof[2]; thus, dof[1] is always added on child[0]!
       */
      if (!parity) {
	/* neigh and el have the same orientation, so switch the DOFs
	 * here.
	 */
	DOF *tmp = dof[1];
	dof[1] = dof[2];
	dof[2] = tmp;
      }
    }

    AI_bisect_element_2d(mesh, neigh, dof);
  }
  
/*--------------------------------------------------------------------------*/
/*  if there are functions to interpolate data to the finer grid, do so     */
/*--------------------------------------------------------------------------*/
  
  if (call_refine_interpol_2d)
    refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist,
		    ref_list, n_neighs);
  if (call_refine_interpol_np_2d) {
    if (neigh && periodic) {
      refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		      ref_list, 1);
      refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		      ref_list+1, 1);
    } else {
      refine_interpol(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		      ref_list, n_neighs);
    }
  }

/*--------------------------------------------------------------------------*/
/*  if there should be no dof information on interior leaf elements remove  */
/*  dofs from edges and the centers of parents                              */
/*--------------------------------------------------------------------------*/
  if (mesh->n_dof[EDGE]) {
/*--------------------------------------------------------------------------*/
/*  the only DOF that can be freed is that in the refinement edge; all other*/
/*  DOFs are handed on to the children                                      */
/*--------------------------------------------------------------------------*/
    node = mesh->node[EDGE];

    free_dof(el->dof[node+2], mesh, EDGE, ADM_PRESERVE_COARSE_DOFS);
    if (neigh && periodic) {
      /* Need also to free the copy of this "periodic" DOF on the
       * neighbour. Passing ADM_PERIODIC as flag makes sure that
       * free_dof() does not free the dof indices of periodic admins a
       * second time.
       */
      free_dof(neigh->dof[node+2], mesh, EDGE,
	       ADM_PRESERVE_COARSE_DOFS|ADM_PERIODIC);
    }
  }

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

    for (i = 0; i < n_neighs; i++)
      free_dof(ref_list[i].el_info.el->dof[node], mesh, CENTER,
	       ADM_PRESERVE_COARSE_DOFS);
  }

  /* After sub-mesh generation the counts are invalid, at lest for
   * edges.
   */
  if (n_vertices < 0) {
    mesh->n_vertices = -1;
    mesh->per_n_vertices = -1;
  }

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

  /* On periodic meshes it can happen that the neighbour information
   * is no longer consistent, i.e. no longer at leaf-element level. In
   * principle this is a pathologic case, which only happens when
   * neigh[2] == neigh[1] or neigh[0], but ...
   */
  if (el_info->neigh[0] && el_info->neigh[0]->child[0]) {
    EL_INFO *rw_el_info = (EL_INFO *)el_info;
    EL  *neigh = el_info->neigh[0];
    int ov = el_info->opp_vertex[0];

    TEST_EXIT(ov != 2, "Mesh consistency destroyed.\n");
    rw_el_info->neigh[0] = neigh->child[1-ov];
    rw_el_info->opp_vertex[0] = 2;
  }
  if (el_info->neigh[1] && el_info->neigh[1]->child[0]) {
    EL_INFO *rw_el_info = (EL_INFO *)el_info;
    EL  *neigh = el_info->neigh[1];
    int ov = el_info->opp_vertex[1];

    TEST_EXIT(ov != 2, "Mesh consistency destroyed.\n");
    rw_el_info->neigh[1] = neigh->child[1-ov];
    rw_el_info->opp_vertex[1] = 2;
  }

  return;
}


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

static const EL_INFO *refine_function_2d(const EL_INFO *el_info,
					 TRAVERSE_STACK *stack)
{
  int          n_neighs;
  MESH         *mesh = el_info->mesh;
  RC_LIST_EL   ref_list[2];

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

  ref_list->el_info = *el_info;
  n_neighs = 1;

/*--------------------------------------------------------------------------*/
/*  get the refinement patch                                                */
/*--------------------------------------------------------------------------*/
  el_info = get_refine_patch_2d(el_info, ref_list, &n_neighs, stack);

/*--------------------------------------------------------------------------*/
/*  and now refine the patch                                                */
/*--------------------------------------------------------------------------*/
  AI_bisect_patch_2d(mesh, ref_list, n_neighs);

  return el_info;
}

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

  fill_flags |= CALL_LEAF_EL|FILL_NEIGH|FILL_BOUND;

  /* Ensure that we have a DOF_ADMIN for vertices. */
  get_vertex_admin(mesh, ADM_PERIODIC);
  
  if (mesh->parametric)
    fill_flags |= FILL_PROJECTION;
  else {
    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) {
	fill_flags |= FILL_PROJECTION|FILL_COORDS|FILL_BOUND;
	break;
      }
    }
  }
  if (get_master(mesh)) {
    fill_flags |= FILL_MASTER_INFO;
  }
  
  call_refine_interpol_2d =
    count_refine_interpol(mesh, AI_get_dof_vec_list(mesh), false, &fill_flags);
  if (mesh->is_periodic) {
    call_refine_interpol_np_2d = 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_2d = true;
  while (do_more_refine_2d) {
    do_more_refine_2d = false;
    el_info = traverse_first(stack, mesh, -1, fill_flags);

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

  free_traverse_stack(stack);

  n_elements = mesh->n_elements - n_elements;

  call_refine_interpol_2d =
    call_refine_interpol_np_2d = false;

  return n_elements ? MESH_REFINED : 0;
}

