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

/** \file
 * \ingroup bmesh
 *
 * Connect verts across faces (splits faces) and bridge tool.
 */

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

#include "bmesh.hh"

#include "intern/bmesh_operators_private.hh" /* own include */

/**
 * TODO(@ideasman42): Many connected edge loops can cause an error attempting
 * to create faces with duplicate vertices. While this needs to be investigated,
 * it's simple enough to check for this case, see: #102232.
 */
#define USE_DUPLICATE_FACE_VERT_CHECK

#define EDGE_MARK 4
#define EDGE_OUT 8
#define FACE_OUT 16

/* el_a and el_b _must_ be same size */
static void bm_bridge_splice_loops(BMesh *bm,
                                   LinkData *el_a,
                                   LinkData *el_b,
                                   const float merge_factor)
{
  BMOperator op_weld;
  BMOpSlot *slot_targetmap;

  BMO_op_init(bm, &op_weld, 0, "weld_verts");

  slot_targetmap = BMO_slot_get(op_weld.slots_in, "targetmap");

  do {
    BMVert *v_a = static_cast<BMVert *>(el_a->data), *v_b = static_cast<BMVert *>(el_b->data);
    BM_data_interp_from_verts(bm, v_a, v_b, v_b, merge_factor);
    interp_v3_v3v3(v_b->co, v_a->co, v_b->co, merge_factor);
    BLI_assert(v_a != v_b);
    BMO_slot_map_elem_insert(&op_weld, slot_targetmap, v_a, v_b);
  } while ((void)(el_b = el_b->next), (el_a = el_a->next));

  BMO_op_exec(bm, &op_weld);
  BMO_op_finish(bm, &op_weld);
}

/* get the 2 loops matching 2 verts.
 * first attempt to get the face corners that use the edge defined by v1 & v2,
 * if that fails just get any loop that's on the vert (the first one) */
static void bm_vert_loop_pair(BMesh *bm, BMVert *v1, BMVert *v2, BMLoop **l1, BMLoop **l2)
{
  BMEdge *e = BM_edge_exists(v1, v2);
  BMLoop *l = e->l;

  if (l) {
    if (l->v == v1) {
      *l1 = l;
      *l2 = l->next;
    }
    else {
      *l2 = l;
      *l1 = l->next;
    }
  }
  else {
    /* fallback to _any_ loop */
    *l1 = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v1, 0));
    *l2 = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v2, 0));
  }
}

/* el_b can have any offset */
static float bm_edgeloop_offset_length(LinkData *el_a,
                                       LinkData *el_b,
                                       LinkData *el_b_first,
                                       const float len_max)
{
  float len = 0.0f;
  BLI_assert(el_a->prev == nullptr); /* must be first */
  do {
    len += len_v3v3(((BMVert *)el_a->data)->co, ((BMVert *)el_b->data)->co);
  } while ((void)(el_b = el_b->next ? el_b->next : el_b_first),
           (el_a = el_a->next) && (len < len_max));
  return len;
}

static void bm_bridge_best_rotation(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b)
{
  ListBase *lb_a = BM_edgeloop_verts_get(el_store_a);
  ListBase *lb_b = BM_edgeloop_verts_get(el_store_b);
  LinkData *el_a = static_cast<LinkData *>(lb_a->first);
  LinkData *el_b = static_cast<LinkData *>(lb_b->first);
  LinkData *el_b_first = el_b;
  LinkData *el_b_best = nullptr;

  float len_best = FLT_MAX;

  for (; el_b; el_b = el_b->next) {
    const float len = bm_edgeloop_offset_length(el_a, el_b, el_b_first, len_best);
    if (len < len_best) {
      el_b_best = el_b;
      len_best = len;
    }
  }

  if (el_b_best) {
    BLI_listbase_rotate_first(lb_b, el_b_best);
  }
}

static void bm_face_edges_tag_out(BMesh *bm, BMFace *f)
{
  BMLoop *l_iter, *l_first;
  l_iter = l_first = BM_FACE_FIRST_LOOP(f);
  do {
    BMO_edge_flag_enable(bm, l_iter->e, EDGE_OUT);
  } while ((l_iter = l_iter->next) != l_first);
}

static bool bm_edge_test_cb(BMEdge *e, void *bm_v)
{
  return BMO_edge_flag_test((BMesh *)bm_v, e, EDGE_MARK);
}

static void bridge_loop_pair(BMesh *bm,
                             BMEdgeLoopStore *el_store_a,
                             BMEdgeLoopStore *el_store_b,
                             const bool use_merge,
                             const float merge_factor,
                             const int twist_offset)
{
  const float eps = 0.00001f;
  LinkData *el_a_first, *el_b_first;
  const bool is_closed = BM_edgeloop_is_closed(el_store_a) && BM_edgeloop_is_closed(el_store_b);
  int el_store_a_len, el_store_b_len;
  bool el_store_b_free = false;
  float el_dir[3];
  float dot_a, dot_b;
  const bool use_edgeout = true;

  el_store_a_len = BM_edgeloop_length_get(el_store_a);
  el_store_b_len = BM_edgeloop_length_get(el_store_b);

  if (el_store_a_len < el_store_b_len) {
    std::swap(el_store_a_len, el_store_b_len);
    std::swap(el_store_a, el_store_b);
  }

  if (use_merge) {
    BLI_assert(el_store_a_len == el_store_b_len);
  }

  if (el_store_a_len != el_store_b_len) {
    BM_mesh_elem_hflag_disable_all(bm, BM_FACE | BM_EDGE, BM_ELEM_TAG, false);
  }

  sub_v3_v3v3(el_dir, BM_edgeloop_center_get(el_store_a), BM_edgeloop_center_get(el_store_b));

  if (is_closed) {
    /* if all loops are closed this will calculate twice for all loops */
    BM_edgeloop_calc_normal(bm, el_store_a);
    BM_edgeloop_calc_normal(bm, el_store_b);
  }
  else {
    ListBase *lb_a = BM_edgeloop_verts_get(el_store_a);
    ListBase *lb_b = BM_edgeloop_verts_get(el_store_b);

    /* normalizing isn't strictly needed but without we may get very large values */
    float no[3];
    float dir_a_orig[3], dir_b_orig[3];
    float dir_a[3], dir_b[3];
    const float *test_a, *test_b;

    sub_v3_v3v3(dir_a_orig,
                ((BMVert *)(((LinkData *)lb_a->first)->data))->co,
                ((BMVert *)(((LinkData *)lb_a->last)->data))->co);
    sub_v3_v3v3(dir_b_orig,
                ((BMVert *)(((LinkData *)lb_b->first)->data))->co,
                ((BMVert *)(((LinkData *)lb_b->last)->data))->co);

    /* make the directions point out from the normals, 'no' is used as a temp var */
    cross_v3_v3v3(no, dir_a_orig, el_dir);
    cross_v3_v3v3(dir_a, no, el_dir);
    cross_v3_v3v3(no, dir_b_orig, el_dir);
    cross_v3_v3v3(dir_b, no, el_dir);

    if (LIKELY(!is_zero_v3(dir_a) && !is_zero_v3(dir_b))) {
      test_a = dir_a;
      test_b = dir_b;
    }
    else {
      /**
       * This is a corner case:
       *
       * <pre>
       *  (loop a)    (loop b)
       * +--------+  +--------+
       * </pre>
       *
       * When loops are aligned to the direction between
       * the loops values of 'dir_a/b' is degenerate,
       * in this case compare the original directions
       * (before they were corrected by 'el_dir'),
       * see: #43013
       */
      test_a = dir_a_orig;
      test_b = dir_b_orig;
    }

    if (dot_v3v3(test_a, test_b) < 0.0f) {
      BM_edgeloop_flip(bm, el_store_b);
    }

    normalize_v3_v3(no, el_dir);
    BM_edgeloop_calc_normal_aligned(bm, el_store_a, no);
    BM_edgeloop_calc_normal_aligned(bm, el_store_b, no);
  }

  dot_a = dot_v3v3(BM_edgeloop_normal_get(el_store_a), el_dir);
  dot_b = dot_v3v3(BM_edgeloop_normal_get(el_store_b), el_dir);

  if (UNLIKELY((len_squared_v3(el_dir) < eps) || ((fabsf(dot_a) < eps) && (fabsf(dot_b) < eps)))) {
    /* in this case there is no depth between the two loops,
     * eg: 2x 2d circles, one scaled smaller,
     * in this case 'el_dir' can't be used, just ensure we have matching flipping. */
    if (dot_v3v3(BM_edgeloop_normal_get(el_store_a), BM_edgeloop_normal_get(el_store_b)) < 0.0f) {
      BM_edgeloop_flip(bm, el_store_b);
    }
  }
  else if ((dot_a < 0.0f) != (dot_b < 0.0f)) {
    BM_edgeloop_flip(bm, el_store_b);
  }

  /* we only care about flipping if we make faces */
  if (use_merge == false) {
    float no[3];

    add_v3_v3v3(no, BM_edgeloop_normal_get(el_store_a), BM_edgeloop_normal_get(el_store_b));

    if (dot_v3v3(no, el_dir) < 0.0f) {
      BM_edgeloop_flip(bm, el_store_a);
      BM_edgeloop_flip(bm, el_store_b);
    }

    /* vote on winding (so new face winding is based on existing connected faces) */
    if (bm->totface) {
      BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
      int i;
      int winding_votes[2] = {0, 0};
      int winding_dir = 1;
      for (i = 0; i < 2; i++, winding_dir = -winding_dir) {
        LISTBASE_FOREACH (LinkData *, el, BM_edgeloop_verts_get(estore_pair[i])) {
          LinkData *el_next = BM_EDGELINK_NEXT(estore_pair[i], el);
          if (el_next) {
            BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el->data),
                                       static_cast<BMVert *>(el_next->data));
            if (e && BM_edge_is_boundary(e)) {
              winding_votes[i] += ((e->l->v == el->data) ? winding_dir : -winding_dir);
            }
          }
        }
      }

      if (winding_votes[0] || winding_votes[1]) {
        bool flip[2] = {false, false};

        /* for direction aligned loops we can't rely on the directly we have,
         * use the winding defined by the connected faces (see #48356). */
        if (fabsf(dot_a) < eps) {
          if (winding_votes[0] < 0) {
            flip[0] = !flip[0];
            winding_votes[0] *= -1;
          }
        }
        if (fabsf(dot_b) < eps) {
          if (winding_votes[1] < 0) {
            flip[1] = !flip[1];
            winding_votes[1] *= -1;
          }
        }

        /* when both loops contradict the winding, flip them so surrounding geometry matches */
        if ((winding_votes[0] + winding_votes[1]) < 0) {
          flip[0] = !flip[0];
          flip[1] = !flip[1];

/* valid but unused */
#if 0
          winding_votes[0] *= -1;
          winding_votes[1] *= -1;
#endif
        }

        if (flip[0]) {
          BM_edgeloop_flip(bm, el_store_a);
        }
        if (flip[1]) {
          BM_edgeloop_flip(bm, el_store_b);
        }
      }
    }
  }

  if (el_store_a_len > el_store_b_len) {
    el_store_b = BM_edgeloop_copy(el_store_b);
    BM_edgeloop_expand(bm, el_store_b, el_store_a_len, false, nullptr);
    el_store_b_free = true;
  }

  if (is_closed) {
    bm_bridge_best_rotation(el_store_a, el_store_b);

    /* add twist */
    if (twist_offset != 0) {
      const int len_b = BM_edgeloop_length_get(el_store_b);
      ListBase *lb_b = BM_edgeloop_verts_get(el_store_b);
      LinkData *el_b = static_cast<LinkData *>(BLI_rfindlink(lb_b, mod_i(twist_offset, len_b)));
      BLI_listbase_rotate_first(lb_b, el_b);
    }
  }

  /* Assign after flipping is finalized */
  el_a_first = static_cast<LinkData *>(BM_edgeloop_verts_get(el_store_a)->first);
  el_b_first = static_cast<LinkData *>(BM_edgeloop_verts_get(el_store_b)->first);

  if (use_merge) {
    bm_bridge_splice_loops(bm, el_a_first, el_b_first, merge_factor);
  }
  else {
    LinkData *el_a = el_a_first;
    LinkData *el_b = el_b_first;

    LinkData *el_a_next;
    LinkData *el_b_next;

    while (true) {
      BMFace *f, *f_example;
      BMLoop *l_iter;
      BMVert *v_a, *v_b, *v_a_next, *v_b_next;

      BMLoop *l_a = nullptr;
      BMLoop *l_b = nullptr;
      BMLoop *l_a_next = nullptr;
      BMLoop *l_b_next = nullptr;

      if (is_closed) {
        el_a_next = BM_EDGELINK_NEXT(el_store_a, el_a);
        el_b_next = BM_EDGELINK_NEXT(el_store_b, el_b);
      }
      else {
        el_a_next = el_a->next;
        el_b_next = el_b->next;
        if (ELEM(nullptr, el_a_next, el_b_next)) {
          break;
        }
      }

      v_a = static_cast<BMVert *>(el_a->data);
      v_b = static_cast<BMVert *>(el_b->data);
      v_a_next = static_cast<BMVert *>(el_a_next->data);
      v_b_next = static_cast<BMVert *>(el_b_next->data);

      /* get loop data - before making the face */
      if (v_b != v_b_next) {
        bm_vert_loop_pair(bm, v_a, v_a_next, &l_a, &l_a_next);
        bm_vert_loop_pair(bm, v_b, v_b_next, &l_b, &l_b_next);
      }
      else {
        /* lazy, could be more clever here */
        bm_vert_loop_pair(bm, v_a, v_a_next, &l_a, &l_a_next);
        l_b = l_b_next = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v_b, 0));
      }

      if (l_a && l_a_next == nullptr) {
        l_a_next = l_a;
      }
      if (l_a_next && l_a == nullptr) {
        l_a = l_a_next;
      }
      if (l_b && l_b_next == nullptr) {
        l_b_next = l_b;
      }
      if (l_b_next && l_b == nullptr) {
        l_b = l_b_next;
      }
      f_example = l_a ? l_a->f : (l_b ? l_b->f : nullptr);

      if (v_b != v_b_next) {
#ifdef USE_DUPLICATE_FACE_VERT_CHECK /* Only check for duplicates between loops. */
        BLI_assert((v_b != v_b_next) && (v_a_next != v_a));
        if (UNLIKELY(ELEM(v_b, v_a_next, v_a) || ELEM(v_b_next, v_a_next, v_a))) {
          f = nullptr;
        }
        else
#endif
        {
          BMVert *v_arr[4] = {v_b, v_b_next, v_a_next, v_a};
          f = BM_face_exists(v_arr, 4);
          if (f == nullptr) {
            /* copy if loop data if its is missing on one ring */
            f = BM_face_create_verts(bm, v_arr, 4, nullptr, BM_CREATE_NOP, true);

            l_iter = BM_FACE_FIRST_LOOP(f);
            if (l_b) {
              BM_elem_attrs_copy(bm, l_b, l_iter);
            }
            l_iter = l_iter->next;
            if (l_b_next) {
              BM_elem_attrs_copy(bm, l_b_next, l_iter);
            }
            l_iter = l_iter->next;
            if (l_a_next) {
              BM_elem_attrs_copy(bm, l_a_next, l_iter);
            }
            l_iter = l_iter->next;
            if (l_a) {
              BM_elem_attrs_copy(bm, l_a, l_iter);
            }
          }
        }
      }
      else {
#ifdef USE_DUPLICATE_FACE_VERT_CHECK /* Only check for duplicates between loops. */
        BLI_assert(v_a_next != v_a);
        if (UNLIKELY(ELEM(v_b, v_a_next, v_a))) {
          f = nullptr;
        }
        else
#endif
        {
          BMVert *v_arr[3] = {v_b, v_a_next, v_a};
          f = BM_face_exists(v_arr, 3);
          if (f == nullptr) {
            /* fan-fill a triangle */
            f = BM_face_create_verts(bm, v_arr, 3, nullptr, BM_CREATE_NOP, true);

            l_iter = BM_FACE_FIRST_LOOP(f);
            if (l_b) {
              BM_elem_attrs_copy(bm, l_b, l_iter);
            }
            l_iter = l_iter->next;
            if (l_a_next) {
              BM_elem_attrs_copy(bm, l_a_next, l_iter);
            }
            l_iter = l_iter->next;
            if (l_a) {
              BM_elem_attrs_copy(bm, l_a, l_iter);
            }
          }
        }
      }

#ifdef USE_DUPLICATE_FACE_VERT_CHECK
      if (f != nullptr)
#endif
      {
        if (f_example && (f_example != f)) {
          BM_elem_attrs_copy(bm, f_example, f);
        }
        BMO_face_flag_enable(bm, f, FACE_OUT);
        BM_elem_flag_enable(f, BM_ELEM_TAG);

        /* tag all edges of the face, untag the loop edges after */
        if (use_edgeout) {
          bm_face_edges_tag_out(bm, f);
        }
      }

      if (el_a_next == el_a_first) {
        break;
      }

      el_a = el_a_next;
      el_b = el_b_next;
    }
  }

  if (el_store_a_len != el_store_b_len) {
    BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
    int i;

    BMOperator op_sub;
    /* when we have to bridge between different sized edge-loops,
     * be clever and post-process for best results */

    /* triangulate inline */
    BMO_op_initf(bm, &op_sub, 0, "triangulate faces=%hf", BM_ELEM_TAG, true);
    /* calc normals for input faces before executing */
    {
      BMOIter siter;
      BMFace *f;
      BMO_ITER (f, &siter, op_sub.slots_in, "faces", BM_FACE) {
        BM_face_normal_update(f);
      }
    }
    BMO_op_exec(bm, &op_sub);
    BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "faces.out", BM_FACE, FACE_OUT);
    BMO_slot_buffer_hflag_enable(bm, op_sub.slots_out, "faces.out", BM_FACE, BM_ELEM_TAG, false);
    BMO_op_finish(bm, &op_sub);

    /* tag verts on each side so we can restrict rotation of edges to verts on the same side */
    for (i = 0; i < 2; i++) {
      LISTBASE_FOREACH (LinkData *, el, BM_edgeloop_verts_get(estore_pair[i])) {
        BM_elem_flag_set((BMVert *)el->data, BM_ELEM_TAG, i);
      }
    }

    BMO_op_initf(bm,
                 &op_sub,
                 0,
                 "beautify_fill faces=%hf edges=ae use_restrict_tag=%b method=%i",
                 BM_ELEM_TAG,
                 true,
                 1);

    if (use_edgeout) {
      BMOIter siter;
      BMFace *f;
      BMO_ITER (f, &siter, op_sub.slots_in, "faces", BM_FACE) {
        BMO_face_flag_enable(bm, f, FACE_OUT);
        bm_face_edges_tag_out(bm, f);
      }
    }

    BMO_op_exec(bm, &op_sub);
    /* there may also be tagged faces that didn't rotate, mark input */

    if (use_edgeout) {
      BMOIter siter;
      BMFace *f;
      BMO_ITER (f, &siter, op_sub.slots_out, "geom.out", BM_FACE) {
        BMO_face_flag_enable(bm, f, FACE_OUT);
        bm_face_edges_tag_out(bm, f);
      }
    }
    else {
      BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "geom.out", BM_FACE, FACE_OUT);
    }

    BMO_op_finish(bm, &op_sub);
  }

  if (use_edgeout && use_merge == false) {
    /* we've enabled all face edges above, now disable all loop edges */
    BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
    int i;
    for (i = 0; i < 2; i++) {
      LISTBASE_FOREACH (LinkData *, el, BM_edgeloop_verts_get(estore_pair[i])) {
        LinkData *el_next = BM_EDGELINK_NEXT(estore_pair[i], el);
        if (el_next) {
          if (el->data != el_next->data) {
            BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el->data),
                                       static_cast<BMVert *>(el_next->data));
            BMO_edge_flag_disable(bm, e, EDGE_OUT);
          }
        }
      }
    }
  }

  if (el_store_b_free) {
    BM_edgeloop_free(el_store_b);
  }
}

void bmo_bridge_loops_exec(BMesh *bm, BMOperator *op)
{
  ListBase eloops = {nullptr};

  /* merge-bridge support */
  const bool use_pairs = BMO_slot_bool_get(op->slots_in, "use_pairs");
  const bool use_merge = BMO_slot_bool_get(op->slots_in, "use_merge");
  const float merge_factor = BMO_slot_float_get(op->slots_in, "merge_factor");
  const bool use_cyclic = BMO_slot_bool_get(op->slots_in, "use_cyclic") && (use_merge == false);
  const int twist_offset = BMO_slot_int_get(op->slots_in, "twist_offset");
  int count;
  bool changed = false;

  BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);

  count = BM_mesh_edgeloops_find(bm, &eloops, bm_edge_test_cb, bm);

  BM_mesh_edgeloops_calc_center(bm, &eloops);

  if (count < 2) {
    BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Select at least two edge loops");
    goto cleanup;
  }

  if (use_pairs && (count % 2)) {
    BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Select an even number of loops to bridge pairs");
    goto cleanup;
  }

  if (use_merge) {
    bool match = true;
    const int eloop_len = BM_edgeloop_length_get(static_cast<BMEdgeLoopStore *>(eloops.first));
    LISTBASE_FOREACH (LinkData *, el_store, &eloops) {
      if (eloop_len != BM_edgeloop_length_get((BMEdgeLoopStore *)el_store)) {
        match = false;
        break;
      }
    }
    if (!match) {
      BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Selected loops must have equal edge counts");
      goto cleanup;
    }
  }

  if (count > 2) {
    if (use_pairs) {
      BM_mesh_edgeloops_calc_normal(bm, &eloops);
    }
    BM_mesh_edgeloops_calc_order(bm, &eloops, use_pairs);
  }

  LISTBASE_FOREACH (LinkData *, el_store, &eloops) {
    LinkData *el_store_next = el_store->next;

    if (el_store_next == nullptr) {
      if (use_cyclic && (count > 2)) {
        el_store_next = static_cast<LinkData *>(eloops.first);
      }
      else {
        break;
      }
    }

    bridge_loop_pair(bm,
                     (BMEdgeLoopStore *)el_store,
                     (BMEdgeLoopStore *)el_store_next,
                     use_merge,
                     merge_factor,
                     twist_offset);
    if (use_pairs) {
      el_store = el_store->next;
    }
    changed = true;
  }

cleanup:
  BM_mesh_edgeloops_free(&eloops);

  if (changed) {
    if (use_merge == false) {
      BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
      BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
    }
  }
}
