/*============================================================================
 * Write a nodal representation associated with a mesh and associated
 * variables to MEDCoupling objects
 *============================================================================*/

/*
  This file is part of Code_Saturne, a general-purpose CFD tool.

  Copyright (C) 1998-2013 EDF S.A.

  This program 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.

  This program 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
  this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
  Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

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

#include "cs_defs.h"

#if defined(HAVE_MEDCOUPLING)

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

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*----------------------------------------------------------------------------
 * MED library headers
 *----------------------------------------------------------------------------*/

#include <MED_version.h>

#include <MEDCouplingUMesh.hxx>
#include <MEDCouplingField.hxx>
#include <MEDCouplingFieldDouble.hxx>

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

#include "bft_error.h"
#include "bft_mem.h"
#include "bft_printf.h"

#include "fvm_defs.h"
#include "fvm_convert_array.h"
#include "fvm_io_num.h"
#include "fvm_nodal.h"
#include "fvm_nodal_priv.h"
#include "fvm_writer_priv.h"

#include "cs_block_dist.h"
#include "cs_file.h"
#include "cs_parall.h"
#include "cs_part_to_block.h"

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

#include "fvm_to_medcoupling.h"

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

using namespace ParaMEDMEM;

/*============================================================================
 * Local Type Definitions
 *============================================================================*/

/*----------------------------------------------------------------------------
 * MED field structure
 *----------------------------------------------------------------------------*/

typedef struct {

  int                       mesh_id;       /* Associated mesh structure id */

  int                       dim;           /* Field dimension */
  TypeOfTimeDiscretization  td;            /* NO_TIME, ONE_TIME, LINEAR_TIME,
                                              or CONST_ON_TIME_INTERVAL */
  MEDCouplingFieldDouble   *f;             /* Pointer to MED writer field */

} fvm_medcoupling_field_t;

/*----------------------------------------------------------------------------
 * MEDCoupling writer/reader structure
 *----------------------------------------------------------------------------*/

typedef struct {

  char                      *name;           /* Writer name */

  int                        rank;           /* Rank of current process
                                                in communicator */
  int                        n_ranks;        /* Size of communicator */

  int                        n_med_meshes;   /* Number of MED meshes */
  MEDCouplingUMesh         **med_meshes;     /* Array of pointers to MED mesh
                                                structure */
  fvm_writer_time_dep_t      time_dependency; /* Mesh time dependency */

  int                        n_fields;       /* Number of fields */
  fvm_medcoupling_field_t  **fields;         /* Array of field helper
                                                structures */

  int                        n_time_steps;   /* Number of meshe time steps */
  int                       *time_steps;     /* Array of meshe time steps */
  double                    *time_values;    /* Array of meshe time values */

#if defined(HAVE_MPI)
  MPI_Comm                   comm;           /* Associated MPI communicator */
#endif

} fvm_to_medcoupling_t;

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

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

/*----------------------------------------------------------------------------
 * Return the MEDCoupling mesh id associated with a given mesh name,
 * or -1 if no association found.
 *
 * parameters:
 *   writer    <-- writer structure
 *   mesh_name <-- mesh name
 *
 * returns:
 *    MEDCouping mesh id, or -1 if MEDCoupling mesh name is not associated
 *    with this writer structure in FVM
 *----------------------------------------------------------------------------*/

static int
_get_medcoupling_mesh_id(fvm_to_medcoupling_t  *writer,
                         const char            *mesh_name)
{
  int i;
  int retval = -1;

  assert(writer != NULL);

  for (i = 0; i < writer->n_med_meshes; i++) {
    if (strcmp(mesh_name, writer->med_meshes[i]->getName()) == 0)
      break;
  }

  if (i < writer->n_med_meshes)
    retval = i;

  return retval;
}

/*----------------------------------------------------------------------------
 * Create a MEDCoupling mesh structure.
 *
 * parameters:
 *   writer    <-- MEDCoupling structure.
 *   mesh      <-- FVM mesh  structure.
 *
 * returns:
 *   MEDCoupling mesh id.
 *----------------------------------------------------------------------------*/

static int
_add_medcoupling_mesh(fvm_to_medcoupling_t  *writer,
                      const fvm_nodal_t     *mesh)
{
  int id;
  int rank = 0;

  assert(writer != NULL);

  /* Add a new MEDCoupling mesh structure */

#if defined(HAVE_MPI)
  if (writer->comm != MPI_COMM_NULL)
    MPI_Comm_rank(writer->comm, &rank);
#endif

  MEDCouplingUMesh *m = NULL;

  if (rank < 1) {

    m = MEDCouplingUMesh::New();

    m->setName(mesh->name);
    m->setDescription("Generated by Code_Saturne/FVM.");
    m->setTimeUnit("s");
    m->setMeshDimension(fvm_nodal_get_max_entity_dim(mesh));

  }

  writer->n_med_meshes += 1;
  id = writer->n_med_meshes - 1;

  BFT_REALLOC(writer->med_meshes, writer->n_med_meshes, MEDCouplingUMesh *);

  writer->med_meshes[id] = m;

  return (writer->n_med_meshes - 1);
}

/*----------------------------------------------------------------------------
 * Define MED geometrical element type according to FVM element type
 *
 * parameters:
 *   fvm_elt_type <-- pointer to fvm element type.
 *
 * return:
 *   med geometrical element type.
 *----------------------------------------------------------------------------*/

static INTERP_KERNEL::NormalizedCellType
_get_norm_elt_type(const fvm_element_t fvm_elt_type)
{
  INTERP_KERNEL::NormalizedCellType  norm_elt_type;

  switch (fvm_elt_type) {

  case FVM_EDGE:
    norm_elt_type = INTERP_KERNEL::NORM_SEG2;
    break;

  case FVM_FACE_TRIA:
    norm_elt_type = INTERP_KERNEL::NORM_TRI3;
    break;

  case FVM_FACE_QUAD:
    norm_elt_type = INTERP_KERNEL::NORM_QUAD4;
    break;

  case FVM_FACE_POLY:
    norm_elt_type = INTERP_KERNEL::NORM_POLYGON;
    break;

  case FVM_CELL_TETRA:
    norm_elt_type = INTERP_KERNEL::NORM_TETRA4;
    break;

  case FVM_CELL_PYRAM:
    norm_elt_type = INTERP_KERNEL::NORM_PYRA5;
    break;

  case FVM_CELL_PRISM:
    norm_elt_type = INTERP_KERNEL::NORM_PENTA6;
    break;

  case FVM_CELL_HEXA:
    norm_elt_type = INTERP_KERNEL::NORM_HEXA8;
    break;

  case FVM_CELL_POLY:
    norm_elt_type = INTERP_KERNEL::NORM_POLYHED;
    break;

  default:
    norm_elt_type = INTERP_KERNEL::NORM_ERROR;
    bft_error(__FILE__, __LINE__, 0,
              "_get_norm_elt_type(): "
              "No association with INTERP_KERNEL element type has been found\n"
              "FVM element type: \"%i\"\n",
              (int)fvm_elt_type);

  } /* End of switch on element type */

  return norm_elt_type;
}

/*----------------------------------------------------------------------------
 * Get vertex order to describe MED element type.
 *
 * parameters:
 *   norm_elt_type  <-- MED element type.
 *   vertex_order  --> Pointer to vertex order array (0 to n-1).
 *
 *----------------------------------------------------------------------------*/

static void
_get_vertex_order(INTERP_KERNEL::NormalizedCellType   norm_elt_type,
                  int                                *vertex_order)
{
  switch(norm_elt_type) {

  case INTERP_KERNEL::NORM_SEG2:
    vertex_order[0] = 0;
    vertex_order[1] = 1;
    break;

  case INTERP_KERNEL::NORM_TRI3:
    vertex_order[0] = 0;
    vertex_order[1] = 2;
    vertex_order[2] = 1;
    break;

  case INTERP_KERNEL::NORM_QUAD4:
    vertex_order[0] = 0;
    vertex_order[1] = 3;
    vertex_order[2] = 2;
    vertex_order[3] = 1;
    break;

  case INTERP_KERNEL::NORM_TETRA4:
    vertex_order[0] = 0;
    vertex_order[1] = 2;
    vertex_order[2] = 1;
    vertex_order[3] = 3;
    break;

  case INTERP_KERNEL::NORM_PYRA5:
    vertex_order[0] = 0;
    vertex_order[1] = 3;
    vertex_order[2] = 2;
    vertex_order[3] = 1;
    vertex_order[4] = 4;
    break;

  case INTERP_KERNEL::NORM_PENTA6:
    vertex_order[0] = 0;
    vertex_order[1] = 2;
    vertex_order[2] = 1;
    vertex_order[3] = 3;
    vertex_order[4] = 5;
    vertex_order[5] = 4;
    break;

  case INTERP_KERNEL::NORM_HEXA8:
    vertex_order[0] = 0;
    vertex_order[1] = 3;
    vertex_order[2] = 2;
    vertex_order[3] = 1;
    vertex_order[4] = 4;
    vertex_order[5] = 7;
    vertex_order[6] = 6;
    vertex_order[7] = 5;
    break;

  case INTERP_KERNEL::NORM_POLYGON:
    vertex_order[0] = -1;
    break;

  case INTERP_KERNEL::NORM_POLYHED:
    vertex_order[0] = -1;
    break;

  default:
    bft_error(__FILE__, __LINE__, 0,
              "_get_vertex_order(): No associated MED element type known\n"
              "INTERP_KERNEL element type: \"%i\"\n",
              (int)norm_elt_type);
  }

  return;
}

/*----------------------------------------------------------------------------
 * Return the MEDCoupling field id associated with given mesh and field names,
 * or -1 if no association found.
 *
 * parameters:
 *   writer    <-- MED writer structure.
 *   fieldname <-- input fieldname.
 *   mesh_id   <-- id of associated mesh in structure.
 *   dim       <-- number of field components.
 *   location  <-- mesh location (cells or vertices).
 *   type      <-- field type (cells, nodes)
 *   td        <-- time discretization type
 *
 * returns
 *   field id in writer structure, or -1 if not found
 *----------------------------------------------------------------------------*/

static int
_get_medcoupling_field_id(fvm_to_medcoupling_t      *writer,
                          const char                *fieldname,
                          int                        mesh_id,
                          int                        dim,
                          fvm_writer_var_loc_t       location,
                          TypeOfTimeDiscretization   td)
{
  int n_fields, f_id;

  TypeOfField type = (location == FVM_WRITER_PER_NODE) ? ON_NODES : ON_CELLS;

  MEDCouplingFieldDouble  *f = NULL;

  /* Loop on fields to know if field has already been created */

  n_fields = writer->n_fields;

  for (f_id = 0; f_id < n_fields; f_id++) {

    f = (writer->fields[f_id])->f;

#if (SALOMEMED_VERSION  >= 0x070300)
    if (   writer->fields[f_id]->mesh_id == mesh_id
        && strcmp(fieldname, f->getName().c_str()) == 0) {
#else
    if (   writer->fields[f_id]->mesh_id == mesh_id
        && strcmp(fieldname, f->getName()) == 0) {
#endif

      /* If field exists, check that dimensions and type are compatible */

      fvm_medcoupling_field_t *field = writer->fields[f_id];
      TypeOfField type_ref = f->getTypeOfField();

      if (field->dim != dim)
        bft_error(__FILE__, __LINE__, 0,
                  _("MEDCoupling field \"%s\" already defined for\n"
                    "coupling \"%s\" and mesh \"%s\" with %d components,\n"
                    "but re-defined with %d components."),
                  fieldname, writer->name,
                  writer->med_meshes[field->mesh_id]->getName(),
                  field->dim, dim);

      else if (type_ref != type)
        bft_error(__FILE__, __LINE__, 0,
                  _("MEDCoupling field \"%s\" already defined for\n"
                    "coupling \"%s\" and mesh \"%s\" with type %d,\n"
                    "but re-defined with type %d."),
                  fieldname, writer->name,
                  writer->med_meshes[field->mesh_id]->getName(),
                  (int)type_ref, type);

      else if ((writer->fields[f_id])->td != td)
        bft_error(__FILE__, __LINE__, 0,
                  _("MEDCoupling field \"%s\" already defined for writer\n"
                    "\"%s\" and mesh \"%s\" with time discretization %d,\n"
                    "but re-defined with time discretization %d."),
                  fieldname, writer->name,
                  writer->med_meshes[field->mesh_id]->getName(),
                  (int)(field->td), (int)td);

      /* return id of field if compatible */

      else
        return f_id;

      break;
    }
  }

  return -1;
}

/*----------------------------------------------------------------------------
 * Create a MEDCoupling field structure.
 *
 * parameters:
 *   writer  <-- MED writer structure.
 *   fieldname <-- input fieldname.
 *   mesh_id   <-- id of associated mesh in structure.
 *   dim       <-- number of field components.
 *   location  <-- mesh location (cells or vertices).
 *   type      <-- field type (cells, nodes)
 *   td        <-- time discretization type
 *
 * returns
 *   field id in writer structure
 *----------------------------------------------------------------------------*/

static int
_add_medcoupling_field(fvm_to_medcoupling_t      *writer,
                       const char                *fieldname,
                       int                        mesh_id,
                       int                        dim,
                       fvm_writer_var_loc_t       location,
                       TypeOfTimeDiscretization   td)
{
  TypeOfField type = (location == FVM_WRITER_PER_NODE) ? ON_NODES : ON_CELLS;

  int f_id = -1;

  /* Prepare writer structure */

  f_id = writer->n_fields;

  BFT_REALLOC(writer->fields,
              writer->n_fields + 1,
              fvm_medcoupling_field_t *);

  /* Build ParaFIELD object if required */

  MEDCouplingFieldDouble  *f = NULL;

  if (writer->med_meshes[mesh_id] != NULL) {

    f = MEDCouplingFieldDouble::New(type, td);

    f->setName(fieldname);

    /* Assign array to field (filled later) */

    int n_locs = 0;
    DataArrayDouble *array = DataArrayDouble::New();

    if (location == FVM_WRITER_PER_NODE)
      n_locs = writer->med_meshes[mesh_id]->getNumberOfNodes();
    else if (location == FVM_WRITER_PER_ELEMENT)
      n_locs = writer->med_meshes[mesh_id]->getNumberOfCells();

    array->alloc(n_locs, dim);
    f->setArray(array);

  }

  /* Update writer structure */

  f_id = writer->n_fields;

  BFT_REALLOC(writer->fields,
              writer->n_fields + 1,
              fvm_medcoupling_field_t *);

  BFT_MALLOC(writer->fields[f_id], 1, fvm_medcoupling_field_t);

  writer->fields[f_id]->mesh_id = mesh_id;
  writer->fields[f_id]->td = td;
  writer->fields[f_id]->dim = dim;

  writer->fields[f_id]->f = f;

  writer->n_fields++;

  return f_id;
}

#if defined(HAVE_MPI)

/*----------------------------------------------------------------------------
 * Write vertex coordinates to a MEDCoupling object in parallel mode
 *
 * parameters:
 *   this_writer <-- pointer to associated writer
 *   mesh        <-- pointer to nodal mesh structure
 *   med_mesh    <-- pointer to MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_vertex_coords_g(const fvm_to_medcoupling_t  *this_writer,
                        const fvm_nodal_t           *mesh,
                        MEDCouplingUMesh            *med_mesh)
{
  cs_lnum_t   i, j;
  size_t stride;

  DataArrayDouble  *Coords = NULL;
  double  *block_coords = NULL;

  const double  *vertex_coords = mesh->vertex_coords;
  const cs_lnum_t  n_vertices
      = fvm_io_num_get_local_count(mesh->global_vertex_num);
  const cs_gnum_t  n_g_vertices
    = fvm_io_num_get_global_count(mesh->global_vertex_num);
  const cs_gnum_t  *g_num
    = fvm_io_num_get_global_num(mesh->global_vertex_num);

  /* Initialize distribution info */

  cs_block_dist_info_t bi
    = cs_block_dist_compute_sizes(this_writer->rank,
                                  this_writer->n_ranks,
                                  0,
                                  n_g_vertices,
                                  n_g_vertices);

  cs_part_to_block_t *d = cs_part_to_block_create_by_gnum(this_writer->comm,
                                                          bi,
                                                          n_vertices,
                                                          g_num);

  /* Vertex coordinates */
  /*--------------------*/

  stride = (size_t)(mesh->dim);

  if (this_writer->rank < 1) {
    Coords = DataArrayDouble::New();
    Coords->alloc(n_g_vertices, 3);
    block_coords = Coords->getPointer();
  }

  if (mesh->parent_vertex_num != NULL || mesh->dim < 3) {

    double  *part_coords = NULL;

    BFT_MALLOC(part_coords, n_vertices, double);

    if (mesh->parent_vertex_num != NULL) {
      const cs_lnum_t  *parent_vertex_num = mesh->parent_vertex_num;
      for (i = 0; i < n_vertices; i++) {
        for (j = 0; j < mesh->dim; j++)
          part_coords[i*3 + j]
            = vertex_coords[(parent_vertex_num[i]-1)*stride + j];
        for (; j < 3; j++)
          part_coords[i*3 + j] = 0.;
      }
    }
    else {
      for (i = 0; i < n_vertices; i++) {
        for (j = 0; j < mesh->dim; j++)
          part_coords[i*3 + j] = vertex_coords[i*stride + j];
        for (; j < 3; j++)
          part_coords[i*3 + j] = 0.;
      }
    }

    cs_part_to_block_copy_array(d,
                                CS_DOUBLE,
                                3,
                                part_coords,
                                block_coords);

    BFT_FREE(part_coords);

  }
  else

    cs_part_to_block_copy_array(d,
                                CS_DOUBLE,
                                3,
                                vertex_coords,
                                block_coords);

  if (med_mesh != NULL) {
    assert(this_writer->rank < 1);
    med_mesh->setCoords(Coords);
    Coords->decrRef();
  }

  cs_part_to_block_destroy(&d);
}

#endif /* defined(HAVE_MPI) */

/*----------------------------------------------------------------------------
 * Write vertex coordinates to a MEDCoupling object in serial mode
 *
 * parameters:
 *   mesh        <-- pointer to nodal mesh structure
 *   med_mesh    <-- pointer to MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_vertex_coords_l(const fvm_nodal_t           *mesh,
                        MEDCouplingUMesh            *med_mesh)
{
  cs_lnum_t   i, j;
  size_t stride;

  DataArrayDouble  *Coords = NULL;
  double  *block_coords = NULL;

  const double  *vertex_coords = mesh->vertex_coords;
  const cs_lnum_t  n_vertices = mesh->n_vertices;

  /* Vertex coordinates */
  /*--------------------*/

  stride = (size_t)(mesh->dim);

  Coords = DataArrayDouble::New();
  Coords->alloc(n_vertices, 3);
  block_coords = Coords->getPointer();

  if (mesh->parent_vertex_num != NULL || mesh->dim < 3) {

    if (mesh->parent_vertex_num != NULL) {
      const cs_lnum_t  *parent_vertex_num = mesh->parent_vertex_num;
      for (i = 0; i < n_vertices; i++) {
        for (j = 0; j < mesh->dim; j++)
          block_coords[i*3 + j]
            = vertex_coords[(parent_vertex_num[i]-1)*stride + j];
        for (; j < 3; j++)
          block_coords[i*3 + j] = 0.;
      }
    }
    else {
      for (i = 0; i < n_vertices; i++) {
        for (j = 0; j < mesh->dim; j++)
          block_coords[i*3 + j] = vertex_coords[i*stride + j];
        for (; j < 3; j++)
          block_coords[i*3 + j] = 0.;
      }
    }

  }
  else
    memcpy(block_coords, vertex_coords, 3*mesh->n_vertices*sizeof(double));

  med_mesh->setCoords(Coords);
  Coords->decrRef();
}

/*----------------------------------------------------------------------------
 * Write strided connectivity block to a MEDCoupling mesh
 *
 * The connectivity on input may use 1 to n numbering, so it is shifted
 * by -1 here.
 *
 * parameters:
 *   type     <-- FVM element type
 *   n_elts   <-- number of elements in block
 *   connect  <-- connectivity array
 *   med_mesh <-> pointer to MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_write_connect_block(fvm_element_t      type,
                     cs_lnum_t          n_elts,
                     const cs_lnum_t    connect[],
                     MEDCouplingUMesh  *med_mesh)
{
  int vertex_order[8];
  int elt_buf[8];
  cs_lnum_t  i;
  int  j;

  const int  stride = fvm_nodal_n_vertices_element[type];
  INTERP_KERNEL::NormalizedCellType med_type = _get_norm_elt_type(type);

  _get_vertex_order(med_type, vertex_order);

  assert(med_mesh != NULL);

  for (i = 0; i < n_elts; i++) {
    for (j = 0; j < stride; j++)
      elt_buf[j] = connect[i*stride + vertex_order[j]] - 1;
    med_mesh->insertNextCell(med_type, stride, elt_buf);
  }
}

#if defined(HAVE_MPI)

/*----------------------------------------------------------------------------
 * Write strided global connectivity block to a MEDCoupling object
 *
 * parameters:
 *   type          <-- FVM element type
 *   num_start     <-- global number of first element for this block
 *   num_end       <-- global number of past last element for this block
 *   block_connect <-> global connectivity block array
 *   comm          <-- associated MPI communicator
 *   med_mesh      <-> pointer to MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_write_block_connect_g(fvm_element_t     type,
                       cs_gnum_t         num_start,
                       cs_gnum_t         num_end,
                       cs_lnum_t         block_connect[],
                       MPI_Comm          comm,
                       MEDCouplingUMesh  *med_mesh)
{
  cs_lnum_t *_block_connect = NULL;

  const int  stride = fvm_nodal_n_vertices_element[type];

  cs_file_serializer_t *s = cs_file_serializer_create(sizeof(cs_lnum_t),
                                                      stride,
                                                      num_start,
                                                      num_end,
                                                      0,
                                                      block_connect,
                                                      comm);

  do {
    cs_gnum_t range[2] = {num_start, num_end};

    _block_connect = (cs_lnum_t *)cs_file_serializer_advance(s, range);

    if (_block_connect != NULL) /* only possible on rank 0 */
      _write_connect_block(type,
                           (range[1] - range[0]),
                           _block_connect,
                           med_mesh);

  } while (_block_connect != NULL);

  cs_file_serializer_destroy(&s);
}

/*----------------------------------------------------------------------------
 * Write block-distributed indexed element (polygons or polyhedra)
 * cell -> vertex connectivity to a MEDCoupling object in parallel mode.
 *
 * Values equal to -2 are used for element (polyhedra of polygon)  bounds,
 * -1 for face separators within polyhedra.
 *
 * parameters:
 *   med_type      <-- associated MED element type
 *   num_start     <-- global number of first element for this block
 *   num_end       <-- global number of past last element for this block
 *   block_index   <-- global connectivity block array
 *   block_connect <-> global connectivity block array
 *   comm          <-- associated MPI communicator
 *   med_mesh      <-> MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_write_block_indexed(INTERP_KERNEL::NormalizedCellType  med_type,
                     cs_gnum_t                          num_start,
                     cs_gnum_t                          num_end,
                     const cs_lnum_t                    block_index[],
                     cs_lnum_t                          block_connect[],
                     MPI_Comm                           comm,
                     MEDCouplingUMesh                  *med_mesh)
{
  cs_gnum_t block_size = 0, block_start = 0, block_end = 0;
  int elt_buf_size = 8;
  int *elt_buf = NULL;

  BFT_MALLOC(elt_buf, elt_buf_size, int);

  /* Prepare write to object */

  block_size = block_index[num_end - num_start];

  MPI_Scan(&block_size, &block_end, 1, CS_MPI_GNUM, MPI_SUM, comm);
  block_end += 1;
  block_start = block_end - block_size;

  cs_lnum_t   i;
  cs_lnum_t *_block_vtx_num = NULL;
  cs_file_serializer_t *s = cs_file_serializer_create(sizeof(cs_lnum_t),
                                                      1,
                                                      block_start,
                                                      block_end,
                                                      0,
                                                      block_connect,
                                                      comm);

  do {
    cs_gnum_t j;
    int k = 0;
    cs_gnum_t range[2] = {block_start, block_end};
    _block_vtx_num = (cs_lnum_t *)cs_file_serializer_advance(s, range);
    if (_block_vtx_num != NULL) { /* only possible on rank 0 */
      assert(med_mesh != NULL);
      for (i = 0, j = range[0]; j < range[1]; i++, j++) {
        if (_block_vtx_num[i] > -2) {
          if (k +1 >= elt_buf_size) {
            elt_buf_size *= 2;
            BFT_REALLOC(elt_buf, elt_buf_size, int);
          }
          elt_buf[k++] = _block_vtx_num[i];
        }
        else {
          med_mesh->insertNextCell(med_type, k, elt_buf);
          k = 0;
        }
      }
    }
  } while (_block_vtx_num != NULL);

  cs_file_serializer_destroy(&s);

  BFT_FREE(elt_buf);
}

/*----------------------------------------------------------------------------
 * Write indexed element (polygons or polyhedra) cell -> vertex connectivity
 * to a MEDCoupling object in parallel mode.
 *
 * parameters:
 *   med_type           <-- associated MED element type
 *   global_vertex_num  <-- vertex global numbering
 *   global_element_num <-- global element numbering
 *   vertex_index       <-- element -> vertex index
 *   vertex_num         <-- element -> vertex number
 *   comm               <-- associated MPI communicator
 *   med_mesh           <-> MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_write_indexed_connect_g(INTERP_KERNEL::NormalizedCellType   med_type,
                         const fvm_io_num_t                 *global_element_num,
                         const cs_lnum_t                     vertex_index[],
                         const cs_lnum_t                     vertex_num[],
                         MPI_Comm                            comm,
                         MEDCouplingUMesh                   *med_mesh)
{
  int rank, n_ranks;
  cs_block_dist_info_t bi;

  cs_datatype_t lnum_type = (sizeof(cs_lnum_t) == 8) ? CS_INT64 : CS_INT32;
  cs_gnum_t loc_size = 0, tot_size = 0, block_size = 0;
  cs_part_to_block_t  *d = NULL;
  cs_lnum_t   *block_index = NULL;
  cs_lnum_t  *block_vtx_num = NULL;
  size_t  min_block_size
    = cs_parall_get_min_coll_buf_size() / sizeof(cs_lnum_t);

  const cs_gnum_t   n_g_elements
    = fvm_io_num_get_global_count(global_element_num);
  const cs_lnum_t   n_elements
    = fvm_io_num_get_local_count(global_element_num);
  const cs_gnum_t   *g_elt_num
    = fvm_io_num_get_global_num(global_element_num);

  /* Get info on the current MPI communicator */

  MPI_Comm_rank(comm, &rank);
  MPI_Comm_size(comm, &n_ranks);

  /* Adjust min block size based on minimum element size */

  loc_size = vertex_index[n_elements];
  MPI_Allreduce(&loc_size, &tot_size, 1, CS_MPI_GNUM, MPI_SUM, comm);

  min_block_size /= (tot_size / n_g_elements);

  /* Allocate memory for additionnal indexes */

  bi = cs_block_dist_compute_sizes(rank,
                                   n_ranks,
                                   0,
                                   min_block_size,
                                   n_g_elements);

  BFT_MALLOC(block_index, bi.gnum_range[1] - bi.gnum_range[0] + 1, cs_lnum_t);

  d = cs_part_to_block_create_by_gnum(comm, bi, n_elements, g_elt_num);

  cs_part_to_block_copy_index(d,
                              vertex_index,
                              block_index);

  block_size = block_index[bi.gnum_range[1] - bi.gnum_range[0]];

  BFT_MALLOC(block_vtx_num, block_size, cs_lnum_t);

  cs_part_to_block_copy_indexed(d,
                                lnum_type,
                                vertex_index,
                                vertex_num,
                                block_index,
                                block_vtx_num);

  /* Write to object */

  _write_block_indexed(med_type,
                       bi.gnum_range[0],
                       bi.gnum_range[1],
                       block_index,
                       block_vtx_num,
                       comm,
                       med_mesh);

  /* Free memory */

  BFT_FREE(block_vtx_num);
  cs_part_to_block_destroy(&d);
  BFT_FREE(block_index);
}

/*----------------------------------------------------------------------------
 * Write polyhedra from a nodal mesh to a MEDCoupling object in parallel mode
 *
 * parameters:
 *   export_section    <-- pointer to MEDCoupling section helper structure
 *   global_vertex_num <-- pointer to vertex global numbering
 *   comm              <-- associated MPI communicator
 *   med_mesh          <-> MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_nodal_polyhedra_g(const fvm_nodal_section_t  *section,
                          const fvm_io_num_t         *global_vertex_num,
                          MPI_Comm                    comm,
                          MEDCouplingUMesh           *med_mesh)
{
  int rank, n_ranks;
  cs_lnum_t  i, j, k, l, face_id;

  cs_lnum_t  cell_length;

  cs_lnum_t  *part_vtx_idx = NULL;
  cs_lnum_t  *part_vtx_num = NULL;

  const cs_gnum_t  *g_vtx_num
    = fvm_io_num_get_global_num(global_vertex_num);

  /* Get info on the current MPI communicator */

  MPI_Comm_rank(comm, &rank);
  MPI_Comm_size(comm, &n_ranks);

  /* Export cell->vertex connectivity by blocks */
  /*--------------------------------------------*/

  BFT_MALLOC(part_vtx_idx, section->n_elements + 1, cs_lnum_t);

  l = 0;

  /* Add -1 to cell vertex connectivity to mark face
     bounds, -2 for cell bounds */

  part_vtx_idx[0] = 0;
  for (i = 0; i < section->n_elements; i++) {
    cell_length = 0;
    for (j = section->face_index[i]; j < section->face_index[i+1]; j++) {
      face_id = CS_ABS(section->face_num[j]) - 1;
      cell_length += (  section->vertex_index[face_id+1]
                      - section->vertex_index[face_id]);
    }
    cell_length += (section->face_index[i+1] - section->face_index[i]);
    part_vtx_idx[i+1] = part_vtx_idx[i] + cell_length;
  }

  BFT_MALLOC(part_vtx_num, part_vtx_idx[section->n_elements], cs_lnum_t);

  l = 0;

  for (i = 0; i < section->n_elements; i++) {
    for (j = section->face_index[i]; j < section->face_index[i+1]; j++) {
      if (section->face_num[j] > 0) {
        face_id = section->face_num[j] - 1;
        for (k = section->vertex_index[face_id];
             k < section->vertex_index[face_id+1];
             k++)
          part_vtx_num[l++] = g_vtx_num[section->vertex_num[k] - 1] - 1;
      }
      else {
        face_id = -section->face_num[j] - 1;
        k = section->vertex_index[face_id];
        part_vtx_num[l++] = g_vtx_num[section->vertex_num[k] - 1];
        for (k = section->vertex_index[face_id+1] - 1;
             k > section->vertex_index[face_id];
             k--)
          part_vtx_num[l++] = g_vtx_num[section->vertex_num[k] - 1] - 1;
      }
      part_vtx_num[l++] = -1; /* mark face limits */
    }
    part_vtx_num[l-1] = -2; /* replace face limits by element end */
  }

  /* Now distribute and write cells -> vertices connectivity */

  _write_indexed_connect_g(INTERP_KERNEL::NORM_POLYHED,
                           section->global_element_num,
                           part_vtx_idx,
                           part_vtx_num,
                           comm,
                           med_mesh);

  BFT_FREE(part_vtx_num);
  BFT_FREE(part_vtx_idx);
}

#endif /* defined(HAVE_MPI) */

/*----------------------------------------------------------------------------
 * Write polyhedra from a nodal mesh to a MEDCoupling object in serial mode
 *
 * parameters:
 *   export_section <-- pointer to MEDCoupling section helper structure
 *   med_mesh       <-> MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_nodal_polyhedra_l(const fvm_nodal_section_t  *section,
                          MEDCouplingUMesh           *med_mesh)
{
  int  face_sgn;
  cs_lnum_t  i, j, k, l;

  cs_lnum_t  face_length, face_id;

  int elt_buf_size = 8;
  int *elt_buf = NULL;

  BFT_MALLOC(elt_buf, elt_buf_size, int);

  /* Write cell/vertex connectivity */
  /*--------------------------------*/

  for (i = 0; i < section->n_elements; i++) {

    int  m = 0;

    /* Loop on cell faces */

    for (j = section->face_index[i];
         j < section->face_index[i+1];
         j++) {

      /* Print face vertex numbers */

      if (section->face_num[j] > 0) {
        face_id = section->face_num[j] - 1;
        face_sgn = 1;
      }
      else {
        face_id = -section->face_num[j] - 1;
        face_sgn = -1;
      }

      face_length = (  section->vertex_index[face_id+1]
                     - section->vertex_index[face_id]);

      while (m + face_length + 1 > elt_buf_size) {
        elt_buf_size *= 2;
        BFT_REALLOC(elt_buf, elt_buf_size, int);
      }

      if (j >  section->face_index[i])
        elt_buf[m++] = -1;

      for (k = 0; k < face_length; k++) {
        l =    section->vertex_index[face_id]
            + (face_length + (k*face_sgn))%face_length;
        elt_buf[m++] = section->vertex_num[l] - 1;
      }

    } /* End of loop on cell faces */

    med_mesh->insertNextCell(INTERP_KERNEL::NORM_POLYHED, m, elt_buf);

  } /* End of loop on polyhedral cells */

  BFT_FREE(elt_buf);
}

#if defined(HAVE_MPI)

/*----------------------------------------------------------------------------
 * Write polygons from a nodal mesh to a MEDCoupling object in parallel mode
 *
 * parameters:
 *   export_section    <-- pointer to MEDCoupling section helper structure
 *   global_vertex_num <-- pointer to vertex global numbering
 *   comm              <-- associated MPI communicator
 *   med_mesh          <-> MEDCouplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_nodal_polygons_g(const fvm_nodal_section_t  *section,
                         const fvm_io_num_t         *global_vertex_num,
                         MPI_Comm                    comm,
                         MEDCouplingUMesh           *med_mesh)
{
  /* Export face->vertex connectivity by blocks */
  /*--------------------------------------------*/

  cs_lnum_t   i, j, k;
  cs_lnum_t   *_part_vtx_idx = NULL;
  const cs_lnum_t   *part_vtx_idx = NULL;
  cs_lnum_t  *part_vtx_num = NULL;

  const cs_gnum_t   *g_vtx_num
    = fvm_io_num_get_global_num(global_vertex_num);

  /* Add -1 markers to cell vertex connectivity for element bounds */

  BFT_MALLOC(_part_vtx_idx, section->n_elements + 1, cs_lnum_t);

  _part_vtx_idx[0] = 0;
  for (i = 0; i < section->n_elements; i++)
    _part_vtx_idx[i+1] = _part_vtx_idx[i] + (  section->vertex_index[i+1]
                                             - section->vertex_index[i]) + 1;

  part_vtx_idx = _part_vtx_idx;

  /* Build connectivity array */

  BFT_MALLOC(part_vtx_num, part_vtx_idx[section->n_elements], cs_lnum_t);

  for (i = 0, k = 0; i < section->n_elements; i++) {
    for (j = section->vertex_index[i+1] - 1;
         j > section->vertex_index[i];
         j++)
      part_vtx_num[k++] = g_vtx_num[section->vertex_num[j] - 1] - 1;
    part_vtx_num[k++] = -2; /* mark element bounds */
  }

  /* Now distribute and write cell -> vertices connectivity */

  _write_indexed_connect_g(INTERP_KERNEL::NORM_POLYGON,
                           section->global_element_num,
                           part_vtx_idx,
                           part_vtx_num,
                           comm,
                           med_mesh);

  BFT_FREE(part_vtx_num);
  if (_part_vtx_idx != NULL)
    BFT_FREE(_part_vtx_idx);
}

#endif /* defined(HAVE_MPI) */

/*----------------------------------------------------------------------------
 * Write polygons from a nodal mesh to a text object in serial mode
 *
 * parameters:
 *   export_section <-- pointer to MEDCoupling section helper structure
 *   med_mesh       <-> MEDCouuplingUMesh object
*----------------------------------------------------------------------------*/

static void
_export_nodal_polygons_l(const fvm_nodal_section_t  *section,
                         MEDCouplingUMesh           *med_mesh)


{
  cs_lnum_t   i, j;

  int elt_buf_size = 8;
  int *elt_buf = NULL;

  BFT_MALLOC(elt_buf, elt_buf_size, int);

  /* Loop on all polygonal faces */
  /*-----------------------------*/

  for (i = 0; i < section->n_elements; i++) {

    int k = 0;

    int face_size = section->vertex_index[i+1] - section->vertex_index[i];
    while (elt_buf_size < face_size) {
      elt_buf_size *= 2;
      BFT_REALLOC(elt_buf, elt_buf_size, int);
    }

    for (j = section->vertex_index[i];
         j < section->vertex_index[i+1];
         j++)
      elt_buf[k++] = section->vertex_num[j] - 1;

    med_mesh->insertNextCell(INTERP_KERNEL::NORM_POLYGON, k, elt_buf);

  } /* End of loop on polygonal faces */

  BFT_FREE(elt_buf);
}

#if defined(HAVE_MPI)

/*----------------------------------------------------------------------------
 * Write strided elements from a nodal mesh to a MEDCoupling object in
 * parallel mode
 *
 * parameters:
 *   export_section    <-- pointer to nodal mesh section
 *   global_vertex_num <-- pointer to vertex global numbering
 *   comm              <-- associated MPI communicator
 *   med_mesh          <-> MEDCouuplingUMesh object (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_nodal_strided_g(const fvm_nodal_section_t  *section,
                        const fvm_io_num_t         *global_vertex_num,
                        MPI_Comm                    comm,
                        MEDCouplingUMesh           *med_mesh)
{
  int  rank, n_ranks;
  cs_lnum_t   i, j;

  cs_datatype_t lnum_type = (sizeof(cs_lnum_t) == 8) ? CS_INT64 : CS_INT32;

  cs_block_dist_info_t bi;

  cs_lnum_t   block_size = 0;
  cs_part_to_block_t  *d = NULL;
  cs_lnum_t  *part_vtx_num = NULL, *block_vtx_num = NULL;

  const int  stride = fvm_nodal_n_vertices_element[section->type];

  const size_t  min_block_size
    = cs_parall_get_min_coll_buf_size() / (sizeof(cs_lnum_t) * stride);

  const cs_lnum_t   n_elements
    = fvm_io_num_get_local_count(section->global_element_num);
  const cs_gnum_t   n_g_elements
    = fvm_io_num_get_global_count(section->global_element_num);
  const cs_gnum_t   *g_elt_num
    = fvm_io_num_get_global_num(section->global_element_num);
  const cs_gnum_t   *g_vtx_num
    = fvm_io_num_get_global_num(global_vertex_num);

  /* Get info on the current MPI communicator */

  MPI_Comm_rank(comm, &rank);
  MPI_Comm_size(comm, &n_ranks);

  /* Prepare distribution structures */

  bi = cs_block_dist_compute_sizes(rank,
                                   n_ranks,
                                   0,
                                   min_block_size,
                                   n_g_elements);

  d = cs_part_to_block_create_by_gnum(comm,
                                      bi,
                                      n_elements,
                                      g_elt_num);

  /* Build connectivity */

  block_size = bi.gnum_range[1] - bi.gnum_range[0];

  BFT_MALLOC(block_vtx_num, block_size*stride, cs_lnum_t);
  BFT_MALLOC(part_vtx_num, n_elements*stride, cs_lnum_t);

  for (i = 0; i < n_elements; i++) {
    for (j = 0; j < stride; j++) {
      part_vtx_num[i*stride + j]
        = g_vtx_num[section->vertex_num[i*stride + j] - 1];
    }
  }

  cs_part_to_block_copy_array(d,
                              lnum_type,
                              stride,
                              part_vtx_num,
                              block_vtx_num);

  BFT_FREE(part_vtx_num);

  _write_block_connect_g(section->type,
                         bi.gnum_range[0],
                         bi.gnum_range[1],
                         block_vtx_num,
                         comm,
                         med_mesh);

  BFT_FREE(block_vtx_num);

  cs_part_to_block_destroy(&d);
}

/*----------------------------------------------------------------------------
 * Write field values associated with nodal values of a nodal mesh to
 * a MEDCoupling object in serial mode.
 *
 * Output fields ar either scalar or 3d vectors or scalars, and are
 * non interlaced. Input arrays may be less than 2d, in which case the z
 * values are set to 0, and may be interlaced or not.
 *
 * parameters:
 *   mesh             <-- pointer to nodal mesh structure
 *   dim              <-- field dimension
 *   interlace        <-- indicates if field in memory is interlaced
 *   n_parent_lists   <-- indicates if field values are to be obtained
 *                        directly through the local entity index (when 0) or
 *                        through the parent entity numbers (when 1 or more)
 *   parent_num_shift <-- parent list to common number index shifts;
 *                        size: n_parent_lists
 *   datatype         <-- input data type (output is real)
 *   field_values     <-- array of associated field value arrays
 *   comm             <-- associated MPI communicator
 *   f                <-- associated MEDCouplingFieldDouble object
 *                        (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_field_values_ng(const fvm_nodal_t       *mesh,
                        int                      dim,
                        cs_interlace_t           interlace,
                        int                      n_parent_lists,
                        const cs_lnum_t          parent_num_shift[],
                        cs_datatype_t            datatype,
                        const void              *const field_values[],
                        MEDCouplingFieldDouble  *f,
                        MPI_Comm                 comm)
{
  int  rank, n_ranks;

  cs_lnum_t  part_size = 0;
  double  *part_values = NULL, *block_values = NULL;

  const cs_lnum_t  n_vertices
    = fvm_io_num_get_local_count(mesh->global_vertex_num);
  const cs_gnum_t  n_g_vertices
    = fvm_io_num_get_global_count(mesh->global_vertex_num);
  const cs_gnum_t  *g_num
    = fvm_io_num_get_global_num(mesh->global_vertex_num);

  /* Initialize distribution info */

  MPI_Comm_rank(comm, &rank);
  MPI_Comm_size(comm, &n_ranks);

  cs_block_dist_info_t bi = cs_block_dist_compute_sizes(rank,
                                                        n_ranks,
                                                        0,
                                                        n_g_vertices,
                                                        n_g_vertices);

  cs_part_to_block_t *d = cs_part_to_block_create_by_gnum(comm,
                                                          bi,
                                                          n_vertices,
                                                          g_num);

  part_size = cs_part_to_block_get_n_part_ents(d);

  BFT_MALLOC(part_values, part_size*dim, double);

  if (f != NULL)
    block_values = f->getArray()->getPointer();

  /* Distribute partition to block values */

  fvm_convert_array(dim,
                    0,
                    dim, /* stride */
                    0, /* start_id */
                    mesh->n_vertices, /* end_id */
                    interlace,
                    datatype,
                    CS_DOUBLE,
                    n_parent_lists,
                    parent_num_shift,
                    mesh->parent_vertex_num,
                    field_values,
                    part_values);

  cs_part_to_block_copy_array(d,
                              CS_DOUBLE,
                              dim,
                              part_values,
                              block_values);

  assert(   f == NULL
         || (bi.gnum_range[0] == 1 && bi.gnum_range[1] == n_g_vertices));

  BFT_FREE(part_values);

  cs_part_to_block_destroy(&d);
}

#endif /* defined(HAVE_MPI) */

/*----------------------------------------------------------------------------
 * Write field values associated with nodal values of a nodal mesh to
 * a MEDCoupling object in serial mode.
 *
 * Output fields ar either scalar or 3d vectors or scalars, and are
 * non interlaced. Input arrays may be less than 2d, in which case the z
 * values are set to 0, and may be interlaced or not.
 *
 * parameters:
 *   mesh             <-- pointer to nodal mesh structure
 *   dim              <-- field dimension
 *   interlace        <-- indicates if field in memory is interlaced
 *   n_parent_lists   <-- indicates if field values are to be obtained
 *                        directly through the local entity index (when 0) or
 *                        through the parent entity numbers (when 1 or more)
 *   parent_num_shift <-- parent list to common number index shifts;
 *                        size: n_parent_lists
 *   datatype         <-- input data type (output is real)
 *   field_values     <-- array of associated field value arrays
 *   f                <-- associated MEDCouplingFieldDouble object
 *----------------------------------------------------------------------------*/

static void
_export_field_values_nl(const fvm_nodal_t           *mesh,
                        int                          dim,
                        cs_interlace_t               interlace,
                        int                          n_parent_lists,
                        const cs_lnum_t              parent_num_shift[],
                        cs_datatype_t                datatype,
                        const void            *const field_values[],
                        MEDCouplingFieldDouble      *f)
{
  assert(f != NULL);

  double  *values = f->getArray()->getPointer();

  fvm_convert_array(dim,
                    0,
                    dim, /* stride */
                    0, /* start_id */
                    mesh->n_vertices, /* end_id */
                    interlace,
                    datatype,
                    CS_DOUBLE,
                    n_parent_lists,
                    parent_num_shift,
                    mesh->parent_vertex_num,
                    field_values,
                    values);
}

#if defined(HAVE_MPI)

/*----------------------------------------------------------------------------
 * Write field values associated with element values of a nodal mesh to
 * a MEDCoupling object.
 *
 * Output fields are non interlaced. Input arrays may be interlaced or not.
 *
 * parameters:
 *   mesh             <-- pointer to nodal mesh structure
 *   dim              <-- field dimension
 *   interlace        <-- indicates if field in memory is interlaced
 *   n_parent_lists   <-- indicates if field values are to be obtained
 *                        directly through the local entity index (when 0) or
 *                        through the parent entity numbers (when 1 or more)
 *   parent_num_shift <-- parent list to common number index shifts;
 *                        size: n_parent_lists
 *   datatype         <-- indicates the data type of (source) field values
 *   field_values     <-- array of associated field value arrays
 *   comm             <-- associated MPI communicator
 *   f                <-- associated MEDCouplingFieldDouble object
 *                        (NULL on ranks > 0)
 *----------------------------------------------------------------------------*/

static void
_export_field_values_eg(const fvm_nodal_t               *mesh,
                        int                              dim,
                        cs_interlace_t                   interlace,
                        int                              n_parent_lists,
                        const cs_lnum_t                  parent_num_shift[],
                        cs_datatype_t                    datatype,
                        const void                *const field_values[],
                        MPI_Comm                         comm,
                        MEDCouplingFieldDouble          *f)
{
  int section_id;
  int  rank, n_ranks;

  cs_block_dist_info_t  bi;
  cs_part_to_block_t  *d = NULL;

  int         n_sections = 0;
  cs_lnum_t   part_size = 0;
  cs_gnum_t   n_g_elements = 0;

  double  *part_values = NULL, *block_values = NULL;

  cs_gnum_t        *_g_elt_num = NULL;
  const cs_gnum_t  *g_elt_num = NULL;

  const int  elt_dim = fvm_nodal_get_max_entity_dim(mesh);

  MPI_Comm_rank(comm, &rank);
  MPI_Comm_size(comm, &n_ranks);

  /* Loop on sections to count output size */

  for (section_id = 0; section_id < mesh->n_sections; section_id++) {

    const fvm_nodal_section_t  *section = mesh->sections[section_id];

    if (section->entity_dim < elt_dim)
      continue;

    n_sections += 1;
    n_g_elements += fvm_io_num_get_global_count(section->global_element_num);
    part_size += fvm_io_num_get_local_count(section->global_element_num);

  }

  /* Build global numbering if necessary */

  if (n_sections > 1) {

    cs_lnum_t start_id = 0;

    BFT_MALLOC(_g_elt_num, part_size, cs_gnum_t);
    g_elt_num = _g_elt_num;

    /* loop on sections which should be appended */

    for (section_id = 0; section_id < mesh->n_sections; section_id++) {

      const fvm_nodal_section_t  *section = mesh->sections[section_id];

      const cs_lnum_t section_size
        = fvm_io_num_get_local_count(section->global_element_num);

      if (section->entity_dim < elt_dim)
        continue;

      memcpy(_g_elt_num + start_id,
             fvm_io_num_get_global_num(section->global_element_num),
             sizeof(cs_gnum_t)*section_size);
      start_id += section_size;

    };
  }

  else if (n_sections == 1)
    g_elt_num
      = fvm_io_num_get_global_num(mesh->sections[0]->global_element_num);


  /* Build distribution structures */

  bi = cs_block_dist_compute_sizes(rank,
                                   n_ranks,
                                   0,
                                   n_g_elements,
                                   n_g_elements);

  d = cs_part_to_block_create_by_gnum(comm, bi, part_size, g_elt_num);

  if (_g_elt_num != NULL)
    cs_part_to_block_transfer_gnum(d, _g_elt_num);

  g_elt_num = NULL;
  _g_elt_num = NULL;

  BFT_MALLOC(part_values, part_size, double);

  if (f != NULL)
    block_values = f->getArray()->getPointer();

  /* Distribute partition to block values */

  cs_lnum_t start_id = 0;
  cs_lnum_t src_shift = 0;

  /* loop on sections which should be appended */

  for (section_id = 0; section_id < mesh->n_sections; section_id++) {

    const fvm_nodal_section_t  *section = mesh->sections[section_id];

    if (section->entity_dim < elt_dim)
      continue;

    fvm_convert_array(dim,
                      0,
                      dim,
                      src_shift,
                      section->n_elements + src_shift,
                      interlace,
                      datatype,
                      CS_DOUBLE,
                      n_parent_lists,
                      parent_num_shift,
                      section->parent_element_num,
                      field_values,
                      part_values + start_id);

    start_id += fvm_io_num_get_local_count(section->global_element_num)*dim;
    if (n_parent_lists == 0)
      src_shift += fvm_io_num_get_local_count(section->global_element_num);

  }

  /* Distribute part values */

  cs_part_to_block_copy_array(d,
                              CS_DOUBLE,
                              1,
                              part_values,
                              block_values);

  BFT_FREE(part_values);

  cs_part_to_block_destroy(&d);
}

#endif /* defined(HAVE_MPI) */

/*----------------------------------------------------------------------------
 * Write field values associated with element values of a nodal mesh to
 * a MEDCoupling object.
 *
 * Output fields are non interlaced. Input arrays may be interlaced or not.
 *
 * parameters:
 *   mesh             <-- pointer to nodal mesh structure
 *   dim              <-- field dimension
 *   interlace        <-- indicates if field in memory is interlaced
 *   n_parent_lists   <-- indicates if field values are to be obtained
 *                        directly through the local entity index (when 0) or
 *                        through the parent entity numbers (when 1 or more)
 *   parent_num_shift <-- parent list to common number index shifts;
 *                        size: n_parent_lists
 *   datatype         <-- indicates the data type of (source) field values
 *   field_values     <-- array of associated field value arrays
 *   f                <-- associated object handle
 *----------------------------------------------------------------------------*/

static void
_export_field_values_el(const fvm_nodal_t               *mesh,
                        int                              dim,
                        cs_interlace_t                   interlace,
                        int                              n_parent_lists,
                        const cs_lnum_t                  parent_num_shift[],
                        cs_datatype_t                    datatype,
                        const void                *const field_values[],
                        MEDCouplingFieldDouble          *f)
{
  int  section_id;

  double  *values = NULL;

  if (f != NULL)
    values = f->getArray()->getPointer();

  /* Distribute partition to block values */

  cs_lnum_t start_id = 0;
  cs_lnum_t src_shift = 0;

  /* loop on sections which should be appended */

  const int  elt_dim = fvm_nodal_get_max_entity_dim(mesh);

  for (section_id = 0; section_id < mesh->n_sections; section_id++) {

    const fvm_nodal_section_t  *section = mesh->sections[section_id];

    if (section->entity_dim < elt_dim)
      continue;

    fvm_convert_array(dim,
                      0,
                      dim,
                      src_shift,
                      section->n_elements + src_shift,
                      interlace,
                      datatype,
                      CS_DOUBLE,
                      n_parent_lists,
                      parent_num_shift,
                      section->parent_element_num,
                      field_values,
                      values + start_id);

    start_id += section->n_elements*dim;
    if (n_parent_lists == 0)
      src_shift += section->n_elements;

  }
}

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

extern "C" {
#if 0
} /* Fake brace to force Emacs auto-indentation back to column 0 */
#endif

/*----------------------------------------------------------------------------
 * Initialize FVM to MEDCoupling object writer.
 *
 * No options are available for this format.
 *
 * parameters:
 *   name           <-- base output case name.
 *   options        <-- whitespace separated, lowercase options list
 *   time_dependecy <-- indicates if and how meshes will change with time
 *   comm           <-- associated MPI communicator.
 *
 * returns:
 *   pointer to opaque MEDCoupling writer structure.
 *----------------------------------------------------------------------------*/

#if defined(HAVE_MPI)
void *
fvm_to_medcoupling_init_writer(const char             *name,
                               const char             *path,
                               const char             *options,
                               fvm_writer_time_dep_t   time_dependency,
                               MPI_Comm                comm)
#else
void *
fvm_to_medcoupling_init_writer(const char             *name,
                               const char             *path,
                               const char             *options,
                               fvm_writer_time_dep_t   time_dependency)
#endif
{
  fvm_to_medcoupling_t  *writer = NULL;

  /* Initialize writer */

  BFT_MALLOC(writer, 1, fvm_to_medcoupling_t);

  writer->rank = 0;
  writer->n_ranks = 1;

  writer->n_med_meshes = 0;
  writer->n_fields  = 0;
  writer->med_meshes   = NULL;
  writer->fields = NULL;

  writer->n_time_steps   = 0;
  writer->time_steps     = NULL;
  writer->time_values    = NULL;
  writer->time_dependency = time_dependency;

  /* Writer name */

  if (name != NULL) {
    BFT_MALLOC(writer->name, strlen(name) + 1, char);
    strcpy(writer->name, name);
  }
  else {
    const char _name[] = "MEDCoupling writer";
    BFT_MALLOC(writer->name, strlen(_name) + 1, char);
    strcpy(writer->name, _name);
  }

  /* Parallel parameters */

#if defined(HAVE_MPI)
  {
    int mpi_flag;
    MPI_Initialized(&mpi_flag);

    if (mpi_flag && comm != MPI_COMM_NULL) {
      writer->comm = comm;
      MPI_Comm_rank(writer->comm, &(writer->rank));
      MPI_Comm_size(writer->comm, &(writer->n_ranks));
    }
    else
      writer->comm = MPI_COMM_NULL;
  }
#endif /* defined(HAVE_MPI) */

  return writer;
}

/*----------------------------------------------------------------------------
 * Finalize FVM to MEDCoupling object writer.
 *
 * parameters:
 *   this_writer_p <-- pointer to opaque writer structure.
 *
 * returns:
 *   NULL pointer
 *----------------------------------------------------------------------------*/

void *
fvm_to_medcoupling_finalize_writer(void  *this_writer_p)
{
  int i;

  fvm_to_medcoupling_t  *writer = (fvm_to_medcoupling_t *)this_writer_p;

  assert(writer != NULL);

  /* Free structures */

  BFT_FREE(writer->name);
  BFT_FREE(writer->time_values);
  BFT_FREE(writer->time_steps);

  /* Free MEDCouplingUMesh and field structures
     (reference counters should go to 0) */

  for (i = 0; i < writer->n_med_meshes; i++)
    writer->med_meshes[i] = NULL; // delete writer->med_meshes[i];
  BFT_FREE(writer->med_meshes);

  for (i = 0; i < writer->n_fields; i++) {
    writer->fields[i]->f = NULL; // delete writer->fields[i]->f;
    BFT_FREE(writer->fields[i]);
  }

  BFT_FREE(writer->fields);

  /* Free fvm_to_medcoupling_t structure */

  BFT_FREE(writer);

  return NULL;
}

/*----------------------------------------------------------------------------
 * Associate new time step with an MEDCoupling geometry.
 *
 * parameters:
 *   this_writer_p <-- pointer to associated writer
 *   time_step     <-- time step number
 *   time_value    <-- time_value number
 *----------------------------------------------------------------------------*/

void
fvm_to_medcoupling_set_mesh_time(void          *this_writer_p,
                                 const int      time_step,
                                 const double   time_value)
{
  int i;

  fvm_to_medcoupling_t  *w = (fvm_to_medcoupling_t *)this_writer_p;

  for (i = 0; i < w->n_med_meshes; i++) {
    MEDCouplingUMesh  *m = w->med_meshes[i];
    m->updateTime();
  }
}

/*----------------------------------------------------------------------------
 * Write nodal mesh to a MEDCoupling object
 *
 * parameters:
 *   this_writer_p <-- pointer to associated writer
 *   mesh          <-- pointer to nodal mesh structure that should be written
 *----------------------------------------------------------------------------*/

void
fvm_to_medcoupling_export_nodal(void               *this_writer_p,
                                const fvm_nodal_t  *mesh)
{
  int  mesh_id, section_id;

  cs_lnum_t  n_g_elts = 0;
  fvm_to_medcoupling_t  *this_writer = (fvm_to_medcoupling_t *)this_writer_p;

  const int  n_ranks = this_writer->n_ranks;
  const int  elt_dim = fvm_nodal_get_max_entity_dim(mesh);

  /* Initialization */
  /*----------------*/

  /* Get matching mesh */

  mesh_id = _get_medcoupling_mesh_id(this_writer,
                                     mesh->name);

  if (mesh_id < 0)
    mesh_id = _add_medcoupling_mesh(this_writer,
                                    mesh);

  MEDCouplingUMesh  *med_mesh = this_writer->med_meshes[mesh_id];

  /* Vertex coordinates */
  /*--------------------*/

#if defined(HAVE_MPI)
  if (n_ranks > 1)
    _export_vertex_coords_g(this_writer, mesh, med_mesh);
#endif

  if (n_ranks == 1)
    _export_vertex_coords_l(mesh, med_mesh);

  /* Element connectivity size */
  /*---------------------------*/

  for (section_id = 0; section_id < mesh->n_sections; section_id++) {

    const fvm_nodal_section_t  *section = mesh->sections[section_id];

    if (section->entity_dim < elt_dim)
      continue;

    if (section->global_element_num != NULL)
      n_g_elts += fvm_io_num_get_global_count(section->global_element_num);
    else
      n_g_elts += section->n_elements;

  } /* End of loop on sections */

  if (med_mesh != NULL)
    med_mesh->allocateCells(n_g_elts);

  /* Element connectivity */
  /*----------------------*/

  for (section_id = 0; section_id < mesh->n_sections; section_id++) {

    const fvm_nodal_section_t  *section = mesh->sections[section_id];

    if (section->entity_dim < elt_dim)
      continue;

    /* Output for strided (regular) element types */
    /*--------------------------------------------*/

    if (section->stride > 0) {

#if defined(HAVE_MPI)

      if (n_ranks > 1)
        _export_nodal_strided_g(section,
                                mesh->global_vertex_num,
                                this_writer->comm,
                                med_mesh);

#endif /* defined(HAVE_MPI) */

      if (n_ranks == 1)
        _write_connect_block(section->type,
                             section->n_elements,
                             section->vertex_num,
                             med_mesh);

    } /* end of output for strided element types */

    /* Output for polygons */
    /*---------------------*/

    else if (section->type == FVM_FACE_POLY) {
#if defined(HAVE_MPI)

      /* output in parallel mode */

      if (n_ranks > 1)
        _export_nodal_polygons_g(section,
                                 mesh->global_vertex_num,
                                 this_writer->comm,
                                 med_mesh);
#endif /* defined(HAVE_MPI) */

      if (n_ranks == 1)
        _export_nodal_polygons_l(section, med_mesh);

    }

    /* Output for polyhedra */
    /*----------------------*/

    else if (section->type == FVM_CELL_POLY) {

#if defined(HAVE_MPI)

      /* output in parallel mode */

      if (n_ranks > 1)
        _export_nodal_polyhedra_g(section,
                                  mesh->global_vertex_num,
                                  this_writer->comm,
                                  med_mesh);

#endif /* defined(HAVE_MPI) */

      if (n_ranks == 1)
        _export_nodal_polyhedra_l(section, med_mesh);

    }

  } /* End of loop on sections */

  /* Update mesh object */
  /*--------------------*/

  med_mesh->finishInsertingCells();
}

/*----------------------------------------------------------------------------
 * Write field associated with a nodal mesh to a MEDCoupling object.
 *
 * Assigning a negative value to the time step indicates a time-independent
 * field (in which case the time_value argument is unused).
 *
 * parameters:
 *   this_writer_p    <-- pointer to associated writer
 *   mesh             <-- pointer to associated nodal mesh structure
 *   name             <-- variable name
 *   location         <-- variable definition location (nodes or elements)
 *   dimension        <-- variable dimension (0: constant, 1: scalar,
 *                        3: vector, 6: sym. tensor, 9: asym. tensor)
 *   interlace        <-- indicates if variable in memory is interlaced
 *   n_parent_lists   <-- indicates if variable values are to be obtained
 *                        directly through the local entity index (when 0) or
 *                        through the parent entity numbers (when 1 or more)
 *   parent_num_shift <-- parent number to value array index shifts;
 *                        size: n_parent_lists
 *   datatype         <-- indicates the data type of (source) field values
 *   time_step        <-- number of the current time step
 *   time_value       <-- associated time value
 *   field_values     <-- array of associated field value arrays
 *----------------------------------------------------------------------------*/

void
fvm_to_medcoupling_export_field(void                  *this_writer_p,
                                const fvm_nodal_t     *mesh,
                                const char            *name,
                                fvm_writer_var_loc_t   location,
                                int                    dimension,
                                cs_interlace_t         interlace,
                                int                    n_parent_lists,
                                const cs_lnum_t        parent_num_shift[],
                                cs_datatype_t          datatype,
                                int                    time_step,
                                double                 time_value,
                                const void      *const field_values[])
{
  int  output_dim, mesh_id, field_id;

  fvm_to_medcoupling_t *this_writer = (fvm_to_medcoupling_t *)this_writer_p;

  TypeOfTimeDiscretization  td = (time_step < 0) ? NO_TIME : ONE_TIME;

  const int  rank = this_writer->rank;
  const int  n_ranks = this_writer->n_ranks;

  /* Initialization */
  /*----------------*/

  mesh_id = _get_medcoupling_mesh_id(this_writer, mesh->name);

  if (mesh_id < 0) {
    mesh_id = _add_medcoupling_mesh(this_writer, mesh);
    fvm_to_medcoupling_export_nodal(this_writer, mesh);
  }

  /* Get field id */

  field_id = _get_medcoupling_field_id(this_writer,
                                       name,
                                       mesh_id,
                                       dimension,
                                       location,
                                       td);

  if (field_id < 0)
    field_id = _add_medcoupling_field(this_writer,
                                      name,
                                      mesh_id,
                                      dimension,
                                      location,
                                      td);

  MEDCouplingFieldDouble  *f = this_writer->fields[field_id]->f;

  /* Per node variable */
  /*-------------------*/

  if (location == FVM_WRITER_PER_NODE) {

#if defined(HAVE_MPI)

    if (n_ranks > 1)
      _export_field_values_ng(mesh,
                              dimension,
                              interlace,
                              n_parent_lists,
                              parent_num_shift,
                              datatype,
                              field_values,
                              f,
                              this_writer->comm);

#endif /* defined(HAVE_MPI) */

    if (n_ranks == 1)
      _export_field_values_nl(mesh,
                              dimension,
                              interlace,
                              n_parent_lists,
                              parent_num_shift,
                              datatype,
                              field_values,
                              f);
  }

  /* Per element variable */
  /*----------------------*/

  else if (location == FVM_WRITER_PER_ELEMENT) {

#if defined(HAVE_MPI)

    if (n_ranks > 1)
      _export_field_values_eg(mesh,
                              dimension,
                              interlace,
                              n_parent_lists,
                              parent_num_shift,
                              datatype,
                              field_values,
                              this_writer->comm,
                              f);

#endif /* defined(HAVE_MPI) */

    if (n_ranks == 1)
      _export_field_values_el(mesh,
                              dimension,
                              interlace,
                              n_parent_lists,
                              parent_num_shift,
                              datatype,
                              field_values,
                              f);

  } /* End for per element variable */

  /* Update field status */
  /*---------------------*/

  if (td != NO_TIME)
    f->setTime(time_value, time_step, -1);
  f->getArray()->declareAsNew();
  f->getArray()->decrRef();
}

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

} /* extern C */

#endif /* HAVE_MEDCOUPLING */
