/*============================================================================
 *
 *     This file is part of the Code_Saturne Kernel, element of the
 *     Code_Saturne CFD tool.
 *
 *     Copyright (C) 1998-2009 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
 *
 *============================================================================*/

/*============================================================================
 * Extract nodal connectivity mesh representations from a native mesh.
 *============================================================================*/

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

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

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

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

#include <bft_error.h>
#include <bft_mem.h>

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

#include "cs_base.h"
#include "cs_mesh.h"

#include <fvm_defs.h>
#include <fvm_nodal.h>
#include <fvm_nodal_from_desc.h>
#include <fvm_nodal_order.h>

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

#include "cs_mesh_connect.h"

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

BEGIN_C_DECLS

/*=============================================================================
 * Local Macro Definitions
 *============================================================================*/

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

/*============================================================================
 *  Global variables
 *============================================================================*/

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

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

/*----------------------------------------------------------------------------
 * Extract a mesh's "cells -> faces" connectivity.
 *
 * We consider a common numbering for internal and boundary faces, in which
 * boundary faces are defined first. The common id for the i-th boundary
 * face is thus i, and that of the j-th interior face is n_b_faces + j.
 *
 * If ind_cel_extr != NULL, then:
 * --- ind_cel_extr[cell_id] = id in the list to extract (0 to n-1)
 *     if cell cell_id should be extracted
 * --- ind_cel_extr[cell_id] = -1 if cells cell_id should be ignored
 *
 * parameters:
 *   mesh             <-- pointer to mesh structure
 *   extr_cell_size   <-- size of extr_cell_id[] array
 *   extr_cell_id     <-- extr_cell_id = ids of extracted cells, or -1
 *   p_cell_faces_idx --> cells -> faces index
 *   p_cell_faces_val --> cells -> faces connectivity
 *----------------------------------------------------------------------------*/

void
cs_mesh_connect_get_cell_faces(const cs_mesh_t             *mesh,
                               fvm_lnum_t                   extr_cell_size,
                               const fvm_lnum_t             extr_cell_id[],
                               fvm_lnum_t          * *const p_cell_faces_idx,
                               fvm_lnum_t          * *const p_cell_faces_val)
{
  fvm_lnum_t  cell_id, c_id1, c_id2, face_id, n_loc_cells;

  fvm_lnum_t  *cell_face_count = NULL;
  fvm_lnum_t  *cell_faces_idx = NULL;
  fvm_lnum_t  *cell_faces_val = NULL;

  /* Allocate and initialize cell ->faces index */

  n_loc_cells = mesh->n_cells;
  if (extr_cell_id != NULL)
    n_loc_cells = extr_cell_size;

  BFT_MALLOC(cell_faces_idx, n_loc_cells + 1, cs_int_t);

  for (cell_id = 0; cell_id < n_loc_cells + 1; cell_id++)
    cell_faces_idx[cell_id] = 0;

  /* Count number of faces per cell (we assign the temporary counter
     corresponding to cell_id to cell_faces_idx[cell_id + 1] rather than
     cell_faces_idx[cell_id] to simplify the next step) */

  /* Remark: test if cell_id < mesh->n_cells on internal faces so
     as to ignore ghost cells */

  for (face_id = 0; face_id < mesh->n_b_faces; face_id++) {
    cell_id = mesh->b_face_cells[face_id] - 1;
    if (extr_cell_id != NULL)
      cell_id = extr_cell_id[cell_id];
    if (cell_id > -1)
      cell_faces_idx[cell_id + 1] += 1;
  }

  for (face_id = 0; face_id < mesh->n_i_faces; face_id++) {
    c_id1 = mesh->i_face_cells[face_id*2    ] - 1;
    c_id2 = mesh->i_face_cells[face_id*2 + 1] - 1;
    if (extr_cell_id != NULL) {
      if (c_id1 < mesh->n_cells)
        c_id1 = extr_cell_id[c_id1];
      else
        c_id1 = -1;
      if (c_id2 < mesh->n_cells)
        c_id2 = extr_cell_id[c_id2];
      else
        c_id2 = -1;
    }
    if (c_id1 > -1 && c_id1 < mesh->n_cells)
      cell_faces_idx[c_id1 + 1] += 1;
    if (c_id2 > -1 && c_id2 < mesh->n_cells)
      cell_faces_idx[c_id2 + 1] += 1;
  }

  /* Build cell -> faces index */

  cell_faces_idx[0] = 1;
  for (cell_id = 0; cell_id < n_loc_cells; cell_id++)
    cell_faces_idx[cell_id + 1] += cell_faces_idx[cell_id];

  /* Build array of values */

  BFT_MALLOC(cell_faces_val, cell_faces_idx[n_loc_cells] - 1, cs_int_t);
  BFT_MALLOC(cell_face_count, n_loc_cells, cs_int_t);

  for (cell_id = 0; cell_id < n_loc_cells; cell_id++)
    cell_face_count[cell_id] = 0;

  for (face_id = 0; face_id < mesh->n_b_faces; face_id++) {
    cell_id = mesh->b_face_cells[face_id] - 1;
    if (extr_cell_id != NULL)
      cell_id = extr_cell_id[cell_id];
    if (cell_id > -1) {
      cell_faces_val[cell_faces_idx[cell_id] + cell_face_count[cell_id] - 1]
        = face_id + 1;
      cell_face_count[cell_id] += 1;
    }
  }

  for (face_id = 0; face_id < mesh->n_i_faces; face_id++) {
    c_id1 = mesh->i_face_cells[face_id*2    ] - 1;
    c_id2 = mesh->i_face_cells[face_id*2 + 1] - 1;
    if (extr_cell_id != NULL) {
      if (c_id1 < mesh->n_cells)
        c_id1 = extr_cell_id[c_id1];
      else
        c_id1 = -1;
      if (c_id2 < mesh->n_cells)
        c_id2 = extr_cell_id[c_id2];
      else
        c_id2 = -1;
    }
    if (c_id1 > -1 && c_id1 < mesh->n_cells) {
      cell_faces_val[cell_faces_idx[c_id1] + cell_face_count[c_id1] - 1]
        =   face_id + mesh->n_b_faces + 1;
      cell_face_count[c_id1] += 1;
    }
    if (c_id2 > -1 && c_id2 < mesh->n_cells) {
      cell_faces_val[cell_faces_idx[c_id2] + cell_face_count[c_id2] - 1]
        = -(face_id + mesh->n_b_faces + 1);
      cell_face_count[c_id2] += 1;
    }
  }

  BFT_FREE(cell_face_count);

  /* Return values */

  *p_cell_faces_idx = cell_faces_idx;
  *p_cell_faces_val = cell_faces_val;

#if 0 && defined(DEBUG) && !defined(NDEBUG)
 {
   cs_int_t ipos, ival;
   /* Print arrays */
   bft_printf("dbg : cs_mesh_ret_cel_fac\n"
              "nombre de cellules extraites = %d\n", extr_cell_size);
   for (ipos = 0; ipos < extr_cell_size; ipos++) {
     bft_printf("  cellule %d\n", ipos);
     bft_printf("    cell_faces_idx[%d] = %d\n", ipos, cell_faces_idx[ipos]);
     for (ival = cell_faces_idx[ipos]     - 1;
          ival < cell_faces_idx[ipos + 1] - 1;
          ival++)
       bft_printf("      cell_faces_val[%d] = %d\n",
                  ival, cell_faces_val[ival]);
   }
   bft_printf("  cell_faces_idx[%d] = %d\n", ipos, cell_faces_idx[ipos]);
 }
#endif

}

/*----------------------------------------------------------------------------
 * Build a nodal connectivity structure from a subset of a mesh's cells.
 *
 * The list of cells to extract is optional (if none is given, all cells
 * faces are extracted by default); it does not need to be ordered on input,
 * but is always ordered on exit (as cells are extracted by increasing number
 * traversal, the list is reordered to ensure the coherency of the extracted
 * mesh's link to its parent cells, built using this list).
 *
 * parameters:
 *   mesh           <-- base mesh
 *   name           <-- extracted mesh name
 *   cell_list_size <-- size of cell_list[] array
 *   cell_list      <-> list of cells (1 to n), or NULL
 *
 * returns:
 *   pointer to extracted nodal mesh
 *----------------------------------------------------------------------------*/

fvm_nodal_t  *
cs_mesh_connect_cells_to_nodal(const cs_mesh_t  *mesh,
                               const char       *name,
                               fvm_lnum_t        cell_list_size,
                               fvm_lnum_t        cell_list[])
{
  cs_int_t   cell_id;

  cs_int_t   extr_cell_count = 0;
  cs_int_t  *extr_cell_idx = NULL;

  cs_int_t  *cell_face_idx = NULL;
  cs_int_t  *cell_face_num = NULL;

  fvm_lnum_t  face_num_shift[3];
  fvm_lnum_t  *face_vertices_idx[2];
  fvm_lnum_t  *face_vertices_num[2];
  fvm_lnum_t  *polyhedra_faces = NULL;

  fvm_nodal_t  *extr_mesh;

  /* Check that the mesh contains face -> vertices connectivity */

  if (mesh->b_face_vtx_idx == NULL || mesh->i_face_vtx_idx == NULL)
    bft_error(__FILE__, __LINE__, 0,
              _("The main mesh does not contain any face -> vertices\n"
                "connectivity, necessary for the nodal connectivity\n"
                "reconstruction (cs_mesh_connect_cells_to_nodal)."));

  /* Count the number of cells to convert */

  if (cell_list != NULL) {

    BFT_MALLOC(extr_cell_idx, mesh->n_cells, cs_int_t);

    /* Initialize index as marker */

    for (cell_id = 0; cell_id < mesh->n_cells; cell_id++)
      extr_cell_idx[cell_id] = -1;
    for (cell_id = 0; cell_id < cell_list_size; cell_id++) {
      if (cell_list[cell_id] <= mesh->n_cells)
        extr_cell_idx[cell_list[cell_id] - 1] = 1;
    }

    /* Convert marked ids to indexes (1 to n) and reconstruct values of
       cell_list[] to ensure that it is ordered. */

    extr_cell_count = 0;
    for (cell_id = 0; cell_id < mesh->n_cells; cell_id++) {
      if (extr_cell_idx[cell_id] == 1) {
        cell_list[extr_cell_count] = cell_id + 1;
        extr_cell_idx[cell_id] = extr_cell_count++;
      }
    }

    assert(extr_cell_count <= cell_list_size);

  }
  else {
    extr_cell_count = CS_MIN(mesh->n_cells, cell_list_size);
    extr_cell_idx = NULL;
  }

  /* Extract "cells -> faces" connectivity */

  cs_mesh_connect_get_cell_faces(mesh,
                                 extr_cell_count,
                                 extr_cell_idx,
                                 &cell_face_idx,
                                 &cell_face_num);

  if (extr_cell_idx != NULL)
    BFT_FREE(extr_cell_idx);

  /* Build nodal connectivity */

  face_num_shift[0] = 0;
  face_num_shift[1] = mesh->n_b_faces + face_num_shift[0];
  face_num_shift[2] = mesh->n_i_faces + face_num_shift[1];

  face_vertices_idx[0] = mesh->b_face_vtx_idx;
  face_vertices_idx[1] = mesh->i_face_vtx_idx;
  face_vertices_num[0] = mesh->b_face_vtx_lst;
  face_vertices_num[1] = mesh->i_face_vtx_lst;

  extr_mesh = fvm_nodal_create(name, 3);

  fvm_nodal_from_desc_add_cells(extr_mesh,
                                extr_cell_count,
                                NULL,
                                2,
                                face_num_shift,
                                (const fvm_lnum_t **)face_vertices_idx,
                                (const fvm_lnum_t **)face_vertices_num,
                                cell_face_idx,
                                cell_face_num,
                                cell_list,
                                &polyhedra_faces);

  fvm_nodal_set_shared_vertices(extr_mesh,
                                mesh->vtx_coord);

  /* Free memory */

  BFT_FREE(polyhedra_faces);

  BFT_FREE(cell_face_idx);
  BFT_FREE(cell_face_num);

  /* Sort cells by increasing global number */

  fvm_nodal_order_cells(extr_mesh, mesh->global_cell_num);
  fvm_nodal_init_io_num(extr_mesh, mesh->global_cell_num, 3);

  /* Sort vertices by increasing global number */

  fvm_nodal_order_vertices(extr_mesh, mesh->global_vtx_num);
  fvm_nodal_init_io_num(extr_mesh, mesh->global_vtx_num, 0);

  /* We are done */

  return extr_mesh;
}

/*----------------------------------------------------------------------------
 * Build a nodal connectivity structure from a subset of a mesh's faces.
 *
 * The lists of faces to extract are optional (if none is given, boundary
 * faces are extracted by default); they do not need to be ordered on input,
 * but they are always ordered on exit (as faces are extracted by increasing
 * number traversal, the lists are reordered to ensure the coherency of
 * the extracted mesh's link to its parent faces, built using these lists).
 *
 * parameters:
 *   mesh             <-- base mesh
 *   name             <-- extracted mesh name
 *   i_face_list_size <-- size of i_face_list[] array
 *   b_face_list_size <-- size of b_face_list[] array
 *   i_face_list      <-> list of interior faces (1 to n), or NULL
 *   b_face_list      <-> list of boundary faces (1 to n), or NULL
 *
 * returns:
 *   pointer to extracted nodal mesh
 *----------------------------------------------------------------------------*/

fvm_nodal_t *
cs_mesh_connect_faces_to_nodal(const cs_mesh_t  *mesh,
                               const char       *name,
                               fvm_lnum_t        i_face_list_size,
                               fvm_lnum_t        b_face_list_size,
                               fvm_lnum_t        i_face_list[],
                               fvm_lnum_t        b_face_list[])
{
  fvm_lnum_t   face_id, i;

  fvm_lnum_t   n_max_faces = 0;
  fvm_lnum_t   b_face_count = 0;
  fvm_lnum_t   i_face_count = 0;
  fvm_lnum_t   extr_face_count = 0;
  fvm_lnum_t  *extr_face_idx = NULL;
  fvm_lnum_t  *extr_face_list = NULL;

  fvm_lnum_t   face_num_shift[3];
  fvm_lnum_t  *face_vertices_idx[2];
  fvm_lnum_t  *face_vertices_num[2];

  fvm_gnum_t  *num_glob_fac = NULL;

  fvm_nodal_t  *extr_mesh;

  /* Check that the mesh contains face -> vertices connectivity */

  if (mesh->b_face_vtx_idx == NULL || mesh->i_face_vtx_idx == NULL)
    bft_error(__FILE__, __LINE__, 0,
              _("The main mesh does not contain any face -> vertices\n"
                "connectivity, necessary for the nodal connectivity\n"
                "reconstruction (cs_mesh_connect_faces_to_nodal)."));

  /* Count the number of faces to convert */

  n_max_faces = mesh->n_i_faces + mesh->n_b_faces;
  BFT_MALLOC(extr_face_idx, n_max_faces, cs_int_t);

  /* Initialize index as marker */

  for (face_id = 0; face_id < n_max_faces; face_id++)
    extr_face_idx[face_id] = -1;

  if (b_face_list_size == mesh->n_b_faces) {
    for (face_id = 0; face_id < mesh->n_b_faces; face_id++)
      extr_face_idx[face_id] = 1;
  }
  else if (b_face_list != NULL) {
    for (face_id = 0; face_id < b_face_list_size; face_id++)
      extr_face_idx[b_face_list[face_id] - 1] = 1;
  }

  if (i_face_list_size == mesh->n_i_faces) {
    for (face_id = 0; face_id < mesh->n_i_faces; face_id++)
      extr_face_idx[face_id + mesh->n_b_faces] = 1;
  }
  else if (i_face_list != NULL) {
    for (face_id = 0; face_id < i_face_list_size; face_id++)
      extr_face_idx[i_face_list[face_id] - 1 + mesh->n_b_faces] = 1;
  }

  /* Convert marked ids to indexes (1 to n) and reconstruct values of
     b_face_list[] and i_face_list[] to ensure that they are ordered. */

  b_face_count = 0;
  i_face_count = 0;

  if (b_face_list != NULL) {
    for (face_id = 0; face_id < mesh->n_b_faces; face_id++) {
      if (extr_face_idx[face_id] == 1) {
        b_face_list[b_face_count] = face_id + 1;
        b_face_count++;
      }
    }
  }
  else
    b_face_count = CS_MIN(b_face_list_size, mesh->n_b_faces);

  if (i_face_list != NULL) {
    for (face_id = 0, i = mesh->n_b_faces;
         face_id < mesh->n_i_faces;
         face_id++, i++) {
      if (extr_face_idx[i] == 1) {
        i_face_list[i_face_count] = face_id + 1;
        i_face_count++;
      }
    }
  }
  else
    i_face_count = CS_MIN(i_face_list_size, mesh->n_i_faces);

  BFT_FREE(extr_face_idx);

  /* Build a contiguous list (boundary faces, interior faces) */

  extr_face_count = b_face_count + i_face_count;

  BFT_MALLOC(extr_face_list, extr_face_count, cs_int_t);

  if (b_face_list != NULL) {
    for (face_id = 0; face_id < b_face_count; face_id++)
      extr_face_list[face_id] = b_face_list[face_id];
  }
  else if (b_face_list == NULL) { /* boundary faces by default if no list */
    for (face_id = 0; face_id < b_face_count; face_id++)
      extr_face_list[face_id] = face_id + 1;
  }

  if (i_face_list != NULL) {
    for (face_id = 0, i = b_face_count; face_id < i_face_count; face_id++, i++)
      extr_face_list[i] = i_face_list[face_id] + mesh->n_b_faces;
  }
  else if (i_face_list == NULL) {
    for (face_id = 0, i = b_face_count; face_id < i_face_count; face_id++, i++)
      extr_face_list[i] = face_id + mesh->n_b_faces + 1;
  }

  /* Build the nodal connectivity */

  face_num_shift[0] = 0;
  face_num_shift[1] = mesh->n_b_faces + face_num_shift[0];
  face_num_shift[2] = mesh->n_i_faces + face_num_shift[1];

  face_vertices_idx[0] = mesh->b_face_vtx_idx;
  face_vertices_idx[1] = mesh->i_face_vtx_idx;
  face_vertices_num[0] = mesh->b_face_vtx_lst;
  face_vertices_num[1] = mesh->i_face_vtx_lst;

  extr_mesh = fvm_nodal_create(name, 3);

  fvm_nodal_from_desc_add_faces(extr_mesh,
                                extr_face_count,
                                extr_face_list,
                                2,
                                face_num_shift,
                                (const fvm_lnum_t **)face_vertices_idx,
                                (const fvm_lnum_t **)face_vertices_num,
                                NULL);

  fvm_nodal_set_shared_vertices(extr_mesh,
                                mesh->vtx_coord);

  BFT_FREE(extr_face_list);

  /* In case of parallelism or face renumbering, sort faces by
     increasing global number */

  if (mesh->global_i_face_num != NULL || mesh->global_b_face_num != NULL) {

    BFT_MALLOC(num_glob_fac, n_max_faces, fvm_gnum_t);

    if (mesh->global_b_face_num == NULL) {
      for (face_id = 0; face_id < mesh->n_b_faces; face_id++)
        num_glob_fac[face_id] = face_id + 1;
    }
    else {
      for (face_id = 0; face_id < mesh->n_b_faces; face_id++)
        num_glob_fac[face_id] = mesh->global_b_face_num[face_id];
    }

    assert(mesh->n_g_b_faces + mesh->n_g_i_faces > 0);

    if (mesh->global_i_face_num == NULL) {
      for (face_id = 0, i = mesh->n_b_faces;
           face_id < mesh->n_i_faces;
           face_id++, i++)
        num_glob_fac[i] = face_id + 1 + mesh->n_g_b_faces;
    }
    else {
      for (face_id = 0, i = mesh->n_b_faces;
           face_id < mesh->n_i_faces;
           face_id++, i++)
        num_glob_fac[i] = mesh->global_i_face_num[face_id] + mesh->n_g_b_faces;
    }

  }

  /* Sort faces by increasing global number */

  fvm_nodal_order_faces(extr_mesh, num_glob_fac);
  fvm_nodal_init_io_num(extr_mesh, num_glob_fac, 2);

  if (num_glob_fac != NULL)
    BFT_FREE(num_glob_fac);

  /* Sort vertices by increasing global number */

  fvm_nodal_order_vertices(extr_mesh, mesh->global_vtx_num);
  fvm_nodal_init_io_num(extr_mesh, mesh->global_vtx_num, 0);

  /* We are done */

  return extr_mesh;
}

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

END_C_DECLS
