/*============================================================================
 *
 *     This file is part of the Code_Saturne Kernel, element of the
 *     Code_Saturne CFD tool.
 *
 *     Copyright (C) 2008-2010 EDF S.A., France
 *
 *     contact: saturne-support@edf.fr
 *
 *     The Code_Saturne Kernel is free software; you can redistribute it
 *     and/or modify it under the terms of the GNU General Public License
 *     as published by the Free Software Foundation; either version 2 of
 *     the License, or (at your option) any later version.
 *
 *     The Code_Saturne Kernel is distributed in the hope that it will be
 *     useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 *     of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with the Code_Saturne Kernel; if not, write to the
 *     Free Software Foundation, Inc.,
 *     51 Franklin St, Fifth Floor,
 *     Boston, MA  02110-1301  USA
 *
 *===========================================================================*/

/*============================================================================
 * Manipulation of low-level structures for the joining operations
 *===========================================================================*/

#if defined(HAVE_CONFIG_H)
#include "cs_config.h"
#endif

/*----------------------------------------------------------------------------
 * Standard C library headers
 *---------------------------------------------------------------------------*/

#include <assert.h>
#include <string.h>
#include <math.h>

/*----------------------------------------------------------------------------
 * BFT library headers
 *---------------------------------------------------------------------------*/

#include <bft_mem.h>
#include <bft_printf.h>

/*----------------------------------------------------------------------------
 * FVM library headers
 *---------------------------------------------------------------------------*/

#include <fvm_parall.h>
#include <fvm_io_num.h>
#include <fvm_order.h>

/*----------------------------------------------------------------------------
 *  Local headers
 *---------------------------------------------------------------------------*/

#include "cs_join_util.h"
#include "cs_mesh.h"
#include "cs_search.h"
#include "cs_sort.h"

/*----------------------------------------------------------------------------
 *  Header for the current file
 *---------------------------------------------------------------------------*/

#include "cs_join_set.h"

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

BEGIN_C_DECLS

/*============================================================================
 * Static global variables
 *===========================================================================*/

int  cs_glob_join_count = 0;
int  cs_glob_n_joinings = 0;
cs_join_t  **cs_glob_join_array = NULL;

/*============================================================================
 * Macro and type definitions
 *===========================================================================*/

/*============================================================================
 * Private function definitions
 *===========================================================================*/

/*----------------------------------------------------------------------------
 * Initialize a cs_join_param_t structure.
 *
 * parameters:
 *   join_num      <-- number of the current joining operation
 *   fraction      <-- value of the fraction parameter
 *   plane         <-- value of the plane parameter
 *   perio_num     <-- periodicity number (0 if not a periodic joining)
 *   verbosity     <-- level of verbosity required
 *
 * returns:
 *   a pointer to a cs_join_param_t structure
 *---------------------------------------------------------------------------*/

static cs_join_param_t
_join_param_define(int      join_num,
                   float    fraction,
                   float    plane,
                   int      perio_num,
                   int      verbosity)
{
  double  cplane;
  cs_join_param_t  param;

  param.num = join_num;
  param.perio_num = perio_num;

  /* geometric parameters */

  /* parameter used to compute the tolerance associated to each vertex.
     Also used for finding equivalent vertices during edge intersections */

  param.fraction = fraction;

  /* parameter used to judge if two faces are in the same plane (during
     the face splitting) */

  param.plane = plane;
  cplane = cos(param.plane *acos(-1.0)/180.);
  param.plane_criteria = cplane * cplane;

  /* Coef. used to modify the tolerance associated to each vertex BEFORE the
     merge operation.
     If coef = 0.0 => no vertex merge
     If coef < 1.0 => reduce vertex merge
     If coef = 1.0 => no change
     If coef > 1.0 => increase vertex merge */

  param.merge_tol_coef = 1.0;

  /* Coef. used to compute a limit under which two vertices are merged
     before the merge step.
     Default value: 1e-3; Should be small. [1e-4, 1e-2] */

   param.pre_merge_factor = 0.05;

  /* Maximum number of equivalence breaks */

   param.n_max_equiv_breaks = 500;

   /* Tolerance computation mode: tcm
      1: (default) tol = min. edge length related to a vertex * fraction
      2: tolerance is computed like in mode 1 with in addition, the
         multiplication by a coef. which is equal to the max sin(e1, e2)
         where e1 and e2 are two edges sharing the same vertex V for which
         we want to compute the tolerance
     11: like 1 but only in taking into account only the selected faces
     12: like 2 but only in taking into account only the selected faces
   */

   param.tcm = 1;

   /* Intersection computation mode: icm
      1: (default) Original algorithm. Try to clip intersection on extremity
      2: New intersection algorithm. Avoid to clip intersection on extremity
   */

   param.icm = 1;

   /* Maximum number of sub-faces for an initial selected face
      Default value: 200 */

   param.max_sub_faces = 200;

   /* Deepest level reachable during tree building
      Default value: 30  */

   param.tree_max_level = 30;

   /* Max. number of boxes which can be related to a leaf of the tree
      if level != tree_max_level
      Default value: 25  */

   param.tree_n_max_boxes = 25;

   /* Stop tree building if: n_linked_boxes > tree_max_box_ratio*n_init_boxes
      Default value: 5.0 */

   param.tree_max_box_ratio = 5.0;

   /* Level of display */

   param.verbosity = verbosity;

   return param;
}

/*----------------------------------------------------------------------------
 * Initialize a structure for the synchronization of single
 * elements
 *
 * returns:
 *   a pointer to a new structure used for synchronizing single elements
 *----------------------------------------------------------------------------*/

static cs_join_sync_t *
_create_join_sync(void)
{
  cs_join_sync_t  *sync = NULL;

  BFT_MALLOC(sync, 1, cs_join_sync_t);

  sync->n_elts = 0;
  sync->n_ranks = 0;
  sync->ranks = NULL;
  sync->index = NULL;
  sync->array = NULL;

  return sync;
}

/*----------------------------------------------------------------------------
 * Destroy a structure for the synchronization of single elements.
 *
 * parameters:
 *   sync   <->  pointer to a structure used for synchronizing single elements
 *----------------------------------------------------------------------------*/

static void
_destroy_join_sync(cs_join_sync_t   **sync)
{
  cs_join_sync_t  *_sync = *sync;

  if (_sync->n_elts > 0)
    BFT_FREE(_sync->array);
  if (_sync->n_ranks > 0)
    BFT_FREE(_sync->ranks);
  BFT_FREE(_sync->index);

  BFT_FREE(_sync);

  *sync = _sync;
}

/*----------------------------------------------------------------------------
 * Destroy a cs_join_select_t structure.
 *
 * parameters:
 *   param       <-- user-defined joining parameters
 *   join_select <-- pointer to pointer to structure to destroy
 *---------------------------------------------------------------------------*/

static void
_join_select_destroy(cs_join_param_t     param,
                     cs_join_select_t  **join_select)
{
  if (*join_select != NULL) {

    cs_join_select_t *_js = *join_select;

    BFT_FREE(_js->faces);
    BFT_FREE(_js->compact_face_gnum);
    BFT_FREE(_js->cell_gnum);
    BFT_FREE(_js->compact_rank_index);
    BFT_FREE(_js->vertices);
    BFT_FREE(_js->b_adj_faces);
    BFT_FREE(_js->i_adj_faces);

    BFT_FREE(_js->b_face_state);
    BFT_FREE(_js->i_face_state);

    if (param.perio_num > 0)
      BFT_FREE(_js->per_v_couples);

    _destroy_join_sync(&(_js->s_vertices));
    _destroy_join_sync(&(_js->c_vertices));
    _destroy_join_sync(&(_js->s_edges));
    _destroy_join_sync(&(_js->c_edges));

    BFT_FREE(*join_select);
    *join_select = NULL;

  }
}

/*----------------------------------------------------------------------------
 * Reduce numbering for the selected border faces.
 * After this function, we have a compact global face numbering for the
 * selected faces.
 *
 * parameters:
 *   n_select_faces      <-- number of selected faces
 *   p_reduce_gnum       <-> pointer to the reduced numbering
 *   p_reduce_gnum_index <-> pointer to an index on ranks for the reduce
 *                           numbering
 *---------------------------------------------------------------------------*/

static void
_compact_face_gnum_selection(cs_int_t     n_select_faces,
                             fvm_gnum_t  *reduce_gnum[],
                             fvm_gnum_t  *reduce_gnum_index[])
{
  cs_int_t  i;

  fvm_gnum_t  shift = 0;
  fvm_gnum_t  *_reduce_gnum = *reduce_gnum;
  fvm_gnum_t  *_reduce_gnum_index = *reduce_gnum_index;

  const int  n_ranks = cs_glob_n_ranks;
  const int  local_rank = CS_MAX(cs_glob_rank_id, 0);

  assert(_reduce_gnum_index == NULL);

  BFT_MALLOC(_reduce_gnum_index, n_ranks + 1, fvm_gnum_t);

  for (i = 0; i < n_ranks; i++)
    _reduce_gnum_index[i] = 0;

  if (n_ranks > 1) {
#if defined(HAVE_MPI)
    MPI_Comm  mpi_comm = cs_glob_mpi_comm;
    fvm_gnum_t  _n_faces = n_select_faces;

    MPI_Allgather(&_n_faces, 1, FVM_MPI_GNUM,
                  &(_reduce_gnum_index[1]), 1, FVM_MPI_GNUM, mpi_comm);
#endif

    for (i = 0; i < n_ranks; i++)
      _reduce_gnum_index[i+1] += _reduce_gnum_index[i];

    shift = _reduce_gnum_index[local_rank];

  }
  else {

    assert(n_ranks == 1);
    _reduce_gnum_index[n_ranks] = (fvm_gnum_t)n_select_faces;

  }

  BFT_MALLOC(_reduce_gnum, n_select_faces, fvm_gnum_t);

  for (i = 0; i < n_select_faces; i++)
    _reduce_gnum[i] = shift + i + 1;

  /* Returns pointer */

  *reduce_gnum = _reduce_gnum;
  *reduce_gnum_index = _reduce_gnum_index;
}

/*----------------------------------------------------------------------------
 * Extract faces implied in the current joining operation.
 * These are faces which share at least one vertex which is in the
 * select_vertices array.
 *
 * parameters:
 *   n_vertices        <-- number of vertices in the whole mesh
 *   selection         <-- pointer to a selection structure
 *   n_faces           <-- number of faces in the whole mesh
 *   f2v_idx           <-- "face -> vertex" connect. index
 *   f2v_lst           <-- "face -> vertex" connect. list
 *   n_contig_faces    <-> pointer to the number of contiguous faces
 *   contig_faces      <-> pointer to the list of contiguous faces
 *---------------------------------------------------------------------------*/

static void
_extract_contig_faces(cs_int_t          n_vertices,
                      cs_join_select_t *selection,
                      cs_int_t          n_faces,
                      const cs_int_t    f2v_idx[],
                      const cs_int_t    f2v_lst[],
                      cs_int_t         *n_contig_faces,
                      cs_int_t         *contig_faces[])
{
  cs_int_t  i, j,  vtx_id, shift;

  cs_int_t   _n_contig_faces = 0;
  cs_int_t  *_contig_faces = NULL, *counter = NULL;
  cs_int_t  *v2f_idx = NULL, *v2f_lst = NULL;

  const cs_int_t  n_select_vertices = selection->n_vertices;
  const cs_int_t  n_single_vertices = selection->s_vertices->n_elts;
  const cs_int_t  *select_vertices = selection->vertices;
  const cs_int_t  *single_vertices = selection->s_vertices->array;

  if (n_select_vertices + n_single_vertices == 0)
    return;

  /* Reverse face -> vertex connectivity */

  BFT_MALLOC(counter, n_vertices, cs_int_t);

  for (i = 0; i < n_vertices; i++)
    counter[i] = 0;

  for (i = 0; i < n_faces; i++) {
    for (j = f2v_idx[i] - 1; j < f2v_idx[i+1] - 1; j++) {
      vtx_id = f2v_lst[j] - 1;
      counter[vtx_id] += 1;
    }
  } /* End of loop on faces */

  /* Define v2f_idx */

  BFT_MALLOC(v2f_idx, n_vertices + 1, cs_int_t);

  v2f_idx[0] = 0;
  for (i = 0; i < n_vertices; i++)
    v2f_idx[i+1] = v2f_idx[i] + counter[i];

  for (i = 0; i < n_vertices; i++)
    counter[i] = 0;

  /* Define v2f_lst */

  BFT_MALLOC(v2f_lst, v2f_idx[n_vertices], cs_int_t);

  for (i = 0; i < n_faces; i++) {

    for (j = f2v_idx[i] - 1; j < f2v_idx[i+1] - 1; j++) {

      vtx_id = f2v_lst[j] - 1;
      shift = v2f_idx[vtx_id] + counter[vtx_id];
      v2f_lst[shift] = i+1;
      counter[vtx_id] += 1;

    }

  } /* End of loop on faces */

  BFT_REALLOC(counter, n_faces, cs_int_t);

  for (i = 0; i < n_faces; i++)
    counter[i] = 0;

  /* Count the number of contiguous faces */

  for (i = 0; i < n_select_vertices; i++) {

    vtx_id = select_vertices[i] - 1;

    for (j = v2f_idx[vtx_id]; j < v2f_idx[vtx_id+1]; j++)
      counter[v2f_lst[j]-1] = 1;

  }

  for (i = 0; i < n_single_vertices; i++) {

    vtx_id = single_vertices[i] - 1;

    for (j = v2f_idx[vtx_id]; j < v2f_idx[vtx_id+1]; j++)
      counter[v2f_lst[j]-1] = 1;

  }

  for (i = 0; i < n_faces; i++)
    _n_contig_faces += counter[i];

  /* Define contig_faces */

  BFT_MALLOC(_contig_faces, _n_contig_faces, cs_int_t);

  _n_contig_faces = 0;
  for (i = 0; i < n_faces; i++) {
    if (counter[i] == 1) {
      _contig_faces[_n_contig_faces] = i+1;
      _n_contig_faces += 1;
    }
  }

  /* Free memory */

  BFT_FREE(v2f_idx);
  BFT_FREE(v2f_lst);
  BFT_FREE(counter);

  /* Return pointers */

  *n_contig_faces = _n_contig_faces;
  *contig_faces = _contig_faces;
}

#if defined(HAVE_MPI)
/*----------------------------------------------------------------------------
 * Define a structure used for synchronizing "single" vertices.
 * Use a fvm_interface_t structure to help the build.
 *
 * parameters:
 *   interfaces     <-- pointer to a fvm_interface_set_t structure
 *   var_size       <-- number of elements in var buffer
 *   count          <-> counter buffer (0: not selected, 1 otherwise)
 *   related_ranks  <-> rank associated to each single vertex (size: var_size)
 *   single         <-> data about the distribution of single vertices
 *----------------------------------------------------------------------------*/

static void
_add_single_vertices(fvm_interface_set_t    *interfaces,
                     fvm_lnum_t              var_size,
                     fvm_lnum_t             *count,
                     fvm_lnum_t             *related_ranks,
                     cs_join_sync_t         *single)
{
  int  request_count, distant_rank, n_interfaces, total_size;
  int  id, ii, last_found_rank;

  int  count_size = 0;
  int  n_max_ranks = 0, n_max_elts = 0;
  fvm_lnum_t  n_entities = 0;
  int  *buf = NULL, *send_buf = NULL, *recv_buf = NULL;

  MPI_Request  *request = NULL;
  MPI_Status  *status  = NULL;
  MPI_Comm  mpi_comm = cs_glob_mpi_comm;

  const int  local_rank = CS_MAX(cs_glob_rank_id, 0);
  const fvm_lnum_t  *local_num = NULL;
  const fvm_interface_t  *interface = NULL;

  assert(count != NULL);
  assert(single != NULL);

  /* Initialize and allocate */

  n_interfaces = fvm_interface_set_size(interfaces);

  for (id = 0; id < n_interfaces; id++) {
    count_size
      += fvm_interface_size(fvm_interface_set_get(interfaces, id));
  }

  total_size = count_size;

  BFT_MALLOC(buf, total_size * 2, int);

  BFT_MALLOC(request, n_interfaces * 2, MPI_Request);
  BFT_MALLOC(status,  n_interfaces * 2, MPI_Status);

  /* Send and Receive data from distant ranks with
     non-blocking communications */

  request_count = 0;
  count_size  = 0;

  /* Receive */

  for (id = 0; id < n_interfaces; id++) {

    interface = fvm_interface_set_get(interfaces, id);
    distant_rank = fvm_interface_rank(interface);
    n_entities = fvm_interface_size(interface);

    recv_buf = buf + count_size;

    MPI_Irecv(recv_buf,
              n_entities,
              MPI_INT,
              distant_rank,
              distant_rank,
              mpi_comm,
              &(request[request_count++]));

    count_size += n_entities;

  }

  assert(count_size == total_size);

  /* Send */

  for (id = 0; id < n_interfaces; id++) {

    /* Preparation of data to send */

    interface = fvm_interface_set_get(interfaces, id);
    distant_rank   = fvm_interface_rank(interface);
    n_entities = fvm_interface_size(interface);
    local_num = fvm_interface_get_local_num(interface);

    send_buf = buf + count_size;

    for (ii = 0; ii < n_entities; ii++)
      send_buf[ii] = count[local_num[ii]-1];

    MPI_Isend(send_buf,
              n_entities,
              MPI_INT,
              distant_rank,
              local_rank,
              mpi_comm,
              &(request[request_count++]));

    count_size += n_entities;

  }

  assert(count_size == 2*total_size);

  /* Sync after each rank had received all the messages */

  MPI_Waitall(request_count, request, status);

  BFT_FREE(request);
  BFT_FREE(status);

  /* Now we estimate the max. number of ranks and elements involved */

  count_size = 0;
  last_found_rank = -1;

  for (id = 0; id < n_interfaces; id++) {

    /* Scan data */

    interface = fvm_interface_set_get(interfaces, id);
    distant_rank = fvm_interface_rank(interface);
    n_entities = fvm_interface_size(interface);
    local_num = fvm_interface_get_local_num(interface);

    recv_buf = buf + count_size;

    for (ii = 0; ii < n_entities; ii++) {

      int  vtx_id = local_num[ii] - 1;

      assert(vtx_id < var_size);

      if (count[vtx_id] == 0 && recv_buf[ii] > 0) { /* Find a single vertex */

        if (last_found_rank != distant_rank) {
          last_found_rank = distant_rank;
          n_max_ranks++;
        }
        n_max_elts++;

      }

    }

    count_size += n_entities;

  }

  if (n_max_elts > 0) { /* We have found single vertices */

    BFT_MALLOC(single->ranks, n_max_ranks, int);
    BFT_MALLOC(single->index, n_max_ranks + 1, int);
    BFT_MALLOC(single->array, n_max_elts, int);

    count_size = 0;
    last_found_rank = -1;
    single->index[0] = 0;
    single->n_elts = 0;
    single->n_ranks = 0;

    for (id = 0; id < n_interfaces; id++) {

      /* Scan data */

      interface = fvm_interface_set_get(interfaces, id);
      distant_rank = fvm_interface_rank(interface);
      n_entities = fvm_interface_size(interface);
      local_num = fvm_interface_get_local_num(interface);

      recv_buf = buf + count_size;

      for (ii = 0; ii < n_entities; ii++) {

        int  vtx_id = local_num[ii] - 1;

        if (count[vtx_id] == 0 && recv_buf[ii] > 0) {

          if (last_found_rank != distant_rank) {
            last_found_rank = distant_rank;
            single->ranks[single->n_ranks++] = distant_rank;
          }

          single->array[single->n_elts++] = local_num[ii];
          single->index[single->n_ranks] = single->n_elts;

          related_ranks[vtx_id] = distant_rank;
          count[vtx_id] = recv_buf[ii];

        }

      }

      count_size += n_entities;

    } /* End of loop on interfaces */

    BFT_REALLOC(single->ranks, single->n_ranks, int);
    BFT_REALLOC(single->index, single->n_ranks + 1, int);
    BFT_REALLOC(single->array, single->n_elts, int);

  } /* End if n_max_elts > 0 */

  BFT_FREE(buf);

#if 0 && defined(DEBUG) && !defined(NDEBUG)
 {
   int  i, j;

    bft_printf("\n  Single vertices for the joining operation: (%p)\n",
               single);
    bft_printf("  Single vertices: n_elts : %8d\n"
               "  Single vertices: n_ranks: %8d\n",
               single->n_elts, single->n_ranks);

    if (single->n_elts > 0) {
      for (i = 0; i < single->n_ranks; i++)
        for (j = single->index[i]; j < single->index[i+1]; j++)
          bft_printf(" %9d | %6d | %9d\n",
                     j, single->ranks[i], single->array[j]);
      bft_printf("\n");
    }
    bft_printf_flush();

 }
#endif

}

/*-----------------------------------------------------------------------------
 * Define a structure used for synchronizing "single" vertices.
 * Use a fvm_interface_t structure to help the build.
 *
 * parameters:
 *  interfaces     --> pointer to a fvm_interface_set_t structure
 *  var_size       --> number of elements in var buffer
 *  related_ranks  <-> rank buffer for synchronization
 *  coupled        <-> pointer to a structure to build on coupled vertices
 *----------------------------------------------------------------------------*/

static void
_add_coupled_vertices(fvm_interface_set_t    *interfaces,
                      fvm_lnum_t              var_size,
                      int                    *related_ranks,
                      cs_join_sync_t         *coupled)
{
  int  id, ii;
  int  request_count, distant_rank, n_interfaces, total_size, last_found_rank;

  int  count_size = 0;
  fvm_lnum_t  n_entities = 0;
  int  *buf = NULL, *send_buf = NULL, *recv_buf = NULL;

  MPI_Request  *request = NULL;
  MPI_Status  *status  = NULL;
  MPI_Comm  mpi_comm = cs_glob_mpi_comm;

  const int  local_rank = CS_MAX(cs_glob_rank_id, 0);
  const fvm_lnum_t  *local_num = NULL;
  const fvm_interface_t  *interface = NULL;

  assert(related_ranks != NULL);
  assert(coupled != NULL);

  /* Initialize and allocate */

  n_interfaces = fvm_interface_set_size(interfaces);

  for (id = 0; id < n_interfaces; id++) {
    count_size
      += fvm_interface_size(fvm_interface_set_get(interfaces, id));
  }

  total_size = count_size;

  BFT_MALLOC(buf, total_size * 2, int);

  BFT_MALLOC(request, n_interfaces * 2, MPI_Request);
  BFT_MALLOC(status,  n_interfaces * 2, MPI_Status);

  /* Send and Receive data from distant ranks with
     non-blocking communications */

  request_count = 0;
  count_size  = 0;

  /* Receive */

  for (id = 0; id < n_interfaces; id++) {

    interface = fvm_interface_set_get(interfaces, id);
    distant_rank = fvm_interface_rank(interface);
    n_entities = fvm_interface_size(interface);

    recv_buf = buf + count_size;

    MPI_Irecv(recv_buf,
              n_entities,
              MPI_INT,
              distant_rank,
              distant_rank,
              mpi_comm,
              &(request[request_count++]));

    count_size += n_entities;

  }

  assert(count_size == total_size);

  /* Send */

  for (id = 0; id < n_interfaces; id++) {

    /* Preparation of data to send */

    interface = fvm_interface_set_get(interfaces, id);
    distant_rank   = fvm_interface_rank(interface);
    n_entities = fvm_interface_size(interface);
    local_num = fvm_interface_get_local_num(interface);

    send_buf = buf + count_size;

    for (ii = 0; ii < n_entities; ii++)
      send_buf[ii] = related_ranks[local_num[ii]-1];

    MPI_Isend(send_buf,
              n_entities,
              MPI_INT,
              distant_rank,
              local_rank,
              mpi_comm,
              &(request[request_count++]));

    count_size += n_entities;

  }

  assert(count_size == 2*total_size);

  /* Sync after each rank had received all the messages */

  MPI_Waitall(request_count, request, status);

  BFT_FREE(request);
  BFT_FREE(status);

  /* Now we scan each part to build coupled */

  count_size = 0;
  coupled->n_elts = 0;
  coupled->n_ranks = 0;
  last_found_rank = -1;

  for (id = 0; id < n_interfaces; id++) {

    /* Scan data */

    interface = fvm_interface_set_get(interfaces, id);
    distant_rank   = fvm_interface_rank(interface);
    n_entities = fvm_interface_size(interface);

    recv_buf = buf + count_size;

    for (ii = 0; ii < n_entities; ii++) {

      if (recv_buf[ii] == local_rank) { /* Coupled vertex found */
        coupled->n_elts++;
        if (last_found_rank != distant_rank) {
          last_found_rank = distant_rank;
          coupled->n_ranks++;
        }
      }

    }
    count_size += n_entities;

  }

  if (coupled->n_elts > 0) {

    int  rank_shift = 0, vtx_shift = 0;

    BFT_MALLOC(coupled->array, coupled->n_elts, int);
    BFT_MALLOC(coupled->index, coupled->n_ranks + 1, int);
    BFT_MALLOC(coupled->ranks, coupled->n_ranks, int);

    coupled->index[0] = 0;

    count_size = 0;
    last_found_rank = -1;

    for (id = 0; id < n_interfaces; id++) {

      /* Retrieve data */

      interface = fvm_interface_set_get(interfaces, id);
      distant_rank   = fvm_interface_rank(interface);
      n_entities = fvm_interface_size(interface);
      local_num = fvm_interface_get_local_num(interface);

      recv_buf = buf + count_size;

      for (ii = 0; ii < n_entities; ii++) {

        if (recv_buf[ii] == local_rank) {

          if (last_found_rank != distant_rank) {
            last_found_rank = distant_rank;
            coupled->ranks[rank_shift++] = distant_rank;
          }

          assert(local_num[ii] <= var_size);
          coupled->array[vtx_shift++] = local_num[ii];
          coupled->index[rank_shift] = vtx_shift;
        }

      }
      count_size += n_entities;

    }

  } /* End if coupled->n_elts > 0 */

  BFT_FREE(buf);

#if 0 && defined(DEBUG) && !defined(NDEBUG)
  {
   int  i, j;

   bft_printf("\n  Coupled vertices for the joining operation: (%p)\n",
              coupled);
   bft_printf("  Coupled vertices: n_elts : %8d\n"
              "  Coupled vertices: n_ranks: %8d\n",
              coupled->n_elts, coupled->n_ranks);

   if (coupled->n_elts > 0) {
     for (i = 0; i < coupled->n_ranks; i++)
       for (j = coupled->index[i]; j < coupled->index[i+1]; j++)
         bft_printf(" %9d | %6d | %9d\n",
                    j, coupled->ranks[i], coupled->array[j]);
     bft_printf("\n");
   }
   bft_printf_flush();

  }
#endif
}

/*----------------------------------------------------------------------------
 * Get the full selection of vertices to extract. Only in parallel. Somme
 * vertices may have been selected on another rank and not on the local rank
 * but you have to take them into account to have a good update of the mesh.
 *
 * parameters:
 *   n_vertices   <--  number of vertices in the parent mesh
 *   ifs          <--  pointer to a fvm_interface_set_t struct.
 *   p_vtx_tag    <->  pointer to an array on vertices. tag=1 if selected
 *   join_select  <->  pointer to a fvm_join_selection_t structure
 *---------------------------------------------------------------------------*/

static void
_get_missing_vertices(cs_int_t              n_vertices,
                      fvm_interface_set_t  *ifs,
                      cs_int_t             *p_vtx_tag[],
                      cs_join_select_t     *selection)
{
  cs_int_t  i;
  fvm_gnum_t  n_l_elts, n_g_elts;

  cs_int_t  *vtx_tag = NULL, *related_ranks = NULL;

  /* Define a counter on vertices. 1 if selected, 0 otherwise */

  BFT_MALLOC(vtx_tag, n_vertices, cs_int_t);
  BFT_MALLOC(related_ranks, n_vertices, cs_int_t);

  for (i = 0; i < n_vertices; i++) {
    vtx_tag[i] = 0;
    related_ranks[i] = -1;
  }

  for (i = 0; i < selection->n_vertices; i++)
    vtx_tag[selection->vertices[i] - 1] = 1;

  /* Has to be done before the search of single edges because
     we need a synchronized vtx_tag */

  _add_single_vertices(ifs,
                       n_vertices,
                       vtx_tag,
                       related_ranks,
                       selection->s_vertices);

  n_l_elts = selection->s_vertices->n_elts;

  MPI_Allreduce(&n_l_elts, &n_g_elts, 1, FVM_MPI_GNUM,
                MPI_SUM, cs_glob_mpi_comm);

  if (n_g_elts > 0) {

    bft_printf("\n  Global number of single vertices found: %6u\n", n_g_elts);
    bft_printf_flush();
    selection->do_single_sync = true;

    _add_coupled_vertices(ifs,
                          n_vertices,
                          related_ranks,
                          selection->c_vertices);

  } /* End if n_g_elts > 0 */

  /* Return pointers */

  *p_vtx_tag = vtx_tag;

  /* Free memory */

  BFT_FREE(related_ranks);

}

/*----------------------------------------------------------------------------
 * Define a vertex -> vertex connectivity for vertices belonging to the
 * selected border faces.
 *
 * parameters:
 *   n_vertices  <--  number of vertices in the parent mesh
 *   selection   <--  pointer to a fvm_join_selection_t structure
 *   b_f2v_idx   <--  border "face -> vertex" connect. index
 *   b_f2v_lst   <--  border "face -> vertex" connect. list
 *   p_v2v_idx   <->  vertex -> vertex connect. index
 *   p_v2v_lst   <->  vertex -> vertex connect. list
 *---------------------------------------------------------------------------*/

static void
_get_select_v2v_connect(cs_int_t               n_vertices,
                        cs_join_select_t      *selection,
                        cs_int_t               b_f2v_idx[],
                        cs_int_t               b_f2v_lst[],
                        cs_int_t              *p_v2v_idx[],
                        cs_int_t              *p_v2v_lst[])
{
  cs_int_t  i, j, save, s, e, n_sel_edges, shift;

  cs_int_t  *count = NULL, *sel_v2v_idx = NULL, *sel_v2v_lst = NULL;

  /* Build a vertex -> vertex connectivity for the selected border faces  */

  BFT_MALLOC(sel_v2v_idx, n_vertices + 1, cs_int_t);

  for (i = 0; i < n_vertices + 1; i++)
    sel_v2v_idx[i] = 0;

  cs_join_build_edges_idx(selection->n_faces,
                          selection->faces,
                          b_f2v_idx,
                          b_f2v_lst,
                          sel_v2v_idx);

  BFT_MALLOC(count, n_vertices, cs_int_t);

  for (i = 0; i < n_vertices; i++) {
    sel_v2v_idx[i+1] += sel_v2v_idx[i];
    count[i] = 0;
  }

  BFT_MALLOC(sel_v2v_lst, sel_v2v_idx[n_vertices], cs_int_t);

  cs_join_build_edges_lst(selection->n_faces,
                          selection->faces,
                          b_f2v_idx,
                          b_f2v_lst,
                          count,
                          sel_v2v_idx,
                          sel_v2v_lst);

  BFT_FREE(count);

  /* Order sub-lists and then clean repeated elements */

  for (i = 0; i < n_vertices; i++)
    cs_sort_shell(sel_v2v_idx[i], sel_v2v_idx[i+1], sel_v2v_lst);

  save = sel_v2v_idx[0];
  shift = 0;

  for (i = 0; i < n_vertices; i++) {

    s = save;
    e = sel_v2v_idx[i+1];

    if (e - s > 0) {
      sel_v2v_lst[shift++] = sel_v2v_lst[s];
      for (j = s + 1; j < e; j++)
        if (sel_v2v_lst[j-1] != sel_v2v_lst[j])
          sel_v2v_lst[shift++] = sel_v2v_lst[j];
    }

    save = e;
    sel_v2v_idx[i+1] = shift;

  }

  n_sel_edges = sel_v2v_idx[n_vertices];
  BFT_REALLOC(sel_v2v_lst, n_sel_edges, cs_int_t);

  /* Return pointers */

  *p_v2v_idx = sel_v2v_idx;
  *p_v2v_lst = sel_v2v_lst;

}

/*----------------------------------------------------------------------------
 * Get the related edge id from a couple of vertex ids.
 *
 * parameters:
 *   v1_id     <-- first vertex id
 *   v2_id     <-- second vertex id
 *   v2v_idx   <-- vertex -> vertex connect. index
 *   v2v_lst   <-- vertex -> vertex connect. list
 *
 * returns:
 *   related edge_id in cs_join_edges_t structure
 *---------------------------------------------------------------------------*/

inline static cs_int_t
_get_edge_id(cs_int_t         v1_id,
             cs_int_t         v2_id,
             const cs_int_t   v2v_idx[],
             const cs_int_t   v2v_lst[])
{
  int  i, va, vb;

  cs_int_t  edge_id = -1;

  if (v1_id < v2_id)
    va = v1_id, vb = v2_id;
  else
    vb = v1_id, va = v2_id;

  for (i = v2v_idx[va]; i < v2v_idx[va+1]; i++)
    if (v2v_lst[i] == vb + 1)
      break;

  if (i != v2v_idx[va+1])
    edge_id = i;

  return  edge_id;
}

/*----------------------------------------------------------------------------
 * Get the full selection of faces related to "single" vertices.
 * Done only if the run is parallel.
 *
 * parameters:
 *   vertex_tag   <--  tag to know if a vertices is in selection
 *   v1_id        <--  first vertex id
 *   v2_id        <--  second vertex id
 *   ref_v2v_idx  <--  vertex -> vertex connect. index
 *   ref_v2v_lst  <--  vertex -> vertex connect. list
 *   p_tmp_size   <->  pointer to the current number of single edges
 *   p_max_size   <->  pointer to the max allocated size of new_s_vertices
 *   p_tmp_size   <->  pointer to the single edges definition
 *---------------------------------------------------------------------------*/

static void
_add_s_edge(cs_int_t         vertex_tag[],
            cs_int_t         v1_id,
            cs_int_t         v2_id,
            const cs_int_t   sel_v2v_idx[],
            const cs_int_t   sel_v2v_lst[],
            int             *p_tmp_size,
            int             *p_max_size,
            int             *p_tmp_edges[])
{
  int  i, a, b, edge_id;

  _Bool  is_selected = false, is_found = false;

  int  tmp_size = *p_tmp_size;
  int  max_size = *p_max_size;
  int  *tmp_edges = *p_tmp_edges;

  if (vertex_tag[v1_id] > 0)
    if (vertex_tag[v2_id] > 0)
      is_selected = true;

  if (is_selected) { /* Check if this edge is in sel_edges */

    edge_id = _get_edge_id(v1_id, v2_id, sel_v2v_idx, sel_v2v_lst);

    if (edge_id == -1) { /* Edge not found among the selected edges */

      assert(v1_id != v2_id);
      if (v1_id < v2_id)
        a = v1_id + 1, b = v2_id + 1;
      else
        a = v2_id + 1, b = v1_id + 1;

      /* n_edges is assumed to be small */
      for (i = 0; i < tmp_size; i++) {
        if (tmp_edges[2*i] == a) {
          if (tmp_edges[2*i+1] == b) {
            is_found = true;
            break;
          }
        }
      }

      if (!is_found) {

        tmp_edges[2*tmp_size] = a;
        tmp_edges[2*tmp_size+1] = b;
        tmp_size += 1;

        if (tmp_size >= max_size) {
          max_size *= 2;
          BFT_REALLOC(tmp_edges, 2*max_size, int);
        }

      }

    } /* this edge is not among the selection */

  } /* this edge should be among the selected edges */

  /* Return pointers */

  *p_max_size = max_size;
  *p_tmp_size = tmp_size;
  *p_tmp_edges = tmp_edges;

}

/*----------------------------------------------------------------------------
 * Get the full selection of single edges. Done only if the run is parallel.
 *
 * parameters:
 *   ifs          <-- pointer to the interface set on vertices
 *   vertex_tag   <-- tag to know if a vertices is in selection
 *   selection    <-- pointer to a fvm_join_selection_t structure
 *   sel_v2v_idx  <-- vertex -> vertex connect. index
 *   sel_v2v_lst  <-- vertex -> vertex connect. list
 *   b_f2v_idx    <-- border "face -> vertex" connect. index
 *   b_f2v_lst    <-- border "face -> vertex" connect. list
 *   i_f2v_idx    <-- interior "face -> vertex" connect. index
 *   i_f2v_lst    <-- interior "face -> vertex" connect. list
 *   i_face_cells <-- interior face -> cells connect.
 *   s_edges      <-> pointer to the single edges structure to define
 *---------------------------------------------------------------------------*/

static void
_add_single_edges(fvm_interface_set_t   *ifs,
                  cs_int_t               vertex_tag[],
                  cs_join_select_t      *selection,
                  cs_int_t               sel_v2v_idx[],
                  cs_int_t               sel_v2v_lst[],
                  cs_int_t               b_f2v_idx[],
                  cs_int_t               b_f2v_lst[],
                  cs_int_t               i_f2v_idx[],
                  cs_int_t               i_f2v_lst[],
                  cs_int_t               i_face_cells[],
                  cs_join_sync_t        *s_edges)
{
  cs_int_t  i, j, fid, s, e;

  int  shift = 0, tmp_size = 0, max_size = 10;
  int  *tmp_edges = NULL;

  assert(s_edges != NULL);

  /* Scan adjacent faces to find new "single" edges */

  BFT_MALLOC(tmp_edges, 2*max_size, int);

  for (i = 0; i < selection->n_b_adj_faces; i++) {

    fid = selection->b_adj_faces[i] - 1;
    s = b_f2v_idx[fid] - 1;
    e = b_f2v_idx[fid+1] - 1;

    for (j = s; j < e - 1; j++)
      _add_s_edge(vertex_tag,
                  b_f2v_lst[j]-1,
                  b_f2v_lst[j+1]-1,
                  sel_v2v_idx,
                  sel_v2v_lst,
                  &tmp_size,
                  &max_size,
                  &tmp_edges);

    _add_s_edge(vertex_tag,
                b_f2v_lst[e-1]-1,
                b_f2v_lst[s]-1,
                sel_v2v_idx,
                sel_v2v_lst,
                &tmp_size,
                &max_size,
                &tmp_edges);

  }

  for (i = 0; i < selection->n_i_adj_faces; i++) {

    fid = selection->i_adj_faces[i] - 1;

    if (i_face_cells[2*fid] == 0 || i_face_cells[2*fid+1] == 0) {

      s = i_f2v_idx[fid] - 1;
      e = i_f2v_idx[fid+1] - 1;

      for (j = s; j < e - 1; j++)
        _add_s_edge(vertex_tag,
                    i_f2v_lst[j]-1,
                    i_f2v_lst[j+1]-1,
                    sel_v2v_idx,
                    sel_v2v_lst,
                    &tmp_size,
                    &max_size,
                    &tmp_edges);

      _add_s_edge(vertex_tag,
                  i_f2v_lst[e-1]-1,
                  i_f2v_lst[s]-1,
                  sel_v2v_idx,
                  sel_v2v_lst,
                  &tmp_size,
                  &max_size,
                  &tmp_edges);

    } /* Face on a parallel boundary */

  }

  /* Find the related ranks for synchronization */

  if (tmp_size > 0) {

    int   n_entities, distant_rank;

    int  last_found_rank = -1;
    int  *edge_tag = NULL;

    const int  n_interfaces = fvm_interface_set_size(ifs);
    const fvm_lnum_t  *local_num = NULL;
    const fvm_interface_t  *interface = NULL;

    shift = 0;
    s_edges->n_elts = tmp_size;
    BFT_REALLOC(tmp_edges, 2*tmp_size, int);

    BFT_MALLOC(edge_tag, tmp_size, int);
    BFT_MALLOC(s_edges->array, 2*tmp_size, int);
    BFT_MALLOC(s_edges->index, n_interfaces + 1, int);
    BFT_MALLOC(s_edges->ranks, n_interfaces, int);

    for (i = 0; i < tmp_size; i++)
      edge_tag[i] = 0; /* Not matched */

    for (i = 0; i < n_interfaces; i++) {

      interface = fvm_interface_set_get(ifs, i);
      distant_rank = fvm_interface_rank(interface);
      n_entities = fvm_interface_size(interface);
      local_num = fvm_interface_get_local_num(interface);

      for (j = 0; j < tmp_size; j++) {

        if (edge_tag[j] == 0) { /* Not already treated */
          if (cs_search_binary(n_entities, tmp_edges[2*j], local_num) > -1) {
            if (cs_search_binary(n_entities, tmp_edges[2*j+1], local_num) > -1) {

              if (last_found_rank != distant_rank) {
                s_edges->ranks[s_edges->n_ranks] = distant_rank;
                s_edges->index[s_edges->n_ranks] = shift;
                s_edges->n_ranks++;
                last_found_rank = distant_rank;
              }

              edge_tag[j] = 1; /* Tag as done */
              s_edges->array[2*shift] = tmp_edges[2*j];
              s_edges->array[2*shift+1] = tmp_edges[2*j+1];
              shift++;

            }
          }
        } /* Not matched yet */

      } /* Loop on single edges */

    } /* Loop on interfaces */

    s_edges->index[s_edges->n_ranks] = shift;
    s_edges->n_elts = shift;

    /* Memory management */

    if (s_edges->n_elts != tmp_size)
        BFT_REALLOC(s_edges->array, 2*s_edges->n_elts, int);

    BFT_REALLOC(s_edges->ranks, s_edges->n_ranks, int);
    BFT_REALLOC(s_edges->index, s_edges->n_ranks + 1, int);
    BFT_FREE(edge_tag);

#if 0 && defined(DEBUG) && !defined(NDEBUG)
    bft_printf("\n  Single edges for the joining operation: (%p)\n",
               s_edges);
    bft_printf("  Single edges: n_elts : %8d\n"
               "  Single edges: n_ranks: %8d\n",
               s_edges->n_elts, s_edges->n_ranks);

    if (s_edges->n_elts > 0) {
      for (i = 0; i < s_edges->n_ranks; i++)
        for (j = s_edges->index[i]; j < s_edges->index[i+1]; j++)
          bft_printf(" %9d | %6d | (%9d, %9d)\n",
                     j, s_edges->ranks[i], s_edges->array[2*j],
                     s_edges->array[2*j+1]);
      bft_printf("\n");
    }
    bft_printf_flush();
#endif

  } /* End if tmp_size > 0 */

  /* Free memory */

  BFT_FREE(tmp_edges);

}

/*----------------------------------------------------------------------------
 * Define a structure for the coupled edges. Only done if single edges have
 * been detected and only in parallel.
 *
 * parameters:
 *  ifs          <--  pointer to the interface set on vertices
 *  s_edges      <--  single edges structure used to build coupled_edges
 *  c_edges      <->  pointer to the coupled edges structure to define
 *---------------------------------------------------------------------------*/

static void
_add_coupled_edges(fvm_interface_set_t   *ifs,
                   cs_join_sync_t        *s_edges,
                   cs_join_sync_t        *c_edges)
{
  int  i, j, id, n_entities;
  int  request_count, rank_shift, distant_rank;

  int  *buf = NULL, *recv_buf = NULL, *send_buf = NULL;
  MPI_Request  *request = NULL;
  MPI_Status  *status  = NULL;
  MPI_Comm  mpi_comm = cs_glob_mpi_comm;

  const int  local_rank = CS_MAX(cs_glob_rank_id, 0);
  const fvm_lnum_t  *local_num = NULL, *distant_num = NULL;
  const int  n_interfaces = fvm_interface_set_size(ifs);
  const fvm_interface_t  *interface = NULL;

  assert(c_edges != NULL);

  /* Exchange number of single edges */

  BFT_MALLOC(request, n_interfaces * 2, MPI_Request);
  BFT_MALLOC(status,  n_interfaces * 2, MPI_Status);
  BFT_MALLOC(buf, 2*n_interfaces, int);

  for (i = 0; i < 2*n_interfaces; i++)
    buf[i] = 0;

  request_count = 0;

  for (id = 0; id < n_interfaces; id++) {

    interface = fvm_interface_set_get(ifs, id);
    distant_rank = fvm_interface_rank(interface);

    MPI_Irecv(&(buf[id]),
              1,
              MPI_INT,
              distant_rank,
              distant_rank,
              mpi_comm,
              &(request[request_count++]));

  }

  /* Send */

  rank_shift = 0;

  for (id = 0; id < n_interfaces; id++) {

    /* Preparation of data to send */

    interface = fvm_interface_set_get(ifs, id);
    distant_rank = fvm_interface_rank(interface);

    if (rank_shift < s_edges->n_ranks) {
      if (s_edges->ranks[rank_shift] == distant_rank) {
        buf[n_interfaces + id] =
          s_edges->index[rank_shift+1] - s_edges->index[rank_shift];
        rank_shift++;
      }
    }

    MPI_Isend(&(buf[n_interfaces + id]),
              1,
              MPI_INT,
              distant_rank,
              local_rank,
              mpi_comm,
              &(request[request_count++]));

  } /* End of loop on interfaces */

  /* Sync after each rank had received all the messages */

  MPI_Waitall(request_count, request, status);

  /* Define c_edges */

  for (i = 0; i < n_interfaces; i++) {
    if (buf[i] > 0) {
      c_edges->n_elts += buf[i];
      c_edges->n_ranks += 1;
    }
  }

  BFT_MALLOC(c_edges->ranks, c_edges->n_ranks, int);
  BFT_MALLOC(c_edges->index, c_edges->n_ranks + 1, int);
  BFT_MALLOC(c_edges->array, 2*c_edges->n_elts, int);

  for (i = 0; i < c_edges->n_ranks + 1; i++)
    c_edges->index[i] = 0;

  rank_shift = 0;

  for (i = 0; i < n_interfaces; i++) {
    if (buf[i] > 0) {

      interface = fvm_interface_set_get(ifs, i);
      distant_rank = fvm_interface_rank(interface);
      c_edges->ranks[rank_shift++] = distant_rank;
      c_edges->index[rank_shift] = buf[i];

    }
  }

  assert(rank_shift == c_edges->n_ranks);

  for (i = 0; i < c_edges->n_ranks; i++)
    c_edges->index[i+1] += c_edges->index[i];

  assert(c_edges->index[c_edges->n_ranks] == c_edges->n_elts);

  BFT_FREE(buf);

  /* Exchange couple of vertices defining coupled edges */

  request_count = 0;
  rank_shift = 0;

  for (id = 0; id < n_interfaces; id++) {

    interface = fvm_interface_set_get(ifs, id);
    distant_rank = fvm_interface_rank(interface);

    if (rank_shift < c_edges->n_ranks) {
      if (c_edges->ranks[rank_shift] == distant_rank) {

        n_entities = c_edges->index[rank_shift+1] - c_edges->index[rank_shift];
        n_entities *= 2; /* couple of vertices */
        recv_buf = c_edges->array + 2*c_edges->index[rank_shift];
        rank_shift++;

        MPI_Irecv(recv_buf, /* receive distant num */
                  n_entities,
                  MPI_INT,
                  distant_rank,
                  distant_rank,
                  mpi_comm,
                  &(request[request_count++]));

      }
    }

  } /* End of loop on interfaces */

  /* Send */

  rank_shift = 0;

  for (id = 0; id < n_interfaces; id++) {

    interface = fvm_interface_set_get(ifs, id);
    distant_rank = fvm_interface_rank(interface);

    if (rank_shift < s_edges->n_ranks) {
      if (s_edges->ranks[rank_shift] == distant_rank) {

        n_entities = s_edges->index[rank_shift+1] - s_edges->index[rank_shift];
        n_entities *= 2; /* couple of vertices */
        send_buf = s_edges->array + 2*s_edges->index[rank_shift];
        rank_shift++;

        MPI_Isend(send_buf,
                  n_entities,
                  MPI_INT,
                  distant_rank,
                  local_rank,
                  mpi_comm,
                  &(request[request_count++]));

      }
    }

  } /* End of loop on interfaces */

  /* Sync after each rank had received all the messages */

  MPI_Waitall(request_count, request, status);

  BFT_FREE(request);
  BFT_FREE(status);

  rank_shift = 0;

  /* Switch received couple vertices from distant num. to local num. */

  for (i = 0; i < n_interfaces; i++) {

    interface = fvm_interface_set_get(ifs, i);
    distant_rank = fvm_interface_rank(interface);

    if (rank_shift < c_edges->n_ranks) {
      if (c_edges->ranks[rank_shift] == distant_rank) {

        n_entities = fvm_interface_size(interface);
        local_num = fvm_interface_get_local_num(interface);
        distant_num = fvm_interface_get_distant_num(interface);

        for (j = c_edges->index[rank_shift];
             j < c_edges->index[rank_shift+1]; j++) {

          id = cs_search_binary(n_entities, c_edges->array[2*j], distant_num);
          assert(id != -1);
          c_edges->array[2*j] = local_num[id];

          id = cs_search_binary(n_entities, c_edges->array[2*j+1], distant_num);
          assert(id != -1);
          c_edges->array[2*j+1] = local_num[id];

        } /* Loop on couple edges */

        rank_shift++;

      } /* This rank is in the list of related ranks */
    }

  } /* Loop on interfaces */

#if 0 && defined(DEBUG) && !defined(NDEBUG)
  bft_printf("\n  Coupled edges for the joining operation: (%p)\n",
             c_edges);
  bft_printf("  Coupled edges: n_elts : %8d\n"
             "  Coupled edges: n_ranks: %8d\n",
             c_edges->n_elts, c_edges->n_ranks);

  if (c_edges->n_elts > 0) {
    int  shift;
    for (i = 0, shift = 0; i < c_edges->n_ranks; i++) {
      for (j = c_edges->index[i]; j < c_edges->index[i+1]; j++) {
        bft_printf(" %9d | %6d | (%9d, %9d)\n",
                   shift, c_edges->ranks[i],
                   c_edges->array[2*j], c_edges->array[2*j+1]);
        shift++;
      }
    }
    bft_printf("\n");
    bft_printf_flush();
  }
#endif

}

/*----------------------------------------------------------------------------
 * Get the full selection of single edges. Done only if the run is parallel.
 *
 * parameters:
 *  selection    <-> pointer to a fvm_join_selection_t structure
 *  sel_v2v_idx  <-- vertex -> vertex connect. index
 *  sel_v2v_lst  <-- vertex -> vertex connect. list
 *---------------------------------------------------------------------------*/

static void
_filter_edge_element(cs_join_select_t   *selection,
                     const cs_int_t      sel_v2v_idx[],
                     const cs_int_t      sel_v2v_lst[])
{
  cs_int_t  i, j, vid1, vid2, edge_id, request_count, shift, save;

  cs_int_t  *c_edge_tag = NULL, *s_edge_tag = NULL;
  cs_join_sync_t  *s_edges = selection->s_edges;
  cs_join_sync_t  *c_edges = selection->c_edges;

  MPI_Request  *request = NULL;
  MPI_Status   *status = NULL;
  MPI_Comm  mpi_comm = cs_glob_mpi_comm;

  const int  loc_rank = CS_MAX(cs_glob_rank_id, 0);

  assert(cs_glob_n_ranks > 1);

  /* Allocate MPI buffers used for exchanging data */

  BFT_MALLOC(request, c_edges->n_ranks + s_edges->n_ranks, MPI_Request);
  BFT_MALLOC(status, c_edges->n_ranks + s_edges->n_ranks, MPI_Status);

  BFT_MALLOC(c_edge_tag, c_edges->n_elts, cs_int_t);
  BFT_MALLOC(s_edge_tag, s_edges->n_elts, cs_int_t);

  for (i = 0; i < c_edges->n_elts; i++)
    c_edge_tag[i] = 1; /* define as selected */

  for (i = 0; i < s_edges->n_elts; i++)
    s_edge_tag[i] = 1; /* define as selected */

  for (i = 0; i < c_edges->n_elts; i++) {

    vid1 = c_edges->array[2*i] - 1;
    vid2 = c_edges->array[2*i+1] - 1;
    edge_id = _get_edge_id(vid1, vid2, sel_v2v_idx, sel_v2v_lst);

    if (edge_id == -1)
      c_edge_tag[i] = 0; /* unselect this coupled edge */

  } /* End of loop on c_edges */

  /* Exchange between ranks the status of the coupled edges.
     If one receive a tag equal to 0 => delete the related single edge */

  request_count = 0;

  for (i = 0; i < s_edges->n_ranks; i++) {

    int  distant_rank = s_edges->ranks[i];
    int  length = s_edges->index[i+1] - s_edges->index[i];
    int  *recv_buf = s_edge_tag + s_edges->index[i];

    MPI_Irecv(recv_buf,
              length,
              MPI_INT,
              distant_rank,
              distant_rank,
              mpi_comm,
              &(request[request_count++]));

  }

  /* We wait for posting all receives (often recommended) */

  MPI_Barrier(mpi_comm);

  /* Send data to distant ranks */

  for (i = 0; i < c_edges->n_ranks; i++) {

    int  distant_rank = c_edges->ranks[i];
    int  length = c_edges->index[i+1] - c_edges->index[i];
    int  *send_buf = c_edge_tag + c_edges->index[i];

    MPI_Isend(send_buf,
              length,
              MPI_INT,
              distant_rank,
              loc_rank,
              mpi_comm,
              &(request[request_count++]));

  }

  /* Wait for all exchanges */

  MPI_Waitall(request_count, request, status);

  /* Delete unselected elements */

  shift = 0;
  if (c_edges-> n_elts > 0) {

    save = c_edges->index[0];

    for (i = 0; i < c_edges->n_ranks; i++) {

      for (j = save; j < c_edges->index[i+1]; j++) {
        if (c_edge_tag[j] == 1) {
          c_edges->array[2*shift] = c_edges->array[2*j];
            c_edges->array[2*shift+1] = c_edges->array[2*j+1];
           shift++;
        }
      }
      save = c_edges->index[i+1];
      c_edges->index[i+1] = shift;

    }
    c_edges->n_elts = shift;

  } /* c_edges->n_elts > 0 */

  shift = 0;
  if (s_edges->n_elts > 0) {

    save = s_edges->index[0];

    for (i = 0; i < s_edges->n_ranks; i++) {

      for (j = save; j < s_edges->index[i+1]; j++) {
        if (s_edge_tag[j] == 1) {
          s_edges->array[2*shift] = s_edges->array[2*j];
          s_edges->array[2*shift+1] = s_edges->array[2*j+1];
          shift++;
        }
      }
      save = s_edges->index[i+1];
      s_edges->index[i+1] = shift;

    }
    s_edges->n_elts = shift;

  } /* s_edges->n_elts > 0 */

  /* Free memory */

  BFT_FREE(c_edge_tag);
  BFT_FREE(s_edge_tag);
  BFT_FREE(request);
  BFT_FREE(status);

}

/*----------------------------------------------------------------------------
 * Get the full selection of vertices to extract. Only in parallel. Somme
 * vertices may have been selected on another rank and not on the local rank
 * but you have to take them into account to have a good update of the mesh
 *
 * parameters:
 *  b_f2v_idx     <-- border "face -> vertex" connect. index
 *  b_f2v_lst     <-- border "face -> vertex" connect. list
 *  i_f2v_idx     <-- interior "face -> vertex" connect. index
 *  i_f2v_lst     <-- interior "face -> vertex" connect. list
 *  n_vertices    <-- number of vertices in the parent mesh
 *  vtx_tag       <-- tag on vertices. 1 if selected, 0 otherwise
 *  ifs           <-- pointer to a fvm_interface_set_t struct.
 *  i_face_cells  <-- interior face -> cells connect.
 *  join_select   <-> pointer to a fvm_join_selection_t structure
 *---------------------------------------------------------------------------*/

static void
_get_missing_edges(cs_int_t              b_f2v_idx[],
                   cs_int_t              b_f2v_lst[],
                   cs_int_t              i_f2v_idx[],
                   cs_int_t              i_f2v_lst[],
                   cs_int_t              n_vertices,
                   cs_int_t              vtx_tag[],
                   fvm_interface_set_t  *ifs,
                   cs_int_t              i_face_cells[],
                   cs_join_select_t     *selection)
{
  fvm_gnum_t  n_l_elts, n_g_elts;

  cs_int_t  *sel_v2v_idx = NULL, *sel_v2v_lst = NULL;

  /* Define single edge element */

  _get_select_v2v_connect(n_vertices,
                          selection,
                          b_f2v_idx,
                          b_f2v_lst,
                          &sel_v2v_idx,
                          &sel_v2v_lst);

  _add_single_edges(ifs,
                    vtx_tag,
                    selection,
                    sel_v2v_idx,
                    sel_v2v_lst,
                    b_f2v_idx,
                    b_f2v_lst,
                    i_f2v_idx,
                    i_f2v_lst,
                    i_face_cells,
                    selection->s_edges);

  n_l_elts = selection->s_edges->n_elts;

  MPI_Allreduce(&n_l_elts, &n_g_elts, 1, FVM_MPI_GNUM,
                MPI_SUM, cs_glob_mpi_comm);

  if (n_g_elts > 0) {

    bft_printf("  Global number of single edges found:    %6llu\n",
               (unsigned long long)n_g_elts);
    bft_printf_flush();
    selection->do_single_sync = true;

    _add_coupled_edges(ifs,
                       selection->s_edges,
                       selection->c_edges);

    _filter_edge_element(selection,
                         sel_v2v_idx,
                         sel_v2v_lst);

  } /* End if n_g_s_elts > 0 */

#if 0 && defined(DEBUG) && !defined(NDEBUG)
 {
  int  i, j;
  cs_join_sync_t  *s_edges = selection->s_edges;
  cs_join_sync_t  *c_edges = selection->c_edges;

  bft_printf("\n  Coupled edges for the joining operation: (%p)\n",
             c_edges);
  bft_printf("  Coupled edges: n_elts : %8d\n"
             "  Coupled edges: n_ranks: %8d\n",
             c_edges->n_elts, c_edges->n_ranks);

  if (c_edges->n_elts > 0) {
    int  shift;
    for (i = 0, shift = 0; i < c_edges->n_ranks; i++) {
      for (j = c_edges->index[i]; j < c_edges->index[i+1]; j++) {
        bft_printf(" %9d | %6d | (%9d, %9d)\n",
                   shift, c_edges->ranks[i],
                   c_edges->array[2*j], c_edges->array[2*j+1]);
        shift++;
      }
    }
    bft_printf("\n");
    bft_printf_flush();
  }

  bft_printf("\n  Single edges for the joining operation: (%p)\n",
             s_edges);
  bft_printf("  Single edges: n_elts : %8d\n"
             "  Single edges: n_ranks: %8d\n",
             s_edges->n_elts, s_edges->n_ranks);

  if (s_edges->n_elts > 0) {
    for (i = 0; i < s_edges->n_ranks; i++)
      for (j = s_edges->index[i]; j < s_edges->index[i+1]; j++)
        bft_printf(" %9d | %6d | (%9d, %9d)\n",
                   j, s_edges->ranks[i], s_edges->array[2*j],
                     s_edges->array[2*j+1]);
    bft_printf("\n");
  }
  bft_printf_flush();

 }
#endif

  /* Free memory */

  BFT_FREE(sel_v2v_idx);
  BFT_FREE(sel_v2v_lst);

}
#endif /* defined(HAVE_MPI) */

/*============================================================================
 * Public function definitions
 *===========================================================================*/

/*----------------------------------------------------------------------------
 * Create and initialize a cs_join_t structure.
 *
 * parameters:
 *   join_number  <-- number related to the joining operation
 *   sel_criteria <-- boundary face selection criteria
 *   fraction     <-- value of the fraction parameter
 *   plane        <-- value of the plane parameter
 *   perio_num    <-- periodicity number (0 if not a periodic joining)
 *   verbosity    <-- level of verbosity required
 *
 * returns:
 *   a pointer to a new allocated cs_join_t structure
 *---------------------------------------------------------------------------*/

cs_join_t *
cs_join_create(int          join_number,
               const char  *sel_criteria,
               float        fraction,
               float        plane,
               int          perio_num,
               int          verbosity)
{
  size_t  l;

  cs_join_t  *join = NULL;

  BFT_MALLOC(join, 1, cs_join_t);

  join->selection = NULL;

  join->param = _join_param_define(join_number,
                                   fraction,
                                   plane,
                                   perio_num,
                                   verbosity);

  /* Copy the selection criteria for future use */

  l = strlen(sel_criteria);
  BFT_MALLOC(join->criteria, l + 1, char);
  strcpy(join->criteria, sel_criteria);

  return join;
}

/*----------------------------------------------------------------------------
 * Destroy a cs_join_t structure.
 *
 * parameters:
 *  join           <-> pointer to the cs_join_t structure to destroy
 *---------------------------------------------------------------------------*/

void
cs_join_destroy(cs_join_t  **join)
{
  if (*join != NULL) {

    cs_join_t  *_join = *join;

    _join_select_destroy(_join->param, &_join->selection);

    BFT_FREE(_join->criteria);

    BFT_FREE(_join);
    *join = NULL;

  }
}

/*----------------------------------------------------------------------------
 * Set advanced parameters to user-defined values.
 *
 * parameters:
 *   join           <-> pointer a to cs_join_t struct. to update
 *   mtf            <-- merge tolerance coefficient
 *   pmf            <-- pre-merge factor
 *   tcm            <-- tolerance computation mode
 *   icm            <-- intersection computation mode
 *   maxbrk         <-- max number of equivalences to break (merge step)
 *   max_sub_faces  <-- max. possible number of sub-faces by splitting a face
 *   tml            <-- tree max level
 *   tmb            <-- tree max boxes
 *   tmr            <-- tree max ratio
 *---------------------------------------------------------------------------*/

void
cs_join_set_advanced_param(cs_join_t   *join,
                           cs_real_t    mtf,
                           cs_real_t    pmf,
                           cs_int_t     tcm,
                           cs_int_t     icm,
                           cs_int_t     maxbrk,
                           cs_int_t     max_sub_faces,
                           cs_int_t     tml,
                           cs_int_t     tmb,
                           cs_real_t    tmr)
{
  /* Deepest level reachable during tree building */

  if (tml < 1)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the tml parameter.\n"
                "  It must be between > 0 and is here: %d\n"), tml);

  join->param.tree_max_level = tml;

  /* Max. number of boxes which can be related to a leaf of the tree
     if level != tree_max_level */

  if (tmb < 1)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the tmb parameter.\n"
                "  It must be between > 0 and is here: %d\n"), tmb);

  join->param.tree_n_max_boxes = tmb;

  /* Stop tree building if:
     n_linked_boxes > tree_max_box_ratio*n_init_boxes */

  if (tmr <= 0.0 )
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the tmr parameter.\n"
                "  It must be between > 0.0 and is here: %f\n"), tmr);

  join->param.tree_max_box_ratio = tmr;

  /* Coef. used to modify the tolerance associated to each vertex BEFORE the
     merge operation.
     If coef = 0.0 => no vertex merge
     If coef < 1.0 => reduce vertex merge
     If coef = 1.0 => no change
     If coef > 1.0 => increase vertex merge */

  if (mtf < 0.0)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the merge tolerance factor.\n"
                "  It must be positive or nul and not: %f\n"), mtf);

  join->param.merge_tol_coef = mtf;

   /* Maximum number of equivalence breaks */

  if (maxbrk < 0)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the max. number of tolerance breaks.\n"
                "  It must be between >= 0 and not: %d\n"), maxbrk);

  join->param.n_max_equiv_breaks = maxbrk;

  /* Pre-merge factor. This parameter is used to define a limit
     under which two vertices are merged before the merge step.
     Tolerance limit for the pre-merge = pmf * fraction
     Default value: 0.10 */

  join->param.pre_merge_factor = pmf;

  /* Tolerance computation mode */

  if ( (tcm)%10 < 1 || (tcm)%10 > 2)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the tcm parameter.\n"
                "  It must be between 1, 2 or 11, 12 and here is: %d\n"), tcm);

  join->param.tcm = tcm;

  /* Intersection computation mode */

  if (icm != 1 && icm != 2)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for icm parameter.\n"
                "  It must be 1 or 2 and here is: %d\n"), icm);

  join->param.icm = icm;

  /* Maximum number of sub-faces */

  if (max_sub_faces < 1)
    bft_error(__FILE__, __LINE__, 0,
              _("Mesh joining:"
                "  Forbidden value for the maxsf parameter.\n"
                "  It must be between > 0 and here is: %d\n"), max_sub_faces);

  join->param.max_sub_faces = max_sub_faces;

}

/*----------------------------------------------------------------------------
 * Create and initialize a cs_join_select_t structure.
 *
 * parameters:
 *   selection_criteria <-- pointer to a cs_mesh_select_t structure
 *   verbosity          <-- level of verbosity required
 *
 * returns:
 *   pointer to a newly created cs_join_select_t structure
 *---------------------------------------------------------------------------*/

cs_join_select_t *
cs_join_select_create(const char  *selection_criteria,
                      int          verbosity)
{
  cs_int_t  i, fid, cid;

  cs_int_t  *vtx_tag = NULL;
  cs_join_select_t  *selection = NULL;
  fvm_lnum_t  *order = NULL, *ordered_faces = NULL;
  fvm_interface_set_t  *ifs = NULL;
  cs_mesh_t  *mesh = cs_glob_mesh;

  const int  n_ranks = cs_glob_n_ranks;

  assert(mesh != NULL);

  /* Initialize cs_join_select_t struct. */

  BFT_MALLOC(selection, 1, cs_join_select_t);

  selection->n_init_b_faces = mesh->n_b_faces;
  selection->n_init_i_faces = mesh->n_i_faces;
  selection->n_init_vertices = mesh->n_vertices;

  selection->n_faces = 0;
  selection->n_g_faces = 0;

  selection->faces = NULL;
  selection->compact_face_gnum = NULL;
  selection->compact_rank_index = NULL;

  selection->cell_gnum = NULL;

  selection->n_vertices = 0;
  selection->n_g_vertices = 0;
  selection->vertices = NULL;

  selection->n_b_adj_faces = 0;
  selection->b_adj_faces = NULL;
  selection->n_i_adj_faces = 0;
  selection->i_adj_faces = NULL;

  selection->b_face_state = NULL;
  selection->i_face_state = NULL;

  selection->n_couples = 0;
  selection->per_v_couples = NULL;

  /*
     Single elements (Only possible in parallel. It appears
     when the domain splitting has a poor quality and elements
     on the joining interface are prisms or tetraedrals)
     s = single / c = coupled
  */

  selection->do_single_sync = false;
  selection->s_vertices = _create_join_sync();
  selection->c_vertices = _create_join_sync();
  selection->s_edges = _create_join_sync();
  selection->c_edges = _create_join_sync();

  /* Extract selected border faces */

  BFT_MALLOC(selection->faces, mesh->n_b_faces, fvm_lnum_t);

  cs_selector_get_b_face_list(selection_criteria,
                              &(selection->n_faces),
                              selection->faces);

  BFT_MALLOC(order, selection->n_faces, fvm_lnum_t);
  BFT_MALLOC(ordered_faces, selection->n_faces, fvm_lnum_t);

  fvm_order_local_allocated(selection->faces, NULL, order, selection->n_faces);

  for (i = 0; i < selection->n_faces; i++)
    ordered_faces[i] = selection->faces[order[i]];

  BFT_FREE(order);
  BFT_FREE(selection->faces);
  selection->faces = ordered_faces;

  /* Define cell_gnum: global numbers of the related cells */

  BFT_MALLOC(selection->cell_gnum, selection->n_faces, fvm_gnum_t);

  if (n_ranks == 1) { /* Serial treatment */

    selection->n_g_faces = selection->n_faces;

    for (i = 0; i < selection->n_faces; i++) {
      fid = selection->faces[i] - 1;
      selection->cell_gnum[i] = mesh->b_face_cells[fid];
    }

  }

#if defined(HAVE_MPI)
  if (n_ranks > 1) { /* Parallel treatment */

    fvm_gnum_t n_l_faces = selection->n_faces;

    MPI_Allreduce(&n_l_faces, &(selection->n_g_faces),
                  1, FVM_MPI_GNUM, MPI_SUM, cs_glob_mpi_comm);

    for (i = 0; i < selection->n_faces; i++) {
      fid = selection->faces[i] - 1;
      cid = mesh->b_face_cells[fid] - 1;
      selection->cell_gnum[i] = mesh->global_cell_num[cid];
    }

  }
#endif

  if (verbosity > 0)
    bft_printf(_("  Global number of boundary faces selected for joining: %10u\n"),
               selection->n_g_faces);

  /* Define a compact global numbering on selected border faces and
     build an index on ranks on this compact numbering */

  _compact_face_gnum_selection(selection->n_faces,
                               &(selection->compact_face_gnum),
                               &(selection->compact_rank_index));

  assert(selection->n_g_faces == selection->compact_rank_index[n_ranks]);

  /* Extract selected vertices from the selected border faces */

  cs_join_extract_vertices(selection->n_faces,
                           selection->faces,
                           mesh->b_face_vtx_idx,
                           mesh->b_face_vtx_lst,
                           mesh->n_vertices,
                           &(selection->n_vertices),
                           &(selection->vertices));

#if defined(HAVE_MPI)
  if (n_ranks > 1) { /* Search for missing vertices */

    assert(mesh->global_vtx_num != NULL);

    ifs = fvm_interface_set_create(mesh->n_vertices,
                                   NULL,
                                   mesh->global_vtx_num,
                                   NULL,
                                   0,
                                   NULL,
                                   NULL,
                                   NULL);

    assert(ifs != NULL);

    _get_missing_vertices(mesh->n_vertices,
                          ifs,
                          &vtx_tag,
                          selection);

  }
#endif

  /* Extract list of border faces contiguous to the selected vertices  */

  _extract_contig_faces(mesh->n_vertices,
                        selection,
                        mesh->n_b_faces,
                        mesh->b_face_vtx_idx,
                        mesh->b_face_vtx_lst,
                        &(selection->n_b_adj_faces),
                        &(selection->b_adj_faces));

  /* Remove border faces already defined in selection->faces */

  cs_join_clean_selection(&(selection->n_b_adj_faces),
                          &(selection->b_adj_faces),
                          selection->n_faces,
                          selection->faces);

  /* Extract list of interior faces contiguous to the selected vertices */

  _extract_contig_faces(mesh->n_vertices,
                        selection,
                        mesh->n_i_faces,
                        mesh->i_face_vtx_idx,
                        mesh->i_face_vtx_lst,
                        &(selection->n_i_adj_faces),
                        &(selection->i_adj_faces));

   /* Check if there is no forgotten vertex in the selection.
      Otherwise define structures to enable future synchronization.
      Only possible in parallel. */

#if defined(HAVE_MPI)
  if (n_ranks > 1) {

    _get_missing_edges(mesh->b_face_vtx_idx,
                       mesh->b_face_vtx_lst,
                       mesh->i_face_vtx_idx,
                       mesh->i_face_vtx_lst,
                       mesh->n_vertices,
                       vtx_tag,
                       ifs,
                       mesh->i_face_cells,
                       selection);

    BFT_FREE(vtx_tag);
    ifs = fvm_interface_set_destroy(ifs);

  }
#endif

  /* Face state setting */

  BFT_MALLOC(selection->b_face_state, mesh->n_b_faces, cs_join_state_t);
  BFT_MALLOC(selection->i_face_state, mesh->n_i_faces, cs_join_state_t);

  for (i = 0; i < mesh->n_b_faces; i++)
    selection->b_face_state[i] = CS_JOIN_STATE_UNDEF;

  for (i = 0; i < mesh->n_i_faces; i++)
    selection->i_face_state[i] = CS_JOIN_STATE_UNDEF;

  for (i = 0; i < selection->n_faces; i++)
    selection->b_face_state[selection->faces[i]-1] = CS_JOIN_STATE_ORIGIN;

  for (i = 0; i < selection->n_b_adj_faces; i++)
    selection->b_face_state[selection->b_adj_faces[i]-1] = CS_JOIN_STATE_ORIGIN;

  for (i = 0; i < selection->n_i_adj_faces; i++)
    selection->i_face_state[selection->i_adj_faces[i]-1] = CS_JOIN_STATE_ORIGIN;

  /* Display information according to the level of verbosity */

  if (verbosity > 1) {

    bft_printf(_("\n  Local information about selection structure:\n"));
    bft_printf(_("    number of faces:               %8d\n"),
               selection->n_faces);
    bft_printf(_("    number of vertices:            %8d\n"),
               selection->n_vertices);
    bft_printf(_("    number of adj. border faces:   %8d\n"),
               selection->n_b_adj_faces);
    bft_printf(_("    number of adj. interior faces: %8d\n"),
               selection->n_i_adj_faces);

    if (selection->do_single_sync == true) {
      bft_printf("\n Information on single/coupled elements:\n");
      bft_printf("   Number of single vertices : %6d with %3d related ranks\n",
                 selection->s_vertices->n_elts, selection->s_vertices->n_ranks);
      bft_printf("   Number of coupled vertices: %6d with %3d related ranks\n",
                 selection->c_vertices->n_elts, selection->c_vertices->n_ranks);
      bft_printf("   Number of single edges    : %6d with %3d related ranks\n",
                 selection->s_edges->n_elts, selection->s_edges->n_ranks);
      bft_printf("   Number of coupled edges   : %6d with %3d related ranks\n",
                 selection->c_edges->n_elts, selection->c_edges->n_ranks);
    }

    if (verbosity > 2) {
      bft_printf("\n  Compact index on ranks for the selected faces:\n");
      for (i = 0; i < n_ranks + 1; i++)
        bft_printf(" %5d | %11u\n", i, selection->compact_rank_index[i]);
      bft_printf("\n");
    }

#if 0 && defined(DEBUG) && !defined(NDEBUG)
  bft_printf(_("\n  Selected faces for the joining operation:\n"));
  for (i = 0; i < selection->n_faces; i++)
    bft_printf(" %9d | %9d | %10u | %10u\n",
               i, selection->faces[i], selection->compact_face_gnum[i],
               selection->cell_gnum[i]);
  bft_printf("\n");
#endif

#if 0 && defined(DEBUG) && !defined(NDEBUG)
  bft_printf(_("\n  Select vertices for the joining operation:\n"));
  for (i = 0; i < selection->n_vertices; i++)
    bft_printf(" %9d | %9d\n", i, selection->vertices[i]);
  bft_printf("\n");
#endif

#if 0 && defined(DEBUG) && !defined(NDEBUG)
  bft_printf(_("\n  Contiguous border faces for the joining operation:\n"));
  for (i = 0; i < selection->n_b_adj_faces; i++)
    bft_printf(" %9d | %9d\n", i, selection->b_adj_faces[i]);
  bft_printf("\n");
#endif

#if 0 && defined(DEBUG) && !defined(NDEBUG)
  bft_printf(_("\n  Contiguous interior faces for the joining operation:\n"));
  for (i = 0; i < selection->n_i_adj_faces; i++)
    bft_printf(" %9d | %9d\n", i, selection->i_adj_faces[i]);
  bft_printf("\n");
#endif

  } /* End if verbosity > 1 */

  bft_printf_flush();

  return  selection;
}

/*----------------------------------------------------------------------------
 * Define a set of parameters to control a contiguous distribution by block.
 *
 * parameters:
 *   n_g_elts   <-- global number of elements to treat
 *   n_ranks    <-- number of ranks in the MPI communicator related to the
 *                  cs_join_block_info_t structure to create
 *   local_rank <-- rank in the MPI communicator related to the
 *                  cs_join_block_info_t structure to create
 *
 * returns:
 *   a new defined cs_join_block_info_t structure
 *---------------------------------------------------------------------------*/

cs_join_block_info_t
cs_join_get_block_info(fvm_gnum_t  n_g_elts,
                       int         n_ranks,
                       int         local_rank)
{
  size_t  block_size, n_treat_elts;
  fvm_gnum_t  first_glob_num, last_glob_num;

  cs_join_block_info_t  block_info;

  /* Compute block_size */

  block_size = n_g_elts/n_ranks;
  if (n_g_elts % n_ranks > 0)
    block_size++;

  first_glob_num = local_rank * block_size + 1;
  last_glob_num = first_glob_num + block_size;

  if (last_glob_num > n_g_elts) {

    if (first_glob_num > n_g_elts) /* n_ranks > n_g_elts */
      n_treat_elts = 0;
    else
      n_treat_elts = n_g_elts - first_glob_num + 1;

  }
  else
    n_treat_elts = block_size;

  block_info.n_g_elts = n_g_elts;
  block_info.first_gnum = first_glob_num;

  block_info.size = block_size;
  block_info.local_size = n_treat_elts;

  block_info.n_ranks = n_ranks;
  block_info.local_rank = local_rank;

  return  block_info;
}

/*----------------------------------------------------------------------------
 * Extract vertices from a selection of faces.
 *
 * parameters:
 *   n_select_faces    <-- number of selected faces
 *   select_faces      <-- list of faces selected
 *   f2v_idx           <-- "face -> vertex" connect. index
 *   f2v_lst           <-- "face -> vertex" connect. list
 *   n_vertices        <-- number of vertices
 *   n_select_vertices <-> pointer to the number of selected vertices
 *   select_vertices   <-> pointer to the list of selected vertices
 *---------------------------------------------------------------------------*/

void
cs_join_extract_vertices(cs_int_t         n_select_faces,
                         const cs_int_t  *select_faces,
                         const cs_int_t  *f2v_idx,
                         const cs_int_t  *f2v_lst,
                         cs_int_t         n_vertices,
                         cs_int_t        *n_select_vertices,
                         cs_int_t        *select_vertices[])
{
  int  i, j, face_id;

  cs_int_t  _n_select_vertices = 0;
  cs_int_t  *counter = NULL, *_select_vertices = NULL;

  if (n_select_faces > 0) {

    BFT_MALLOC(counter, n_vertices, cs_int_t);

    for (i = 0; i < n_vertices; i++)
      counter[i] = 0;

    for (i = 0; i < n_select_faces; i++) {

      face_id = select_faces[i] - 1;

      for (j = f2v_idx[face_id] - 1; j < f2v_idx[face_id+1] - 1; j++)
        counter[f2v_lst[j]-1] = 1;

    }

    for (i = 0; i < n_vertices; i++)
      _n_select_vertices += counter[i];

    BFT_MALLOC(_select_vertices, _n_select_vertices, cs_int_t);

    _n_select_vertices = 0;
    for (i = 0; i < n_vertices; i++)
      if (counter[i] == 1)
        _select_vertices[_n_select_vertices++] = i + 1;

    assert(_n_select_vertices > 0);

    BFT_FREE(counter);

  } /* End if n_select_faces > 0 */

  /* Return pointers */

  *n_select_vertices = _n_select_vertices;
  *select_vertices = _select_vertices;
}

/*----------------------------------------------------------------------------
 * Eliminate redundancies found between two lists of elements.
 * Delete elements in elts[] and keep elements in the reference list.
 *
 * parameters:
 *  n_elts      <->  number of elements in the list to clean
 *  elts        <->  list of elements in the list to clean
 *  n_ref_elts  <--  number of elements in the reference list
 *  ref_elts    <--  list of reference elements
 *---------------------------------------------------------------------------*/

void
cs_join_clean_selection(cs_int_t   *n_elts,
                        cs_int_t   *elts[],
                        cs_int_t    n_ref_elts,
                        cs_int_t    ref_elts[])
{
  cs_int_t  i = 0, j = 0;
  cs_int_t  _n_elts = 0;
  cs_int_t  *_elts = *elts;

  while (i < *n_elts && j < n_ref_elts) {

    if (_elts[i] < ref_elts[j])
      _elts[_n_elts++] = _elts[i++];
    else if (_elts[i] > ref_elts[j])
      j++;
    else
      i++, j++;

  }

  for (;i < *n_elts; i++, _n_elts++)
    _elts[_n_elts] = _elts[i];

  BFT_REALLOC(_elts, _n_elts, cs_int_t);

  *n_elts = _n_elts;
  *elts = _elts;
}

/*----------------------------------------------------------------------------
 * Build vertex -> vertex index for a selection of faces.
 *
 * "v2v_idx" is already allocated to the number of vertices in the mesh.
 * At this stage, it is just a counter.
 *
 * parameters:
 *   n_faces <-- number of selected faces
 *   faces   <-- list of selected faces
 *   f2v_idx <-- face -> vertex connectivity index
 *   f2v_lst <-- face -> vertex connectivity list
 *   v2v_idx <-> index to build (already allocated and may be used again)
 *---------------------------------------------------------------------------*/

void
cs_join_build_edges_idx(cs_int_t        n_faces,
                        const cs_int_t  faces[],
                        const cs_int_t  f2v_idx[],
                        const cs_int_t  f2v_lst[],
                        cs_int_t        v2v_idx[])
{
  cs_int_t  i, j, v1, v2, fid, s, e;

  /* Loop on all selected faces. No need to loop on other faces because
     the selected vertices are all found with this only step. */

  for (i = 0; i < n_faces; i++) {

    fid = faces[i] - 1;
    s = f2v_idx[fid] - 1;
    e = f2v_idx[fid+1] - 1;

    for (j = s; j < e - 1; j++) { /* scan edges */

      v1 = f2v_lst[j];
      v2 = f2v_lst[j+1];

      if (v1 < v2)
        v2v_idx[v1] += 1;
      else if (v2 < v1)
        v2v_idx[v2] += 1;
      else
        bft_error(__FILE__, __LINE__, 0,
                  _("  Inconsistent mesh definition. Cannot build edges.\n"
                    "  Face %d has the same vertex %d twice.\n"), fid+1, v1);

    }

    /* Last edge */

    v1 = f2v_lst[e-1];
    v2 = f2v_lst[s];

    if (v1 < v2)
      v2v_idx[v1] += 1;
    else if (v2 < v1)
      v2v_idx[v2] += 1;
    else
      bft_error(__FILE__, __LINE__, 0,
                _("  Inconsistent mesh definition. Cannot build edges.\n"
                  "  Face %d has the same vertex %d twice.\n"), fid+1, v1);

  } /* End of loop on selected faces */
}

/*----------------------------------------------------------------------------
 * Build vertex -> vertex list for a selection of faces.
 * "count" and "v2v_lst" are already allocated to the number of vertices in
 * the mesh.
 *
 * parameters:
 *   n_faces <-- number of selected faces
 *   faces   <-- list of selected faces
 *   f2v_idx <-- face -> vertex connectivity index
 *   f2v_lst <-- face -> vertex connectivity list
 *   count   <-> array used to count the number of values already added
 *   v2v_idx <-- vertex -> vertex connect. index
 *   v2v_lst <-> vertex -> vertex connect. list to build (can be used again)
 *---------------------------------------------------------------------------*/

void
cs_join_build_edges_lst(cs_int_t        n_faces,
                        const cs_int_t  faces[],
                        const cs_int_t  f2v_idx[],
                        const cs_int_t  f2v_lst[],
                        cs_int_t        count[],
                        const cs_int_t  v2v_idx[],
                        cs_int_t        v2v_lst[])
{
  cs_int_t  i, j, v1_id, v2_id, fid, s, e, shift;

  for (i = 0; i < n_faces; i++) {

    fid = faces[i] - 1;
    s = f2v_idx[fid] - 1;
    e = f2v_idx[fid+1] - 1;

    for (j = s; j < e - 1; j++) { /* Scan edges */

      v1_id = f2v_lst[j] - 1;
      v2_id = f2v_lst[j+1] - 1;

      if (v1_id < v2_id) {
        shift = v2v_idx[v1_id] + count[v1_id];
        v2v_lst[shift] = v2_id + 1;
        count[v1_id] += 1;
      }
      else if (v2_id < v1_id) {
        shift = v2v_idx[v2_id] + count[v2_id];
        v2v_lst[shift] = v1_id + 1;
        count[v2_id] += 1;
      }

    }

    /* Last edge */

    v1_id = f2v_lst[e-1] - 1;
    v2_id = f2v_lst[s] - 1;

    if (v1_id < v2_id) {
      shift = v2v_idx[v1_id] + count[v1_id];
      v2v_lst[shift] = v2_id + 1;
      count[v1_id] += 1;
    }
    else if (v2_id < v1_id) {
      shift = v2v_idx[v2_id] + count[v2_id];
      v2v_lst[shift] = v1_id + 1;
      count[v2_id] += 1;
    }

  } /* End of loop on selected faces */

}

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

END_C_DECLS
