/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     macro_2d.c                                                     */
/*                                                                          */
/*                                                                          */
/* description:  dimension dependent part of reading/writing macro          */
/*               triangulations for 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                                    */
/*                                                                          */
/*             Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Albert-Ludwigs-Universitaet Freiburg                         */
/*             Hermann-Herder-Str. 10                                       */
/*             D-79104 Freiburg                                             */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
/*  count_edges_2d():                                                       */
/*  calculates the number of edges in the given macro triangulation.        */
/*--------------------------------------------------------------------------*/

static void count_edges_2d(MESH *mesh)
{
  int        i0, n, n_edges = 0, per_n_edges = 0;
  MACRO_EL   *mel;

  for(n = 0; n < mesh->n_macro_el; n++) {
    mel = mesh->macro_els + n;

    for (i0 = 0; i0 < N_NEIGH_2D; i0++) {
      if (mel->neigh[i0]) {
	int i1  = (i0 + 1) % N_VERTICES_2D;
	int i2  = (i0 + 2) % N_VERTICES_2D;
	int i2n = (mel->opp_vertex[i0] + 2) % N_VERTICES_2D;

	if (mel->coord[i1] == mel->neigh[i0]->coord[i2n] ||
	    mel->coord[i2] == mel->neigh[i0]->coord[i2n]) {
	  if (mel->neigh[i0]->index < mel->index) {
	    n_edges++;
	    per_n_edges++;
	  }
	} else {
	  n_edges++;
	  if (mel->neigh[i0]->index < mel->index) {
	    per_n_edges++;
	  }
	}
      } else {
	n_edges++;
	per_n_edges++;
      }
    }
  }
  
  mesh->n_edges = n_edges;
  mesh->per_n_edges = per_n_edges;

  return;
}

/*--------------------------------------------------------------------------*/
/*  fill_bound_info_2d():                                                   */
/*  fills boundary information for the vertices of the macro triangulation  */
/*  The type of a boundary vertex is equal to the highest type of all       */
/*  adjoining boundary edges. If there are no boundary edges containing the */
/*  vertex, it is assumed to be an interior vertex.                         */
/*--------------------------------------------------------------------------*/

static void
fill_bound_info_2d(MESH *mesh, const int *mel_vertices, int nv, int ne)
{
  /*FUNCNAME("fill_bound_info_2d");*/
  MACRO_EL    *mel = mesh->macro_els;
  int         i, j;
  BNDRY_FLAGS *bound, *non_per_bound;
  BNDRY_TYPE  m_bound;

  bound = MEM_ALLOC(nv, BNDRY_FLAGS);
  non_per_bound = MEM_ALLOC(nv, BNDRY_FLAGS);

  for (i = 0; i < nv; i++) {
    BNDRY_FLAGS_INIT(non_per_bound[i]);
    BNDRY_FLAGS_INIT(bound[i]);
  }

  for (i = 0; i < ne; i++) {
    for (j = 0; j < N_WALLS_2D; j++) {
      if ((m_bound = mel[i].wall_bound[j]) != INTERIOR) {
	int j1 = mel_vertices[VERT_IND(2,i,(j+1)%3)];
	int j2 = mel_vertices[VERT_IND(2,i,(j+2)%3)];

	if (mel[i].neigh_vertices[j][0] == -1) {
	  BNDRY_FLAGS_SET(bound[j1], m_bound);
	  BNDRY_FLAGS_SET(bound[j2], m_bound);
	}
	BNDRY_FLAGS_SET(non_per_bound[j1], m_bound);
	BNDRY_FLAGS_SET(non_per_bound[j2], m_bound);
      }
    }
  }

  for (i = 0; i < ne; i++) {
    for (j = 0; j < N_VERTICES_2D; j++) {
      BNDRY_FLAGS_CPY(mel[i].vertex_bound[j],
		      bound[mel_vertices[VERT_IND(2,i,j)]]);
      BNDRY_FLAGS_CPY(mel[i].np_vertex_bound[j],
		      non_per_bound[mel_vertices[VERT_IND(2,i,j)]]);
    }
  }

  MEM_FREE(bound, nv, BNDRY_FLAGS);
  MEM_FREE(non_per_bound, nv, BNDRY_FLAGS);
}


/*--------------------------------------------------------------------------*/
/*  check_cycles_2d(): check "data" for potential cycles during refinement  */
/*  returns -1 if data is OK else the global index of an element where a    */
/*  cycle is found                                                          */
/*--------------------------------------------------------------------------*/

static int check_cycles_2d(MACRO_DATA *data)
{
  FUNCNAME("check_cycles_2d");
  int     zykstart, i, elem, elemlfd;
  U_CHAR *test, *zykl, flg;

  test = MEM_CALLOC(data->n_macro_elements, U_CHAR);
  zykl = MEM_ALLOC(data->n_macro_elements, U_CHAR);
 
  zykstart = -1;

  for (elemlfd = 0; elemlfd < data->n_macro_elements; elemlfd++) {
    if (!test[elemlfd]) {
      for (i = 0; i < data->n_macro_elements; i++)
        zykl[i] = 0;
    
      elem = elemlfd;

      flg = 2;
      do { 
        if (zykl[elem] == 1) {
          flg = 0;
          zykstart = elem;
        } else{
          zykl[elem] = 1;
      
          if (test[elem] == 1) {       
            flg = 1;
          } else if (data->neigh[NEIGH_IND(2,elem,2)] < 0) {
            flg = 1;
            test[elem] = 1;
          } else if (elem
		     == 
		     data->neigh[
		       NEIGH_IND(2,data->neigh[NEIGH_IND(2,elem,2)],2)]) {
            flg = 1;
            test[elem] = 1;
            test[data->neigh[NEIGH_IND(2,elem,2)]] = 1;
          } else {
            elem = data->neigh[NEIGH_IND(2,elem,2)];
          } 
        }

      } while(flg == 2);
 
      if (flg != 1) break;
    }
  }
  
  MEM_FREE(zykl, data->n_macro_elements, U_CHAR);
  MEM_FREE(test, data->n_macro_elements, U_CHAR);
 
  return zykstart;
}

/*--------------------------------------------------------------------------*/
/* fill_best_edges_2d(): The main job of this routine is to fill the arrays */
/* best_edges[] and neighs[] below. best_edges[elem] is best explained with */
/* some examples:                                                           */
/* best_edges[elem] == {2, 3, 3}: one longest edge, namely 2                */
/* best_edges[elem] == {0, 1, 3}: two longest edges, namely 0 and 1         */
/* best_edges[elem] == {2, 0, 1}: three longest edges, namely 2, 0, 1       */
/* neighs[elem] contains the global indices of the neighbour edges ordered  */
/* by length to match best_edges[elem].                                     */ 
/*--------------------------------------------------------------------------*/

typedef U_CHAR UCHAR_NNEIGH[N_NEIGH_2D];
typedef int INT_NNEIGH[N_NEIGH_2D];

static void fill_best_edges_2d(MACRO_DATA *data, int elem, UCHAR_NNEIGH edge,
			       INT_NNEIGH neighs)
{
  static U_CHAR i;
  static REAL   l[3];

  for(i = 0; i < N_EDGES_2D; i++) {
    l[i] =
      DIST_DOW(data->coords[
		 data->mel_vertices[VERT_IND(2,elem,(i+1)%N_EDGES_2D)]],
	       data->coords[
		 data->mel_vertices[VERT_IND(2,elem,(i+2)% N_EDGES_2D)]]);
    edge[i] = i;
  }

  for (i = 0; i < N_EDGES_2D; i++) {
    if (l[i] > l[edge[0]]) edge[0] = i;
    if (l[i] < l[edge[2]]) edge[2] = i;
  }

  edge[1] = N_EDGES_2D - edge[0] - edge[2];

  for(i = 0; i < N_NEIGH_2D; i++) 
    neighs[i] = data->neigh[NEIGH_IND(2,elem, edge[i])];

  for (i = 1; i < N_EDGES_2D; i++)
    if ( (l[edge[i - 1]] - l[edge[i]]) > REAL_EPSILON * l[edge[i]] )
      break;

  for (; i < N_EDGES_2D; i++)
    edge[i] = N_EDGES_2D;
}


/*--------------------------------------------------------------------------*/
/* new_refine_edge_2d(): change the local indices of vertices, neighbours   */
/* and boundaries to fit the choice of the new refinement edge.             */
/* NOTE: the element's orientation is unimportant for this routine.         */
/*--------------------------------------------------------------------------*/

static inline void
permute_vertex_numbering(MACRO_DATA *data, int elem, int v0, int v1)
{
  FUNCNAME("permute_numbering");
  static BNDRY_TYPE bswap;
  int i, j, swap;

  /* correct the local vertex numbering */
  swap = data->mel_vertices[VERT_IND(2,elem,0)];
  data->mel_vertices[VERT_IND(2,elem,0)] =
    data->mel_vertices[VERT_IND(2,elem,v0)];
  data->mel_vertices[VERT_IND(2,elem,v0)] =
    data->mel_vertices[VERT_IND(2,elem,v1)];
  data->mel_vertices[VERT_IND(2,elem,v1)] = swap;
  
  /* adjust the boundary type */
  bswap = data->boundary[NEIGH_IND(2,elem,0)];
  data->boundary[NEIGH_IND(2,elem,0)]  = data->boundary[NEIGH_IND(2,elem,v0)];
  data->boundary[NEIGH_IND(2,elem,v0)] = data->boundary[NEIGH_IND(2,elem,v1)];
  data->boundary[NEIGH_IND(2,elem,v1)] = bswap;
  
  /* the neighbours */
  swap = data->neigh[NEIGH_IND(2,elem,0)];
  data->neigh[NEIGH_IND(2,elem,0)]  = data->neigh[NEIGH_IND(2,elem,v0)];
  data->neigh[NEIGH_IND(2,elem,v0)] = data->neigh[NEIGH_IND(2,elem,v1)];
  data->neigh[NEIGH_IND(2,elem,v1)] = swap;

  if (data->opp_vertex) {
    /* adjust our opp_vertex numbers, see also further below! */
    swap = data->opp_vertex[NEIGH_IND(2,elem,0)];
    data->opp_vertex[NEIGH_IND(2,elem,0)] =
      data->opp_vertex[NEIGH_IND(2,elem,v0)];
    data->opp_vertex[NEIGH_IND(2,elem,v0)] =
    data->opp_vertex[NEIGH_IND(2,elem,v1)];
    data->opp_vertex[NEIGH_IND(2,elem,v1)] = swap;
  }

  /* On periodic meshes we a have to change even more. */
  if (data->n_wall_vtx_trafos > 0) {
    swap = data->el_wall_vtx_trafos[NEIGH_IND(2, elem, 0)];
    data->el_wall_vtx_trafos[NEIGH_IND(2, elem, 0)] =
      data->el_wall_vtx_trafos[NEIGH_IND(2, elem, v0)];
    data->el_wall_vtx_trafos[NEIGH_IND(2, elem, v0)] =
      data->el_wall_vtx_trafos[NEIGH_IND(2, elem, v1)];
    data->el_wall_vtx_trafos[NEIGH_IND(2, elem, v1)] = swap;
  }
  if (data->el_wall_trafos) {
    swap = data->el_wall_trafos[NEIGH_IND(2, elem, 0)];
    data->el_wall_trafos[NEIGH_IND(2, elem, 0)] =
      data->el_wall_trafos[NEIGH_IND(2, elem, v0)];
    data->el_wall_trafos[NEIGH_IND(2, elem, v0)] =
      data->el_wall_trafos[NEIGH_IND(2, elem, v1)];
    data->el_wall_trafos[NEIGH_IND(2, elem, v1)] = swap;
  }

  /* Now it remains to also correct the opp-vertex numbers of the
   * neighbour elements.
   */
  if (data->opp_vertex) {
    for (i = 0; i < N_NEIGH_2D; i++) {
      int neigh = data->neigh[NEIGH_IND(2, elem, i)];

      if (neigh < 0) {
	continue;
      }
      for (j = 0; j < N_NEIGH_2D; j++) {
	if (data->neigh[NEIGH_IND(2, neigh, j)] == elem) {
	  data->opp_vertex[NEIGH_IND(2, neigh, j)] = i;
	  break;
	}
      }
      TEST_EXIT(j < N_NEIGH_2D, "Inconsistent neighbour information.\n");
    }
  }
}

static inline void flip_ref_edge(MACRO_DATA *data, int elem)
{
  FUNCNAME("flip_ref_edge");
  static BNDRY_TYPE bswap;
  int i, j, swap;

  /* correct the local vertex numbering */
  swap = data->mel_vertices[VERT_IND(2,elem,0)];
  data->mel_vertices[VERT_IND(2,elem,0)] =
    data->mel_vertices[VERT_IND(2,elem,1)];
  data->mel_vertices[VERT_IND(2,elem,1)] = swap;
  
  /* adjust the boundary type */
  bswap = data->boundary[NEIGH_IND(2,elem,0)];
  data->boundary[NEIGH_IND(2,elem,0)] = data->boundary[NEIGH_IND(2,elem,1)];
  data->boundary[NEIGH_IND(2,elem,1)] = bswap;
  
  /* the neighbours */
  if (data->neigh) {
    swap = data->neigh[NEIGH_IND(2,elem,0)];
    data->neigh[NEIGH_IND(2,elem,0)] = data->neigh[NEIGH_IND(2,elem,1)];
    data->neigh[NEIGH_IND(2,elem,1)] = swap;

    /* adjust our opp_vertex numbers, see also further below! */
    if (data->opp_vertex) {
      swap = data->opp_vertex[NEIGH_IND(2,elem,0)];
      data->opp_vertex[NEIGH_IND(2,elem,0)] =
	data->opp_vertex[NEIGH_IND(2,elem,1)];
      data->opp_vertex[NEIGH_IND(2,elem,1)] = swap;

      /* Now it remains to also correct the opp-vertex numbers of the
       * neighbour elements.
       */
      for (i = 0; i < N_NEIGH_2D; i++) {
	int neigh = data->neigh[NEIGH_IND(2, elem, i)];

	if (neigh < 0) {
	  continue;
	}
	for (j = 0; j < N_NEIGH_2D; j++) {
	  if (data->neigh[NEIGH_IND(2, neigh, j)] == elem) {
	    data->opp_vertex[NEIGH_IND(2, neigh, j)] = i;
	    break;
	  }
	}
	TEST_EXIT(j < N_NEIGH_2D, "Inconsistent neighbour information.\n");
      }
    }
  }

  /* On periodic meshes we a have to change even more. */
  if (data->n_wall_vtx_trafos > 0) {
    swap = data->el_wall_vtx_trafos[NEIGH_IND(2, elem, 0)];
    data->el_wall_vtx_trafos[NEIGH_IND(2, elem, 0)] =
      data->el_wall_vtx_trafos[NEIGH_IND(2, elem, 1)];
    data->el_wall_vtx_trafos[NEIGH_IND(2, elem, 1)] = swap;
  }
  if (data->n_wall_trafos) {
    swap = data->el_wall_trafos[NEIGH_IND(2, elem, 0)];
    data->el_wall_trafos[NEIGH_IND(2, elem, 0)] =
      data->el_wall_trafos[NEIGH_IND(2, elem, 1)];
    data->el_wall_trafos[NEIGH_IND(2, elem, 1)] = swap;
  }
}

static void new_refine_edge_2d(MACRO_DATA *data, int elem, U_CHAR new_edge)
{
  switch (new_edge) {
  case 0:
    permute_vertex_numbering(data, elem, 1, 2);
    break;
  case 1:
    permute_vertex_numbering(data, elem, 2, 1);
    break;
  case 2:
    break;
  }
}


/*--------------------------------------------------------------------------*/
/* reorder_2d(): the main routine for correcting cycles                     */
/*                                                                          */
/* Refinement edges are chosen using the following priority:                */
/*      0. If the current element elem only has one longest edge then       */
/*         choose that edge as refinement edge.                             */
/*      1. If possible, choose the refinement edge as one of the longest    */
/*         edges along the boundary.                                        */
/*      2. Otherwise chose the refinement edge as one of the longest edges  */
/*         whose corresponding neighbour's edge is also one of its longest  */
/*         ones (thus creating compatibly divisible pairs of elements)      */
/*      3. Choose a longest edge towards an already tested element.         */
/*      4. If all else fails, choose an arbitrary longest edge.             */
/*--------------------------------------------------------------------------*/

static void reorder_2d(MACRO_DATA *data, U_CHAR *test, int elem,
                    INT_NNEIGH *neighs, UCHAR_NNEIGH *best_edges)
{
  FUNCNAME("reorder_2d");
  static U_CHAR j, k;

  if (data->n_macro_elements < 20) {
    MSG("Current elem: %d, best_edges: %d %d %d\n",
	elem, best_edges[elem][0], best_edges[elem][1], best_edges[elem][2]);
  }

  test[elem] = true;

  if (best_edges[elem][1] == N_EDGES_2D) {
    new_refine_edge_2d(data, elem, best_edges[elem][0]);
    return;
  }

  for (j = 0; best_edges[elem][j] < N_EDGES_2D; j++) {
    MSG("Looking at best_edges[%d][%d]...\n", elem, j);
    if (neighs[elem][j] < 0) {
      MSG("It is a border edge! Selecting it...\n");
      new_refine_edge_2d(data, elem, best_edges[elem][j]);
      return;
    }
    if (!test[neighs[elem][j]]) {
      for (k = 0; best_edges[neighs[elem][j]][k] < N_EDGES_2D; k++)
        if(neighs[neighs[elem][j]][k] == elem) {
	  MSG("Found compatibly divisible neighbour %d!\n", neighs[elem][j]);
          test[neighs[elem][j]] = true;
          new_refine_edge_2d(data, elem, best_edges[elem][j]);
          new_refine_edge_2d(data,neighs[elem][j],best_edges[neighs[elem][j]][k]);
          return;
        }
    }
  }

  MSG("No immediate patch found - trying to select an edge towards tested elements.\n");

  for (j = 0; best_edges[elem][j] < N_EDGES_2D; j++) {
    MSG("Looking at best_edges[%d][%d]...\n", elem, j);
    if (test[neighs[elem][j]]) {
      MSG("Found tested neighbour on edge %d.", j);
      new_refine_edge_2d(data, elem, best_edges[elem][j]);
       return;
    }
  }
  MSG("Finally resorted to selecting edge %d.\n", best_edges[elem][0]);
  new_refine_edge_2d(data, elem, best_edges[elem][0]);
  return;
}


/*--------------------------------------------------------------------------*/
/* correct_cycles_2d(): Correct refinement cycles using reorder_2d()        */
/*--------------------------------------------------------------------------*/

static void correct_cycles_2d(MACRO_DATA *data)
{
  FUNCNAME("correct_cycles_2d");

  int elem;
  U_CHAR *test;

  INT_NNEIGH *neighs = MEM_ALLOC(data->n_macro_elements, INT_NNEIGH);
  UCHAR_NNEIGH *best_edges = MEM_ALLOC(data->n_macro_elements, UCHAR_NNEIGH);  

  test = MEM_CALLOC(data->n_macro_elements, U_CHAR);

  for (elem = 0; elem < data->n_macro_elements; elem++) {
    fill_best_edges_2d(data, elem, best_edges[elem], neighs[elem]);
  }

  for (elem = 0; elem < data->n_macro_elements; elem++) {
    if (!test[elem]) {
      reorder_2d(data, test, elem, neighs, best_edges);
    }
  }
  
  MEM_FREE(test, data->n_macro_elements, U_CHAR);
  MEM_FREE(neighs, data->n_macro_elements, INT_NNEIGH);
  MEM_FREE(best_edges, data->n_macro_elements, UCHAR_NNEIGH);
}

#if DIM_OF_WORLD == 2
/*--------------------------------------------------------------------------*/
/* orientation_2d(): checks and corrects whether the element is oriented    */
/* counterclockwise. Only makes sense for DIM_OF_WORLD == 2!                */
/*--------------------------------------------------------------------------*/

static U_CHAR orientation_2d(MACRO_DATA *data)
{
  int        i;
  REAL_D     e1, e2;
  REAL       det, *a0;
  U_CHAR     result = false;

  for (i = 0; i < data->n_macro_elements; i++) {
    a0 = data->coords[data->mel_vertices[VERT_IND(2,i,0)]];

    e1[0] = data->coords[data->mel_vertices[VERT_IND(2,i,1)]][0] - a0[0];
    e1[1] = data->coords[data->mel_vertices[VERT_IND(2,i,1)]][1] - a0[1];
    e2[0] = data->coords[data->mel_vertices[VERT_IND(2,i,2)]][0] - a0[0];
    e2[1] = data->coords[data->mel_vertices[VERT_IND(2,i,2)]][1] - a0[1];

    det = e1[0]*e2[1] - e1[1]*e2[0];

    if (det < 0) {
      result = true;      
      flip_ref_edge(data, i);
    }
  }

  return result;
}
#endif

/*--------------------------------------------------------------------------*/
/* macro_test_2d(): Check data for potential cycles during refinement       */
/* At the moment, a correction (and subsequent writing of a correct macro   */
/* data file) can only be done in 2D                                        */
/* Additionally, the element orientation is checked and corrected.          */
/*--------------------------------------------------------------------------*/

static void macro_test_2d(MACRO_DATA *data, const char *nameneu)
{
  FUNCNAME("macro_test_2d");
 
  U_CHAR error_found = false;
  int i = -1;

  i = check_cycles_2d(data);

  if (i >= 0) {
    error_found = true;
    WARNING("There is a cycle beginning in macro element %d.\n", i);
    MSG("Correcting refinement edges....\n");
    correct_cycles_2d(data); 
  }

#if DIM_OF_WORLD == 2
  if(orientation_2d(data)) {
    error_found = true;
    WARNING("Element orientation was corrected for some elements.\n");
  }
#endif

  if (error_found && nameneu) {
    MSG("Attempting to write corrected macro data to file %s...\n", nameneu);
    write_macro_data(data, nameneu);
  }

  return;
}
