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

/** \file
 * \ingroup eduv
 *
 * Utilities for manipulating UV islands.
 *
 * \note This is similar to `GEO_uv_parametrizer.hh`,
 * however the data structures there don't support arbitrary topology
 * such as an edge with 3 or more faces using it.
 * This API uses #BMesh data structures and doesn't have limitations for manifold meshes.
 */

#include "BLI_listbase.h"
#include "BLI_math_vector.h"

#include "BKE_editmesh.h"

#include "DNA_image_types.h"

#include "ED_uvedit.hh" /* Own include. */

/* -------------------------------------------------------------------- */
/** \name UDIM packing helper functions
 * \{ */

bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2])
{
  const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
  const bool is_tiled_image = image && (image->source == IMA_SRC_TILED);

  if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) {
    return true;
  }
  /* Check if selection lies on a valid UDIM image tile. */
  if (is_tiled_image) {
    LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) {
      const int tile_index = tile->tile_number - 1001;
      const int target_x = (tile_index % 10);
      const int target_y = (tile_index / 10);
      if (coords_floor[0] == target_x && coords_floor[1] == target_y) {
        return true;
      }
    }
  }
  /* Probably not required since UDIM grid checks for 1001. */
  else if (image && !is_tiled_image) {
    if (is_zero_v2(coords_floor)) {
      return true;
    }
  }

  return false;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Calculate UV Islands
 * \{ */

struct SharedUVLoopData {
  BMUVOffsets offsets;
  bool use_seams;
};

static bool bm_loop_uv_shared_edge_check(const BMLoop *l_a, const BMLoop *l_b, void *user_data)
{
  const SharedUVLoopData *data = static_cast<const SharedUVLoopData *>(user_data);

  if (data->use_seams) {
    if (BM_elem_flag_test(l_a->e, BM_ELEM_SEAM)) {
      return false;
    }
  }

  return BM_loop_uv_share_edge_check((BMLoop *)l_a, (BMLoop *)l_b, data->offsets.uv);
}

/**
 * Returns true if `efa` is able to be affected by a packing operation, given various parameters.
 *
 * Checks if it's (not) hidden, and optionally selected, and/or UV selected.
 *
 * Will eventually be superseded by `BM_uv_element_map_create()`.
 *
 * Loosely based on `uvedit_is_face_affected`, but "bug-compatible" with previous code.
 */
static bool uvedit_is_face_affected_for_calc_uv_islands(const Scene *scene,
                                                        BMFace *efa,
                                                        const bool only_selected_faces,
                                                        const bool only_selected_uvs,
                                                        const BMUVOffsets &uv_offsets)
{
  if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
    return false;
  }
  if (only_selected_faces) {
    if (only_selected_uvs) {
      return BM_elem_flag_test(efa, BM_ELEM_SELECT) &&
             uvedit_face_select_test(scene, efa, uv_offsets);
    }
    return BM_elem_flag_test(efa, BM_ELEM_SELECT);
  }
  return true;
}

int bm_mesh_calc_uv_islands(const Scene *scene,
                            BMesh *bm,
                            ListBase *island_list,
                            const bool only_selected_faces,
                            const bool only_selected_uvs,
                            const bool use_seams,
                            const float aspect_y,
                            const BMUVOffsets uv_offsets)
{
  BLI_assert(uv_offsets.uv >= 0);
  int island_added = 0;
  BM_mesh_elem_table_ensure(bm, BM_FACE);

  int *groups_array = static_cast<int *>(
      MEM_mallocN(sizeof(*groups_array) * size_t(bm->totface), __func__));

  int(*group_index)[2];

  /* Set the tag for `BM_mesh_calc_face_groups`. */
  BMFace *f;
  BMIter iter;
  BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
    const bool face_affected = uvedit_is_face_affected_for_calc_uv_islands(
        scene, f, only_selected_faces, only_selected_uvs, uv_offsets);
    BM_elem_flag_set(f, BM_ELEM_TAG, face_affected);
  }

  SharedUVLoopData user_data = {{0}};
  user_data.offsets = uv_offsets;
  user_data.use_seams = use_seams;

  const int group_len = BM_mesh_calc_face_groups(bm,
                                                 groups_array,
                                                 &group_index,
                                                 nullptr,
                                                 bm_loop_uv_shared_edge_check,
                                                 &user_data,
                                                 BM_ELEM_TAG,
                                                 BM_EDGE);

  for (int i = 0; i < group_len; i++) {
    const int faces_start = group_index[i][0];
    const int faces_len = group_index[i][1];
    BMFace **faces = static_cast<BMFace **>(MEM_mallocN(sizeof(*faces) * faces_len, __func__));

    float bounds_min[2], bounds_max[2];
    INIT_MINMAX2(bounds_min, bounds_max);

    for (int j = 0; j < faces_len; j++) {
      faces[j] = BM_face_at_index(bm, groups_array[faces_start + j]);
    }

    FaceIsland *island = static_cast<FaceIsland *>(MEM_callocN(sizeof(*island), __func__));
    island->faces = faces;
    island->faces_len = faces_len;
    island->offsets = uv_offsets;
    island->aspect_y = aspect_y;
    BLI_addtail(island_list, island);
    island_added += 1;
  }

  MEM_freeN(groups_array);
  MEM_freeN(group_index);
  return island_added;
}

/** \} */
