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

/* fill_bound_info_3d(): fill boundary info on faces from MACRO_DATA
 * *data into corresponding macro elements, set boundary types of
 * vertices (edges are handled later in fill_more_bound_info_3d). The
 * boundary type of a vertex or edge is just a bit-field where bits
 * are set according to the boundary types of the surrounding faces.
 */
static void
fill_bound_info_3d(MESH *mesh, const int *mel_vertices, int nv, int ne)
{
  MACRO_EL    *mel = mesh->macro_els;
  int         i, j, k;
  BNDRY_FLAGS bound[nv], non_per_bound[nv];
  BNDRY_TYPE  m_bound;

/*--------------------------------------------------------------------------*/
/* The first N_FACES_3D entries of mel[].boundary will contain pointers to  */
/* BOUNDARY structures corresponding to the elements' faces. These are the  */
/* crucial infos provided externally, the other subsimplices (here vertices)*/
/* are set to a boundary type depending on the face containing them.        */
/*--------------------------------------------------------------------------*/

  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_3D; j++) {
      if ((m_bound = mel[i].wall_bound[j]) != INTERIOR) {
	if (mel[i].neigh_vertices[j][0] == -1) {
	  for (k = 1; k < N_VERTICES_3D; k++) {
	    BNDRY_FLAGS_SET(bound[mel_vertices[VERT_IND(3, i, (j+k)%4)]],
			    m_bound);
	  }
	}
	for (k = 1; k < N_VERTICES_3D; k++) {
	  BNDRY_FLAGS_SET(non_per_bound[mel_vertices[VERT_IND(3, i, (j+k)%4)]],
			  m_bound);
	}
      }
    }
  }

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


/*--------------------------------------------------------------------------*/
/* new_edge_3d(): This procedure counts the number of faces                 */
/* sharing the edge. Another important task is setting the boundary type of */
/* the edge depending on the types of these faces via new_bound_val_3d().   */
/*--------------------------------------------------------------------------*/

static inline
int new_edge_3d(const int *mel_vertices,
		MACRO_EL *mel, int mel_edge_no, int *n_neigh,
		int non_periodic)
{
  FUNCNAME("new_edge_3d"); 
  typedef BNDRY_FLAGS *LIST_BOUND;
  MACRO_EL   *neigh, *old_neigh = NULL;
  int        i, j, k, opp_v = -1, mel_index, edge_no;
  int        vertex[2] = { -1, }, face, dir;
  BNDRY_FLAGS bound;
  int        max_no_list_el = 20;
  LIST_BOUND *list_bound = MEM_ALLOC(max_no_list_el, LIST_BOUND);
  int        np_n_neigh, neigh_periodic = false;
  static const int next_el[6][2] = {{3,2},{1,3},{1,2},{0,3},{0,2},{0,1}};

  if (non_periodic) {
    n_neigh = &np_n_neigh;    
  }
  
  *n_neigh = 1;

  mel_index = mel->index;

  BNDRY_FLAGS_INIT(bound);
  if (non_periodic) {
    list_bound[0] = &(mel->np_edge_bound[mel_edge_no]);
  } else {
    list_bound[0] = &(mel->edge_bound[mel_edge_no]);
  }

/*--------------------------------------------------------------------------*/
/*  first look for all neighbours in one direction until a boundary is      */
/*  reached :-( or we are back to mel :-)                                   */
/*  if the index of a neighbour is smaller than the element index, the edge */
/*  is counted by this neighbour, return 0.                                 */
/*  If we are back to element, return 1, to count the edge                  */
/*--------------------------------------------------------------------------*/

  neigh = NULL;
  for (dir = 0; dir < 2; dir++) {

    if (!neigh) {
 
      for (j = 0; j < 2; j++) {
	vertex[j] = mel_vertices[VERT_IND(3, mel_index,
					  vertex_of_edge_3d[mel_edge_no][j])];
      }

      edge_no = mel_edge_no;
      face  = next_el[edge_no][dir];
      neigh = mel->neigh[face];
      neigh_periodic = mel->neigh_vertices[face][0] != -1;
      if (non_periodic && neigh_periodic) {
	neigh = NULL; /* neighbour across periodic wall */
      }
  
      opp_v = mel->opp_vertex[face];
      if ((non_periodic || !neigh_periodic) && mel->wall_bound[face] != INTERIOR) {
	BNDRY_FLAGS_SET(bound, mel->wall_bound[face]);
      }

      old_neigh = mel;
    }

    while (neigh && neigh != mel) {
      if (neigh->index < mel_index) {
	MEM_FREE(list_bound, max_no_list_el, LIST_BOUND);
	return false;
      }

      if (neigh_periodic) {
	for (i = 0; i < N_VERTICES_2D; i++) {
	  j = neigh->neigh_vertices[opp_v][i];
	  if (mel_vertices[VERT_IND(3, old_neigh->index, j)] == vertex[0])
	    break;
	}
	j = (opp_v + i + 1) % N_VERTICES_3D;
	vertex[0] = mel_vertices[VERT_IND(3, neigh->index, j)];
	for (i = 0; i < N_VERTICES_2D; i++) {
	  k = neigh->neigh_vertices[opp_v][i];
	  if (mel_vertices[VERT_IND(3, old_neigh->index, k)] == vertex[1])
	    break;
	}
	k = (opp_v + i + 1) % N_VERTICES_3D;
	vertex[1] = mel_vertices[VERT_IND(3, neigh->index, k)];
      } else {
	for (i = 0; i < N_VERTICES_2D; i++) {
	  j = (opp_v + i + 1) % N_VERTICES_3D;
	  if (mel_vertices[VERT_IND(3, neigh->index, j)] == vertex[0])
	    break;
	}
	for (i = 0; i < N_VERTICES_2D; i++) {
	  k = (opp_v + i + 1) % N_VERTICES_3D;
	  if (mel_vertices[VERT_IND(3, neigh->index, k)] == vertex[1])
	    break;
	}
      }
      edge_no = edge_of_vertices_3d[j][k];

      if (*n_neigh == max_no_list_el) {
	list_bound = MEM_REALLOC(list_bound,
				 max_no_list_el, max_no_list_el + 20,
				 LIST_BOUND);
	max_no_list_el += 20;
      } 
      
      if (non_periodic) {
	list_bound[(*n_neigh)++] = &(neigh->np_edge_bound[edge_no]);
      } else {
	list_bound[(*n_neigh)++] = &(neigh->edge_bound[edge_no]);
      }
      
      face = (next_el[edge_no][0] == opp_v)
	? next_el[edge_no][1] : next_el[edge_no][0];
      
      neigh_periodic = neigh->neigh_vertices[face][0] != -1;
      if ((non_periodic || !neigh_periodic) && neigh->wall_bound[face] != INTERIOR) {
	BNDRY_FLAGS_SET(bound, neigh->wall_bound[face]);
      }

      opp_v = neigh->opp_vertex[face];      
      if (non_periodic && neigh_periodic) {
	neigh = NULL; /* neighbour across periodic wall */
      } else {
	old_neigh = neigh;
	neigh = neigh->neigh[face];
      }
    }
  }

  for (j = 0; j < *n_neigh; j++) {
    BNDRY_FLAGS_CPY(*(list_bound[j]), bound);
  }

  MEM_FREE(list_bound, max_no_list_el, LIST_BOUND);

  return true;
}

/*--------------------------------------------------------------------------*/
/* fill_more_bound_info_3d(): This function fills boundary information using*/
/* the function new_edge_3d() above. It also counts faces and edges.        */
/*--------------------------------------------------------------------------*/

static void
fill_more_bound_info_3d(MESH *mesh,
			const int *mel_vertices, int ne, bool do_count)
{
  /* FUNCNAME("fill_more_bound_info_3d"); */
  int      i, k;
  int      n_edges = 0, n_faces = 0, max_n_neigh = 0, n_neigh = 0;
  MACRO_EL *mel = mesh->macro_els, *neigh;

  if (do_count) {
    mesh->max_edge_neigh = 8;
  }

  for (i = 0; i < ne; i++) {
    for (k = 0; k < N_EDGES_3D; k++) {
/*--------------------------------------------------------------------------*/
/*  check for not counted edges                                             */
/*--------------------------------------------------------------------------*/
      if (new_edge_3d(mel_vertices, mel+i, k, &n_neigh, false)) {
	n_edges++;
	max_n_neigh = MAX(max_n_neigh, n_neigh);
      }
    }

    for (k = 0; k < N_NEIGH_3D; k++) {
      neigh = mel[i].neigh[k];
/*--------------------------------------------------------------------------*/
/*  face is counted by the element with bigger index                        */
/*--------------------------------------------------------------------------*/
      if (neigh && (neigh->index > mel[i].index))
	continue;
      
      n_faces++;
    }
  }

  if (mesh->is_periodic) {
    /* fill also mel->np_edge_bound for non-periodic mesh-traversal,
     * and update mesh->n_edges to include edges across periodic
     * walls. The reason is that n_edges is used to layout storage for
     * DOF-pointers and DOFs are duplicated across periodic walls.
     */
    if (do_count) {
      mesh->per_n_edges = n_edges;
      mesh->per_n_faces = n_faces;
    }

    /* per_n_vertices is computed elsewhere */

    n_edges = 0;
    n_faces = 0;
    for (i = 0; i < ne; i++) {
      for (k = 0; k < N_EDGES_3D; k++) {
	if (new_edge_3d(mel_vertices, mel+i, k, &n_neigh, true)) {
	  n_edges++;
	  max_n_neigh = MAX(max_n_neigh, n_neigh);
	}
      }
      for (k = 0; k < N_NEIGH_3D; k++) {
	/* skip periodic neighbours this time; this way n_faces always
	 * corresponds to the number of face DOF-pointers.
	 */
	neigh = mel[i].neigh_vertices[k][0] == -1 ? mel[i].neigh[k] : NULL;

	/* face is counted by the element with bigger index */
	if (neigh && (neigh->index > mel[i].index))
	  continue;
	
	n_faces++;
      }
    }
  }

  if (do_count) {
    mesh->max_edge_neigh = MAX(mesh->max_edge_neigh, 2*max_n_neigh);
    mesh->n_edges = n_edges;
    mesh->n_faces = n_faces;
  }

  return;
}

/*--------------------------------------------------------------------------*/
/* AI_get_orientation_3d(): This function calculates an elements orientation*/
/* and returns it as a signed integer suitable for the MACRO_EL orientation */
/* field. Also called from read_mesh.c                                      */
/*--------------------------------------------------------------------------*/

S_CHAR AI_get_orientation_3d(MACRO_EL *mel)
{
  int i;
  REAL_DD a;

  if (DIM_OF_WORLD != 3) {
    return 1;
  }

  for (i = 0; i < 3; i++) {
    AXPBY_DOW(1.0, *mel->coord[i+1], -1.0, *mel->coord[0], a[i]);
  }

  return MDET_DOW((const REAL_D *)a) >= 0 ? 1 : -1;
}

/*--------------------------------------------------------------------------*/
/* macro_test_3d(): 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.                                       */
/*--------------------------------------------------------------------------*/

#if 0
ALBERTA_UNUSED(static int check_cycles_3d(MACRO_DATA *));
#endif

static void macro_test_3d(MACRO_DATA *data, const char *new_name)
{
  FUNCNAME("macro_test_3d");
#if 1
  WARNING("not implemented for 3d yet: no check is performed\n");
#else
  U_CHAR error_found = false;
  int i = -1;

  i = check_cycles_3d(data);

  if (i >= 0)
  {
    error_found = true;
    WARNING("There is a cycle beginning in macro element %d.\n", i);
    ERROR_EXIT("Correction of cycles not implemented yet in 3D\n");
  }

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