/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     submesh_3d.c                                                   */
/*                                                                          */
/*                                                                          */
/* description: Support for master/slave meshes for master->dim == 3        */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung fuer Angewandte Mathematik                         */
/*             Albert-Ludwigs-Universitaet Freiburg                         */
/*             Hermann-Herder-Str. 10                                       */
/*             D-79104 Freiburg im Breisgau, Germany                        */
/*                                                                          */
/*  http://www.alberta-fem.de                                               */
/*                                                                          */
/*  (c) by D. Koester (2005),                                               */
/*         C.-J. Heine (2006-2007)                                          */
/*--------------------------------------------------------------------------*/

/* slave-numbering moved to alberta_internal.h */

static void get_slave_elements_rec_3d(MESH *master, MESH *slave,
				      int neigh, EL *m_el, EL *s_el,
				      S_CHAR orientation, U_CHAR el_type)
{
  /* FUNCNAME("get_slave_elements_rec_3d"); */
  int     child_face[2];
  S_CHAR  child_1_orient;
  EL     *s_child[2];

  if (m_el->child[0]) {
    child_face[0] = child_face_3d[el_type][neigh][0];
    child_face[1] = child_face_3d[el_type][neigh][1];

    if(el_type == 0)
      child_1_orient = orientation;
    else
      child_1_orient = -orientation;

    if(neigh == 0) {
      get_slave_elements_rec_3d(master, slave, 3,
				m_el->child[1], s_el,
				child_1_orient, (el_type + 1)%3);
    }
    else if (neigh == 1) {
      get_slave_elements_rec_3d(master, slave, 3,
				m_el->child[0], s_el,
				orientation, (el_type + 1)%3);
    }
    else if (neigh == 2) {
      /* We MUST copy m_el->new_coord here!!! This will result in
       * duplicated new_coord entries (we do not have access to
       * neighbourhood information here. We will cleanup that later.
       */
      if (m_el->new_coord) {
	s_el->new_coord = get_real_d(slave);
	COPY_DOW(m_el->new_coord, s_el->new_coord);
      }
      AI_bisect_element_2d(slave, s_el, NULL);

      if(orientation > 0) {
	s_child[0] = s_el->child[0];
	s_child[1] = s_el->child[1];
      }
      else {
	s_child[1] = s_el->child[0];
	s_child[0] = s_el->child[1];
      }

      get_slave_elements_rec_3d(master, slave, child_face[0],
				m_el->child[0], s_child[0],
				orientation, (el_type + 1)%3);
      get_slave_elements_rec_3d(master, slave, child_face[1],
				m_el->child[1], s_child[1],
				child_1_orient, (el_type + 1)%3);
    }
    else if (neigh == 3) {
      /* see above */
      if (m_el->new_coord) {
	s_el->new_coord = get_real_d(slave);
	COPY_DOW(m_el->new_coord, s_el->new_coord);
      }
      AI_bisect_element_2d(slave, s_el, NULL);

      if(orientation > 0) {
	s_child[1] = s_el->child[0];
	s_child[0] = s_el->child[1];
      }
      else {
	s_child[0] = s_el->child[0];
	s_child[1] = s_el->child[1];
      }

      get_slave_elements_rec_3d(master, slave, child_face[0],
				m_el->child[0], s_child[0],
				orientation, (el_type + 1)%3);
      get_slave_elements_rec_3d(master, slave, child_face[1],
				m_el->child[1], s_child[1],
				child_1_orient, (el_type + 1)%3);
    }
  }

  return;
}

static void get_slave_elements_3d(MESH *master, MESH *slave,
				  bool (*binding_method)(MESH *master,
							 MACRO_EL *el,
							 int face,
							 void *data),
				  void *data)
{
  FUNCNAME("get_slave_elements_3d");
  MACRO_EL *m_mel, *s_mel;
  int       n, i;

  s_mel = slave->macro_els;
  for(n = 0; n < master->n_macro_el; n++) {
    m_mel = master->macro_els + n;

    for(i = 0; i < N_NEIGH_3D; i++)
      if(binding_method(master, m_mel, i, data)) {
	DEBUG_TEST_EXIT(s_mel,
		    "Ran out of slave macro elements... Wrong meshes?\n");

	get_slave_elements_rec_3d(master, slave,
				  i, m_mel->el, s_mel->el,
				  m_mel->orientation, m_mel->el_type);
	s_mel++;
      }
  }

  return;
}


/****************************************************************************/
/* master interpolation/restriction routines.                               */
/* These call the corresponding refinement/coarsening routines for the slave*/
/* mesh patchwise.                                                          */
/****************************************************************************/

static void master_interpol_3d(DOF_PTR_VEC *m_dpv, RC_LIST_EL *m_rclist,
			       int mn)
{
  FUNCNAME("master_interpol_3d");
  MESH_MEM_INFO   *m_mem_info = (MESH_MEM_INFO *)
    m_dpv->fe_space->admin->mesh->mem_info;
  int             m_n0 = m_dpv->fe_space->admin->n0_dof[FACE];
  int             m_n = m_dpv->fe_space->admin->mesh->node[FACE];
  EL              *m_el, *s_el;
  EL              *s_child[2], *m_child[2];
  int             n_slaves = m_mem_info->n_slaves;
  int             s_n0, s_n, i, j, sn = 0;
  MESH            *slave = NULL;
  DOF_PTR_VEC     *s_dpv;
  RC_LIST_EL      s_rclist[2] = {{{0}}};
  S_CHAR          m_orientation;
  U_CHAR          m_type;
  const NODE_PROJECTION *s_proj;

/****************************************************************************/
/* Retrieve the slave mesh. Rather unelegant, sorry...                      */
/****************************************************************************/
  for (j = 0; j < n_slaves; j++) {
    slave = m_mem_info->slaves[j];
    if(((MESH_MEM_INFO *)slave->mem_info)->slave_binding == m_dpv) break;
  }
  DEBUG_TEST_EXIT(j < n_slaves, "Slave mesh not found!\n");
  
  s_dpv = ((MESH_MEM_INFO *)slave->mem_info)->master_binding; 
  s_n0  = s_dpv->fe_space->admin->n0_dof[CENTER];
  s_n   = slave->node[CENTER];

/****************************************************************************/
/* Run over all patch elements. Check if any faces belong to slave elements.*/
/* Build a 2D refinement patch (RC_LIST_EL list).                           */
/****************************************************************************/

  for (i = 0; i < mn; i++) {
    m_el          = m_rclist[i].el_info.el;
    m_orientation = m_rclist[i].el_info.orientation;
    m_type        = m_rclist[i].el_info.el_type;
    m_child[0]    = m_el->child[0];
    m_child[1]    = m_el->child[1];
    
/****************************************************************************/
/* Set the master child DOFs to NULL on the new faces.                       */
/****************************************************************************/
    m_dpv->vec[m_child[0]->dof[m_n+0][m_n0]] = NULL;
    m_dpv->vec[m_child[1]->dof[m_n+0][m_n0]] = NULL;


    /* These are the faces along the ref. edge. */
    for(j = 2; j < N_WALLS_3D; j++) {
      s_el = (EL *)m_dpv->vec[m_el->dof[m_n+j][m_n0]];
    
      if(s_el && m_el == (EL *)s_dpv->vec[s_el->dof[s_n][s_n0]]) {
	DEBUG_TEST_EXIT(sn < 2,
		    "Too many slave elements along refinement edge!\n");
	s_el->mark = MAX(s_el->mark, 1);
	s_rclist[sn].el_info.el                 = s_el;
	s_rclist[sn].el_info.master.el          = m_el;
	s_rclist[sn].el_info.master.opp_vertex  = j;
	s_rclist[sn].el_info.master.orientation = m_orientation;
	s_rclist[sn].el_info.master.el_type     = m_type;
	s_rclist[sn].el_info.mesh               = slave;
	s_rclist[sn].el_info.fill_flag         |= FILL_MASTER_INFO;
	s_rclist[sn].no = sn;

/* Fill active projection on slave rclist for parametric meshes.            */
	s_proj = wall_proj(&m_rclist[i].el_info, j);
	if (!s_proj)
	  s_proj = wall_proj(&m_rclist[i].el_info, -1);
	s_rclist[sn].el_info.active_projection = s_proj;
	if (s_proj != NULL) {
	  s_rclist[sn].el_info.fill_flag |= FILL_PROJECTION|FILL_COORDS;
	
/* DK: Also fill coordinate information on the ad-hoc el_info. This is      */
/* necessary when we have a non-parametric mesh with projection functions,  */
/* i.e. we are using the traditional "new_coord" mechanism.                 */

	  COPY_DOW(m_rclist[i].el_info.coord[0],
		   s_rclist[sn].el_info.coord
		   [slave_numbering_3d[MIN(m_type, 1)]
		    [(m_orientation > 0)? 0:1][j][0]]);
	  COPY_DOW(m_rclist[i].el_info.coord[1],
		   s_rclist[sn].el_info.coord
		   [slave_numbering_3d[MIN(m_type, 1)]
		    [(m_orientation > 0)? 0:1][j][1]]);
	  COPY_DOW(m_rclist[i].el_info.coord[2+3-j],
		   s_rclist[sn].el_info.coord
		   [slave_numbering_3d[MIN(m_type, 1)]
		    [(m_orientation > 0)? 0:1][j][2+3-j]]);
	}
	
	sn++;
      } else {
/****************************************************************************/
/* Set more master child DOFs to NULL.                                       */
/****************************************************************************/
	if(m_type == 0) {
	  m_dpv->vec[m_child[0]->dof[m_n + j - 1][m_n0]] = NULL;
	  m_dpv->vec[m_child[1]->dof[m_n + 4 - j][m_n0]] = NULL;
	}
	else {
	  m_dpv->vec[m_child[0]->dof[m_n + j - 1][m_n0]] = NULL;
	  m_dpv->vec[m_child[1]->dof[m_n + j - 1][m_n0]] = NULL;
	}
      }
    }
  }
  
/****************************************************************************/
/* Send the slave refinement list to AI_bisect_patch_2d() for bisection.    */
/* This routine can call refine_fct_1d() in turn if necessary, if the       */
/* current slave has 1D slaves itself. Take care of coarse DOFs as above.   */
/****************************************************************************/

  if(sn)
    AI_bisect_patch_2d(slave, s_rclist, sn);

/****************************************************************************/
/* Run over all master patch elements again. Set all necessary DOFs.        */
/****************************************************************************/

  for(i = 0; i < mn; i++) {
    m_el          = m_rclist[i].el_info.el;
    m_orientation = m_rclist[i].el_info.orientation;
    m_type        = m_rclist[i].el_info.el_type;
    m_child[0]    = m_el->child[0];
    m_child[1]    = m_el->child[1];

    for(j = 0; j < N_WALLS_3D; j++) {
      s_el = (EL *)m_dpv->vec[m_el->dof[m_n+j][m_n0]];
    
      if(s_el && m_el == (EL *)s_dpv->vec[s_el->dof[s_n][s_n0]]) {
	if(j == 2) {
/****************************************************************************/
/* We are on a face along the refinement edge. Retrieve new slave children  */
/* and set DOF pointers. There are two cases concerning whether slave child */
/* 0 belongs to master child 0 or master child 1.                           */
/****************************************************************************/
	  if(m_orientation > 0) {
	    s_child[0] = s_el->child[0];
	    s_child[1] = s_el->child[1];
	  }
	  else {
	    s_child[1] = s_el->child[0];
	    s_child[0] = s_el->child[1];
	  }

	  m_dpv->vec[m_child[0]->dof[m_n+child_face_3d[m_type][2][0]][m_n0]]
	    = s_child[0];
	  m_dpv->vec[m_child[1]->dof[m_n+child_face_3d[m_type][2][1]][m_n0]]
	    = s_child[1];

	  s_dpv->vec[s_child[0]->dof[s_n][s_n0]] = m_child[0];
	  s_dpv->vec[s_child[1]->dof[s_n][s_n0]] = m_child[1];

	}
	else if (j == 3) {
	  if(m_orientation > 0) {
	    s_child[1] = s_el->child[0];
	    s_child[0] = s_el->child[1];
	  }
	  else {
	    s_child[0] = s_el->child[0];
	    s_child[1] = s_el->child[1];
	  }

	  m_dpv->vec[m_child[0]->dof[m_n+child_face_3d[m_type][3][0]][m_n0]]
	    = s_child[0];
	  m_dpv->vec[m_child[1]->dof[m_n+child_face_3d[m_type][3][1]][m_n0]]
	    = s_child[1];

	  s_dpv->vec[s_child[0]->dof[s_n][s_n0]] = m_child[0];
	  s_dpv->vec[s_child[1]->dof[s_n][s_n0]] = m_child[1];

	}
	else {
/****************************************************************************/
/* If we are not on a refinement edge, set the correct master child         */
/* pointer to point to the slave element.                                   */
/****************************************************************************/
	  m_dpv->vec[m_child[1-j]->dof[m_n+3][m_n0]] = s_el;
	  s_dpv->vec[s_el->dof[s_n][s_n0]] = m_child[1-j];

	}
      }
    }
  }

  return;
}


static void master_restrict_3d(DOF_PTR_VEC *m_dpv, RC_LIST_EL *m_rclist,int mn)
{
  FUNCNAME("master_restrict_3d");
  MESH_MEM_INFO   *m_mem_info = (MESH_MEM_INFO *)
    m_dpv->fe_space->admin->mesh->mem_info;
  int              m_n0 = m_dpv->fe_space->admin->n0_dof[FACE];
  int              m_n = m_dpv->fe_space->admin->mesh->node[FACE];
  EL              *m_el, *s_el, *cm_el;
  EL              *m_child[2];
  int              n_slaves = m_mem_info->n_slaves;
  int              s_n0, s_n, i, j, sn = 0;
  MESH            *slave = NULL;
  DOF_PTR_VEC     *s_dpv;
  RC_LIST_EL       s_rclist[2] = {{{0}}};

/****************************************************************************/
/* Retrieve the slave mesh. Rather unelegant, sorry...                      */
/****************************************************************************/
  for (j = 0; j < n_slaves; j++) {
    slave = m_mem_info->slaves[j];
    if(((MESH_MEM_INFO *)slave->mem_info)->slave_binding == m_dpv) break;
  }
  DEBUG_TEST_EXIT(j < n_slaves, "Slave mesh not found!\n");
  
  s_dpv = ((MESH_MEM_INFO *)slave->mem_info)->master_binding; 
  s_n0  = s_dpv->fe_space->admin->n0_dof[CENTER];
  s_n   = slave->node[CENTER];

/****************************************************************************/
/* Run over all patch elements. Check if any faces belong to slave elements.*/
/* Build a 2D coarsening patch (RC_LIST_EL list).                           */
/****************************************************************************/

  for(i = 0; i < mn; i++) {
    m_el = m_rclist[i].el_info.el;

    for(j = 0; j < N_WALLS_3D; j++) {
      s_el = (EL *)m_dpv->vec[m_el->dof[m_n+j][m_n0]];
    
      if(s_el && m_el == (EL *)s_dpv->vec[s_el->dof[s_n][s_n0]]) {
	if(j == 2 || j == 3) {  /* These are the faces along the coa. edge. */
	  DEBUG_TEST_EXIT(sn < 2,
		      "Too many slave elements along coarsening edge!\n");
	  s_el->child[0]->mark = -1;
	  s_el->child[1]->mark = -1;

	  s_rclist[sn].el_info.el = s_el;
	  s_rclist[sn].no = sn;
	  sn++;
	}
      }
    }
  }

/****************************************************************************/
/* Run over all master patch elements again. Set all necessary DOFs.        */
/****************************************************************************/

  for(i = 0; i < mn; i++) {
    m_el          = m_rclist[i].el_info.el;
    m_child[0]    = m_el->child[0];
    m_child[1]    = m_el->child[1];

    for(j = 0; j < 2; j++) {
      s_el = (EL *)m_dpv->vec[m_el->dof[m_n+j][m_n0]];
      if(s_el) {
	cm_el = (EL *)s_dpv->vec[s_el->dof[s_n][s_n0]];
    
	if ((cm_el==m_child[0] || cm_el==m_child[1])) {
/****************************************************************************/
/* If we are not on a refinement edge, set the correct slave pointer to     */
/* point to the master parent element.                                      */
/****************************************************************************/
	  s_dpv->vec[s_el->dof[s_n][s_n0]] = m_el;
	}
      }
    }
  }

/****************************************************************************/
/* Send the slave coarsening list to AI_coarse_patch_2d() for coarsening.   */
/* This routine can call coarse_recursive_1d() in turn if necessary, when   */
/*the slave has 1D slaves itself.                                           */
/****************************************************************************/

  if(sn) AI_coarse_patch_2d(slave, s_rclist, sn);

  return;
}


static void join_elements_recursive_3d(const MESH *master,
				       const MESH *slave,
				       const DOF_ADMIN *m_admin,
				       const DOF_ADMIN *s_admin,
				       const DOF_PTR_VEC *m_dpv,
				       const DOF_PTR_VEC *s_dpv,
				       const int subsimplex,
				       const EL *m_el,
				       const EL *s_el,
				       const S_CHAR m_orientation,
				       const U_CHAR m_type)
{
  FUNCNAME("join_elements_recursive_3d");
  DOF s_dof_el = s_el->dof[slave->node[CENTER]][s_admin->n0_dof[CENTER]];
  DOF m_dof_el = m_el->dof[master->node[FACE] + subsimplex][m_admin->n0_dof[FACE]];

  s_dpv->vec[s_dof_el] = (void *)m_el;

  m_dpv->vec[m_dof_el] = (void*)s_el;

  if(m_el->child[0]) {
    EL *s_child[2];

    if (subsimplex == 2) { /* first face along ref. edge */
/* Determine which slave child belongs to which master child.               */
      DEBUG_TEST_EXIT(s_el->child[0],
		  "Could not find slave children!\n");
      
      if(m_orientation > 0) {
	s_child[0] = s_el->child[0];
	s_child[1] = s_el->child[1];
      }
      else {
	s_child[1] = s_el->child[0];
	s_child[0] = s_el->child[1];
      }
      
      join_elements_recursive_3d(master, slave, m_admin, s_admin,
				 m_dpv, s_dpv,
				 child_face_3d[m_type][2][0],
				 m_el->child[0], s_child[0],
				 m_orientation*child_orientation_3d[m_type][0],
				 (m_type + 1) % 3);
      
      join_elements_recursive_3d(master, slave, m_admin, s_admin,
				 m_dpv, s_dpv,
				 child_face_3d[m_type][2][1],
				 m_el->child[1], s_child[1],
				 m_orientation*child_orientation_3d[m_type][1],
				 (m_type + 1) % 3);
    }
    else if (subsimplex == 3) { /* second face along ref. edge */
/* Determine which slave child belongs to which master child.               */
      DEBUG_TEST_EXIT(s_el->child[0],
		  "Could not find slave children!\n");
      
      if(m_orientation > 0) {
	s_child[1] = s_el->child[0];
	s_child[0] = s_el->child[1];
      }
      else {
	s_child[0] = s_el->child[0];
	s_child[1] = s_el->child[1];
      }
      
      join_elements_recursive_3d(master, slave, m_admin, s_admin,
				 m_dpv, s_dpv,
				 child_face_3d[m_type][3][0],
				 m_el->child[0], s_child[0],
				 m_orientation*child_orientation_3d[m_type][0],
				(m_type + 1) % 3);
      
      join_elements_recursive_3d(master, slave, m_admin, s_admin,
				 m_dpv, s_dpv,
				 child_face_3d[m_type][3][1],
				 m_el->child[1], s_child[1],
				 m_orientation*child_orientation_3d[m_type][1],
				 (m_type + 1) % 3);
    }
    else
      join_elements_recursive_3d(master, slave, m_admin, s_admin,
				 m_dpv, s_dpv,
				 3,
				 m_el->child[1 - subsimplex], s_el,
				 m_orientation*child_orientation_3d[m_type][0],
				 (m_type + 1) % 3);
  }

  return;
}

/* If we have a periodic mesh then we need to transfer the wall
 * transformations to the slave mesh. Of course, the slave can only
 * inherit transformations which make sense, i.e. do not map vertices
 * away from the child.
 *
 * Wall transformations must be set before calling
 * compute_neigh_fast()
 *
 * We may produce duplicates here, but that does not matter, its just
 * a slight performance hit.
 */
static void transfer_wall_trafos_3d(MESH *master,
				    MACRO_DATA *sdata,
				    int *vert_ind)
{
  static const int wall_vert[N_WALLS_2D][N_VERTICES_1D] = {
    { 1, 2 }, { 2, 0 }, { 0, 1 }
  };
  int (*master_wall_vtx_trafos)[N_VERTICES(DIM_MAX-1)][2];
  int (*slave_wall_vtx_trafos)[N_VERTICES(DIM_MAX-1)][2] = NULL;
  int nwt;
  int nswt, v, i;

  nwt = _AI_compute_macro_wall_trafos(master, &master_wall_vtx_trafos);

  nswt = 0;
  for (i = 0; i < nwt; i++) {
    for (v = 0; v < N_VERTICES_2D; v++) {
      if (vert_ind[master_wall_vtx_trafos[i][wall_vert[v][0]][0]] >= 0 &&
	  vert_ind[master_wall_vtx_trafos[i][wall_vert[v][0]][1]] >= 0 &&
	  vert_ind[master_wall_vtx_trafos[i][wall_vert[v][1]][0]] >= 0 &&
	  vert_ind[master_wall_vtx_trafos[i][wall_vert[v][1]][1]] >= 0) {
	++nswt;
      }
    }
  }

  if (nswt > 0) {
    slave_wall_vtx_trafos = (int (*)[N_VERTICES(DIM_MAX-1)][2])
      MEM_ALLOC(nswt*sizeof(*slave_wall_vtx_trafos), char);

    nswt = 0;
    for (i = 0; i < nwt; i++) {
      for (v = 0; v < N_VERTICES_2D; v++) {
	int from[2];
	int to[2];
	  
	from[0] = vert_ind[master_wall_vtx_trafos[i][wall_vert[v][0]][0]];
	to[0]   = vert_ind[master_wall_vtx_trafos[i][wall_vert[v][0]][1]];
	from[1] = vert_ind[master_wall_vtx_trafos[i][wall_vert[v][1]][0]];
	to[1]   = vert_ind[master_wall_vtx_trafos[i][wall_vert[v][1]][1]];
	
	if (from[0] >= 0 && from[1] >= 0 && to[0] >= 0 && to[1] >= 0) {
	  slave_wall_vtx_trafos[nswt][0][0] = from[0];
	  slave_wall_vtx_trafos[nswt][0][1] = to[0];
	  slave_wall_vtx_trafos[nswt][1][0] = from[1];
	  slave_wall_vtx_trafos[nswt][1][1] = to[1];
	  ++nswt;
	}
      }
    }
    sdata->n_wall_vtx_trafos = nswt;
    sdata->wall_vtx_trafos   = slave_wall_vtx_trafos;

    sdata->el_wall_vtx_trafos =
      MEM_ALLOC(sdata->n_macro_elements*N_WALLS_2D, int);
    _AI_compute_element_wall_transformations(sdata);
  }

  MEM_FREE(master_wall_vtx_trafos, nwt*sizeof(*master_wall_vtx_trafos), char);
}



/****************************************************************************/
/* get_submesh_3d(master, name, binding_method, data): returns a 2D submesh */
/* of master based on the information given by the user routine             */
/* binding_method().                                                        */
/****************************************************************************/

static MESH *
get_submesh_3d(MESH *master, const char *name,
	       bool (*binding_method)(MESH *master,
				      MACRO_EL *el, int face,
				      void *data),
	       void *data)
{
  FUNCNAME("get_submesh_3d");
  MACRO_DATA      s_data = { 0 /* dim */, };
  MESH_MEM_INFO   *m_info, *s_info;
  MESH            *slave = NULL;
  const FE_SPACE  *slave_space, *master_space;
  DOF_PTR_VEC     *slave_to_master_binding;
  DOF_PTR_VEC     *master_to_slave_binding;
  int              s_n_dof[N_NODE_TYPES] = { 0, };
  int              m_n_dof[N_NODE_TYPES] = { 0, };
  MACRO_EL        *m_mel, *s_mel;
  const DOF_ADMIN *m_admin, *s_admin;
  int              i, j, k, n, ne = 0, nv = 0, *vert_ind = NULL, index;
  char             new_name[1024];

  m_info = ((MESH_MEM_INFO *)master->mem_info);

/****************************************************************************/
/* Count all needed vertices and elements.                                  */
/****************************************************************************/
  s_data.dim = 2;
  s_data.coords = MEM_ALLOC(master->n_vertices, REAL_D);  /* resized later! */

  vert_ind = MEM_ALLOC(master->n_vertices, int);
  for(i = 0; i < master->n_vertices; i++)
    vert_ind[i] = -1;

  for(n = 0; n < master->n_macro_el; n++) {
    m_mel = master->macro_els + n;

    for(i = 0; i < N_WALLS_3D; i++)
      if(binding_method(master, m_mel, i, data)) {
	ne++;
	
	for(j = 0; j < N_VERTICES_3D; j++)
	  if(j != i) {
/* Make use of the slave->mem_info->coords vector to get vertex indices.    */
	    index = m_mel->coord[j] - &m_info->coords[0];
	      
	    if (vert_ind[index] < 0) {
	      vert_ind[index] = nv;    
	      for(k = 0; k < DIM_OF_WORLD; k++)
		s_data.coords[nv][k] = m_info->coords[index][k];

	      nv++;
	    }
	  }
      }
  }

/****************************************************************************/
/* Allocate the needed amount of macro elements and coordinates.            */
/* Fill element and coordinate information.                                 */
/****************************************************************************/

  TEST_EXIT(nv, "Bad mesh: no vertices counted!\n");
  TEST_EXIT(ne, "Bad mesh: no elements counted!\n");

  s_data.n_total_vertices = nv;
  s_data.n_macro_elements = ne;
  s_data.coords       = MEM_REALLOC(s_data.coords, master->n_vertices,
				    nv, REAL_D);
  s_data.mel_vertices = MEM_ALLOC(ne * N_NEIGH_2D, int);
  ne = 0;

  for(n = 0; n < master->n_macro_el; n++) {
    m_mel = master->macro_els + n;

    for(i = 0; i < N_WALLS_3D; i++)
      if(binding_method(master, m_mel, i, data)) {	  
	for(j = 0; j < N_VERTICES_3D; j++)
	  if(j != i) {
	    index = m_mel->coord[j] - &m_info->coords[0];
	    nv = vert_ind[index];
	    
	    s_data.mel_vertices[N_VERTICES_2D * ne +
  slave_numbering_3d[(m_mel->el_type > 0)? 1 : 0]
				[(m_mel->orientation > 0)? 0:1][i][j]] = nv;  
	  }

	ne++;
      }
  }

  /* Wall transformations must be set before calling
   * compute_neigh_fast(). Slaves of peridic meshes need not be
   * periodic.
   */
  if (master->is_periodic) {
    transfer_wall_trafos_3d(master, &s_data, vert_ind);
  }

  compute_neigh_fast(&s_data);

  /* Assign all boundaries a no-boundary type (also allocates .boundary) */
  default_boundary(&s_data, INTERIOR, true);

  /* Now correct the boundary values if the master edge has nonzero type.
   *
   * We only look at the first bit set in the boundary bit-masks. The
   * smallest of these bit-numbers will finally be assigned as
   * boundary type of the slave element.
   */
  ne = 0;
  for(n = 0; n < master->n_macro_el; n++) {
    m_mel = master->macro_els + n;

    for (i = 0; i < N_WALLS_3D; i++)
      if (binding_method(master, m_mel, i, data)) {
	for (j = 0; j < N_EDGES_2D; j++) {
	  if (s_data.neigh[N_NEIGH_2D * ne + j] < 0) {
	    BNDRY_FLAGS mask;
	    BNDRY_FLAGS_INIT(mask);
	    BNDRY_FLAGS_SET(mask, m_mel->wall_bound[i]);
	    BNDRY_FLAGS_XOR(mask, m_mel->edge_bound
			    [master_edge_3d[(m_mel->el_type > 0)? 1 : 0]
			     [(m_mel->orientation > 0)?0:1][i][j]]);
	    int bound = BNDRY_FLAGS_FFBB(mask);
	    if (bound > INTERIOR &&
		(s_data.boundary[N_NEIGH_2D * ne + j] == INTERIOR ||
		 (unsigned)bound < s_data.boundary[N_NEIGH_2D * ne + j])) {
	      s_data.boundary[N_NEIGH_2D * ne + j] = bound;
	    }
	  }
	}
	ne++;
      }
  }

  /* Assign all remaining boundaries a boundary type of 1. Should we
   * make this configurable? Or use (N_BNDRY_BITS-1) in order not to
   * interfere with existing boundary classifications?
   */
  default_boundary(&s_data, DIRICHLET, false);

/****************************************************************************/
/* Allocate a submesh.                                                      */
/****************************************************************************/

  if(!name) {
    static int count_3d = 1;

    sprintf(new_name, "Submesh %d of %s", count_3d, master->name);
    name = new_name;

    count_3d++;
  }

  slave = GET_MESH(2, name, &s_data, NULL, NULL);

/****************************************************************************/
/* Clean up.                                                                */
/****************************************************************************/

  nv = s_data.n_total_vertices;
  ne = s_data.n_macro_elements;
  
  MEM_FREE(s_data.coords, nv, REAL_D);
  MEM_FREE(s_data.mel_vertices, ne*N_VERTICES_2D, int);
  MEM_FREE(s_data.neigh, ne*N_NEIGH_2D, int);
  MEM_FREE(s_data.opp_vertex, ne*N_NEIGH_2D, int);
  MEM_FREE(s_data.boundary, ne*N_NEIGH_2D, S_CHAR); 
  MEM_FREE(vert_ind, master->n_vertices, int);

/****************************************************************************/
/* Fill more slave elements, if the master mesh was already refined.        */
/****************************************************************************/

  get_slave_elements_3d(master, slave, binding_method, data);

/****************************************************************************/
/* We set slave->n_vertices/n_edges to an invalid value since this          */
/* information is not accessible using get_slave_elements_3d().             */
/****************************************************************************/

  if(slave->n_elements < slave->n_hier_elements) {
    slave->n_vertices =
      slave->n_edges =
      slave->per_n_vertices =
      slave->per_n_edges = -1;
  }

/****************************************************************************/
/*  Allocate special FE spaces for the slave.                               */
/****************************************************************************/

  s_n_dof[CENTER] = 1;

  slave_space = get_dof_space(slave, "Center dof fe_space", s_n_dof,
			      ADM_PRESERVE_COARSE_DOFS);

  slave_to_master_binding = get_dof_ptr_vec("Slave - master pointers",
					    slave_space);

/****************************************************************************/
/*  Allocate special FE spaces for master.                                  */
/****************************************************************************/
  
  m_n_dof[FACE] = 1;
  master_space = get_dof_space(master, "Face dof fe_space", m_n_dof,
			       ADM_PRESERVE_COARSE_DOFS);

#if ALBERTA_DEBUG == 1
  check_mesh(slave);
#endif

/****************************************************************************/
/* Allocate special DOF_PTR_VECs for both master and slave. These serve to  */
/* help find the corresponding subsimplex to each boundary master simplex   */
/* during refinement and vice versa.                                        */
/****************************************************************************/

  master_to_slave_binding = get_dof_ptr_vec("Master - slave pointers",
					    master_space);

  master_to_slave_binding->refine_interpol = master_interpol_3d;
  master_to_slave_binding->coarse_restrict = master_restrict_3d;

/****************************************************************************/
/* Set the special pointers in the MESH_MEM_INFO components of both master  */
/* and slave grids.                                                         */
/****************************************************************************/

  s_info                 = (MESH_MEM_INFO *)slave->mem_info;
  s_info->master         = master;
  s_info->slave_binding  = master_to_slave_binding;
  s_info->master_binding = slave_to_master_binding;

  m_info->slaves = MEM_REALLOC(m_info->slaves,
			       m_info->n_slaves,
			       m_info->n_slaves + 1,
			       MESH *);
  m_info->slaves[m_info->n_slaves] = slave;

  m_info->n_slaves++;

/****************************************************************************/
/* Set the element pointer vec entries to the correct values.               */
/* This assumes that slave macro elements were allocated in the order given */
/* by the loop below.                                                       */
/****************************************************************************/

  m_admin = master_to_slave_binding->fe_space->admin;
  s_admin = slave_to_master_binding->fe_space->admin;

  FOR_ALL_DOFS(s_admin, slave_to_master_binding->vec[dof] = NULL);
  FOR_ALL_DOFS(m_admin, master_to_slave_binding->vec[dof] = NULL);

  s_mel = slave->macro_els;
  for(n = 0; n < master->n_macro_el; n++) {
    int ov;

    m_mel = master->macro_els + n;

    for (ov = 0; ov < N_NEIGH_3D; ov++) {
      if (binding_method(master, m_mel, ov, data)) {
	int o, t, i;

	DEBUG_TEST_EXIT(s_mel,
		    "Ran out of slave macro elements... Wrong meshes?\n");

	/* Here we take care of node projection function transfer. */
	if(m_mel->projection[ov+1])
	  s_mel->projection[0] = m_mel->projection[ov+1];
	else
	  s_mel->projection[0] = m_mel->projection[0];
	    
	join_elements_recursive_3d(master, slave, m_admin, s_admin,
				   master_to_slave_binding, 
				   slave_to_master_binding,
				   ov, m_mel->el, s_mel->el,
				   m_mel->orientation, m_mel->el_type);

	s_mel->master.macro_el    = m_mel;
	s_mel->master.opp_vertex  = ov;

	/* also store the boundary classification of the master
	 * vertices and edges.
	 */
	t  = m_mel->el_type > 0;
	o  = m_mel->orientation < 0;
	for (i = 0; i < N_EDGES_2D; i++) {
	  int mst_edge = master_edge_3d[t][o][ov][i];
	  BNDRY_FLAGS_CPY(s_mel->master.edge_bound[i],
			  m_mel->edge_bound[mst_edge]);
	  BNDRY_FLAGS_CPY(s_mel->master.np_edge_bound[i],
			  m_mel->np_edge_bound[mst_edge]);
	}
	for (i = 0; i < N_VERTICES_3D; i++) {
	  int slv_vtx;
	  if (i == ov) {
	    continue;
	  }
	  slv_vtx = slave_numbering_3d[t][o][ov][i];
	  BNDRY_FLAGS_CPY(s_mel->master.vertex_bound[slv_vtx],
			  m_mel->vertex_bound[i]);
	  BNDRY_FLAGS_CPY(s_mel->master.np_vertex_bound[slv_vtx],
			  m_mel->np_vertex_bound[i]);
	}

	s_mel++;
      }
    }
  }

  /****************************************************************************
   *
   * Cleanup duplicate new_coord entries, use FILL_NONPERIODIC to keep
   * duplicate new_coord entries across periodic boundaries.
   *
   ****************************************************************************/
  
  if (slave->n_elements < slave->n_hier_elements) {
    TRAVERSE_FIRST(slave, -1,
		   CALL_EVERY_EL_PREORDER|FILL_NEIGH|FILL_NON_PERIODIC) {
      if (!el_info->neigh[2] || el_info->opp_vertex[2] != 2 ||
	  !el_info->el->new_coord ||
	  el_info->el->new_coord == el_info->neigh[2]->new_coord) {
	continue;
      }
      free_real_d(slave, el_info->neigh[2]->new_coord);
      el_info->neigh[2]->new_coord = el_info->el->new_coord;
    } TRAVERSE_NEXT();
  }

  return slave;
}
