/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup bmesh
 *
 * Duplicate geometry from one mesh from another.
 */

#include "MEM_guardedalloc.h"

#include "BLI_array.hh"

#include "bmesh.hh"

namespace blender {

static BMVert *bm_vert_copy(BMesh *bm_dst,
                            const std::optional<BMCustomDataCopyMap> &cd_vert_map,
                            BMVert *v_src)
{
  BMVert *v_dst = BM_vert_create(bm_dst, v_src->co, nullptr, BM_CREATE_SKIP_CD);
  if (cd_vert_map.has_value()) {
    BM_elem_attrs_copy(bm_dst, cd_vert_map.value(), v_src, v_dst);
  }
  else {
    BM_elem_attrs_copy(bm_dst, v_src, v_dst);
  }
  return v_dst;
}

static BMEdge *bm_edge_copy_with_arrays(BMesh *bm_dst,
                                        const std::optional<BMCustomDataCopyMap> &cd_edge_map,
                                        BMEdge *e_src,
                                        BMVert **verts_dst)
{
  BMVert *e_dst_v1 = verts_dst[BM_elem_index_get(e_src->v1)];
  BMVert *e_dst_v2 = verts_dst[BM_elem_index_get(e_src->v2)];
  BMEdge *e_dst = BM_edge_create(bm_dst, e_dst_v1, e_dst_v2, nullptr, BM_CREATE_SKIP_CD);
  if (cd_edge_map.has_value()) {
    BM_elem_attrs_copy(bm_dst, cd_edge_map.value(), e_src, e_dst);
  }
  else {
    BM_elem_attrs_copy(bm_dst, e_src, e_dst);
  }
  return e_dst;
}

static BMFace *bm_face_copy_with_arrays(BMesh *bm_dst,
                                        const std::optional<BMCustomDataCopyMap> cd_face_map,
                                        const std::optional<BMCustomDataCopyMap> &cd_loop_map,
                                        BMFace *f_src,
                                        BMVert **verts_dst,
                                        BMEdge **edges_dst)
{
  BMFace *f_dst;
  Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vtar(f_src->len);
  Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edar(f_src->len);
  BMLoop *l_iter_src, *l_iter_dst, *l_first_src;
  int i;

  l_first_src = BM_FACE_FIRST_LOOP(f_src);

  /* Lookup verts & edges. */
  l_iter_src = l_first_src;
  i = 0;
  do {
    vtar[i] = verts_dst[BM_elem_index_get(l_iter_src->v)];
    edar[i] = edges_dst[BM_elem_index_get(l_iter_src->e)];
    i++;
  } while ((l_iter_src = l_iter_src->next) != l_first_src);

  /* Create new face. */
  f_dst = BM_face_create(bm_dst, vtar.data(), edar.data(), f_src->len, nullptr, BM_CREATE_SKIP_CD);

  /* Copy attributes. */
  if (cd_face_map.has_value()) {
    BM_elem_attrs_copy(bm_dst, cd_face_map.value(), f_src, f_dst);
  }
  else {
    BM_elem_attrs_copy(bm_dst, f_src, f_dst);
  }

  /* Copy per-loop custom data. */
  l_iter_src = l_first_src;
  l_iter_dst = BM_FACE_FIRST_LOOP(f_dst);
  do {
    if (cd_loop_map.has_value()) {
      BM_elem_attrs_copy(bm_dst, cd_loop_map.value(), l_iter_src, l_iter_dst);
    }
    else {
      BM_elem_attrs_copy(bm_dst, l_iter_src, l_iter_dst);
    }
  } while ((void)(l_iter_dst = l_iter_dst->next), (l_iter_src = l_iter_src->next) != l_first_src);

  return f_dst;
}

void BM_mesh_copy_arrays(BMesh *bm_src,
                         BMesh *bm_dst,
                         BMVert **verts_src,
                         uint verts_src_len,
                         BMEdge **edges_src,
                         uint edges_src_len,
                         BMFace **faces_src,
                         uint faces_src_len)
{
  const std::optional<BMCustomDataCopyMap> cd_vert_map =
      (bm_src == bm_dst) ? std::nullopt :
                           std::optional<BMCustomDataCopyMap>{
                               CustomData_bmesh_copy_map_calc(bm_src->vdata, bm_dst->vdata)};
  const std::optional<BMCustomDataCopyMap> cd_edge_map =
      (bm_src == bm_dst) ? std::nullopt :
                           std::optional<BMCustomDataCopyMap>{
                               CustomData_bmesh_copy_map_calc(bm_src->edata, bm_dst->edata)};
  const std::optional<BMCustomDataCopyMap> cd_face_map =
      (bm_src == bm_dst) ? std::nullopt :
                           std::optional<BMCustomDataCopyMap>{
                               CustomData_bmesh_copy_map_calc(bm_src->pdata, bm_dst->pdata)};
  const std::optional<BMCustomDataCopyMap> cd_loop_map =
      (bm_src == bm_dst) ? std::nullopt :
                           std::optional<BMCustomDataCopyMap>{
                               CustomData_bmesh_copy_map_calc(bm_src->ldata, bm_dst->ldata)};

  /* Vertices. */
  BMVert **verts_dst = MEM_new_array_uninitialized<BMVert *>(verts_src_len, __func__);
  for (uint i = 0; i < verts_src_len; i++) {
    BMVert *v_src = verts_src[i];
    BM_elem_index_set(v_src, i); /* set_dirty! */

    BMVert *v_dst = bm_vert_copy(bm_dst, cd_vert_map, v_src);
    BM_elem_index_set(v_dst, i); /* set_ok */
    verts_dst[i] = v_dst;
  }
  bm_src->elem_index_dirty |= BM_VERT;
  bm_dst->elem_index_dirty &= ~BM_VERT;

  /* Edges. */
  BMEdge **edges_dst = MEM_new_array_uninitialized<BMEdge *>(edges_src_len, __func__);
  for (uint i = 0; i < edges_src_len; i++) {
    BMEdge *e_src = edges_src[i];
    BM_elem_index_set(e_src, i); /* set_dirty! */

    BMEdge *e_dst = bm_edge_copy_with_arrays(bm_dst, cd_edge_map, e_src, verts_dst);
    BM_elem_index_set(e_dst, i);
    edges_dst[i] = e_dst;
  }
  bm_src->elem_index_dirty |= BM_EDGE;
  bm_dst->elem_index_dirty &= ~BM_EDGE;

  /* Faces. */
  for (uint i = 0; i < faces_src_len; i++) {
    BMFace *f_src = faces_src[i];
    BMFace *f_dst = bm_face_copy_with_arrays(
        bm_dst, cd_face_map, cd_loop_map, f_src, verts_dst, edges_dst);
    BM_elem_index_set(f_dst, i);
  }
  bm_dst->elem_index_dirty &= ~BM_FACE;

  /* Cleanup. */
  MEM_delete(verts_dst);
  MEM_delete(edges_dst);
}

}  // namespace blender
