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

/** \file
 * \ingroup bmesh
 *
 * Edge-net Fill.
 */

#include <climits>

#include "MEM_guardedalloc.h"

#include "BLI_alloca.h"
#include "BLI_linklist.h"
#include "BLI_mempool.h"
#include "BLI_utildefines.h"

#include "bmesh.hh"
#include "bmesh_edgenet.hh" /* own include */

#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */

/* Struct for storing a path of verts walked over */
struct VertNetInfo {
  BMVert *prev; /* previous vertex */
  int pass;     /* path scanning pass value, for internal calculation */
  int face;     /* face index connected to the edge between this and the previous vert */
  int flag;     /* flag */
};

enum {
  VNINFO_FLAG_IS_MIXFACE = (1 << 0),
};

/**
 * Check if this edge can be used in a path.
 */
static bool bm_edge_step_ok(BMEdge *e)
{
  return BM_elem_flag_test(e, BM_ELEM_TAG) && ELEM(e->l, nullptr, e->l->radial_next);
}

static int bm_edge_face(BMEdge *e)
{
  return e->l ? BM_elem_index_get(e->l->f) : -1;
}

/**
 * Get the next available edge we can use to attempt to calculate a path from.
 */
static BMEdge *bm_edgenet_edge_get_next(BMesh *bm,
                                        LinkNode **edge_queue,
                                        BLI_mempool *edge_queue_pool)
{
  BMEdge *e;
  BMIter iter;

  while (*edge_queue) {
    e = static_cast<BMEdge *>(BLI_linklist_pop_pool(edge_queue, edge_queue_pool));
    if (bm_edge_step_ok(e)) {
      return e;
    }
  }

  BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
    if (bm_edge_step_ok(e)) {
      return e;
    }
  }

  return nullptr;
}

/**
 * Edge loops are built up using links to the 'prev' member.
 * with each side of the loop having its own pass (negated from the other).
 *
 * This function returns half a loop, the caller needs to run twice to get both sides.
 */
static uint bm_edgenet_path_from_pass(BMVert *v,
                                      LinkNode **v_ls,
                                      VertNetInfo *vnet_info,
                                      BLI_mempool *path_pool)
{
  VertNetInfo *vn = &vnet_info[BM_elem_index_get(v)];
  const int pass = vn->pass;
  uint v_ls_tot = 0;

  do {
    BLI_linklist_prepend_pool(v_ls, v, path_pool);
    v_ls_tot += 1;
    v = vn->prev;
    vn = &vnet_info[BM_elem_index_get(v)];
  } while (vn->pass == pass);

  return v_ls_tot;
}

/**
 * Specialized wrapper for #BM_face_exists_overlap_subset
 * that gets the verts from a path before we allocate it in the correct order.
 */
static bool bm_edgenet_path_check_overlap(BMVert *v1, BMVert *v2, VertNetInfo *vnet_info)
{
  /* vert order doesn't matter */
  uint v_ls_tot = 0;
  LinkNode *v_ls = nullptr;
  BMVert *v_pair[2] = {v1, v2};
  uint i;

  for (i = 0; i < 2; i++) {
    BMVert *v = v_pair[i];
    VertNetInfo *vn = &vnet_info[BM_elem_index_get(v)];
    const int pass = vn->pass;
    do {
      BLI_linklist_prepend_alloca(&v_ls, v);
      v_ls_tot += 1;
      v = vn->prev;
      vn = &vnet_info[BM_elem_index_get(v)];
    } while (vn->pass == pass);
  }

  if (v_ls_tot) {
    BMVert **vert_arr = BLI_array_alloca(vert_arr, v_ls_tot);
    LinkNode *v_lnk;
    for (i = 0, v_lnk = v_ls; i < v_ls_tot; v_lnk = v_lnk->next, i++) {
      vert_arr[i] = static_cast<BMVert *>(v_lnk->link);
    }

    return BM_face_exists_overlap_subset(vert_arr, int(v_ls_tot));
  }
  return false;
}

/**
 * Create a face from the path.
 */
static BMFace *bm_edgenet_face_from_path(BMesh *bm, LinkNode *path, const uint path_len)
{
  BMFace *f;
  LinkNode *v_lnk;
  int i;
  bool ok;

  BMVert **vert_arr = BLI_array_alloca(vert_arr, path_len);
  BMEdge **edge_arr = BLI_array_alloca(edge_arr, path_len);

  for (v_lnk = path, i = 0; v_lnk; v_lnk = v_lnk->next, i++) {
    vert_arr[i] = static_cast<BMVert *>(v_lnk->link);
  }

  ok = BM_edges_from_verts(edge_arr, vert_arr, i);
  BLI_assert(ok);
  UNUSED_VARS_NDEBUG(ok);

/* no need for this, we do overlap checks before allowing the path to be used */
#if 0
  if (BM_face_exists_multi(vert_arr, edge_arr, path_len)) {
    return nullptr;
  }
#endif

  f = BM_face_create(bm, vert_arr, edge_arr, int(path_len), nullptr, BM_CREATE_NOP);

  return f;
}

/**
 * Step along the path from \a v_curr to any vert not already in the path.
 *
 * \return The connecting edge if the path is found, otherwise nullptr.
 */
static BMEdge *bm_edgenet_path_step(BMVert *v_curr,
                                    LinkNode **v_ls,
                                    VertNetInfo *vnet_info,
                                    BLI_mempool *path_pool)
{
  const VertNetInfo *vn_curr;

  BMEdge *e;
  BMIter iter;
  uint tot;
  uint v_ls_tot;

begin:
  tot = 0;
  v_ls_tot = 0;
  vn_curr = &vnet_info[BM_elem_index_get(v_curr)];

  BM_ITER_ELEM (e, &iter, v_curr, BM_EDGES_OF_VERT) {
    BMVert *v_next = BM_edge_other_vert(e, v_curr);
    if (v_next != vn_curr->prev) {
      if (bm_edge_step_ok(e)) {
        VertNetInfo *vn_next = &vnet_info[BM_elem_index_get(v_next)];

        /* check we're not looping back on ourselves */
        if (vn_curr->pass != vn_next->pass) {

          if (vn_curr->pass == -vn_next->pass) {
            if ((vn_curr->flag & VNINFO_FLAG_IS_MIXFACE) ||
                (vn_next->flag & VNINFO_FLAG_IS_MIXFACE))
            {
              /* found connecting edge */
              if (bm_edgenet_path_check_overlap(v_curr, v_next, vnet_info) == false) {
                return e;
              }
            }
          }
          else {
            vn_next->face = bm_edge_face(e);
            vn_next->pass = vn_curr->pass;
            vn_next->prev = v_curr;

            /* flush flag down the path */
            vn_next->flag &= ~VNINFO_FLAG_IS_MIXFACE;
            if ((vn_curr->flag & VNINFO_FLAG_IS_MIXFACE) || (vn_next->face == -1) ||
                (vn_next->face != vn_curr->face))
            {
              vn_next->flag |= VNINFO_FLAG_IS_MIXFACE;
            }

            /* add to the list! */
            BLI_linklist_prepend_pool(v_ls, v_next, path_pool);
            v_ls_tot += 1;
          }
        }
      }
      tot += 1;
    }
  }

  /* trick to walk along wire-edge paths */
  if (v_ls_tot == 1 && tot == 1) {
    v_curr = static_cast<BMVert *>(BLI_linklist_pop_pool(v_ls, path_pool));
/* avoid recursion, can crash on very large nets */
#if 0
    bm_edgenet_path_step(v_curr, v_ls, vnet_info, path_pool);
#else
    goto begin;
#endif
  }

  return nullptr;
}

/**
 * Given an edge, find the first path that can form a face.
 *
 * \return A linked list of verts.
 */
static LinkNode *bm_edgenet_path_calc(BMEdge *e,
                                      const int pass_nr,
                                      const uint path_cost_max,
                                      uint *r_path_len,
                                      uint *r_path_cost,
                                      VertNetInfo *vnet_info,
                                      BLI_mempool *path_pool)
{
  VertNetInfo *vn_1, *vn_2;
  const int f_index = bm_edge_face(e);
  bool found;

  LinkNode *v_ls_prev = nullptr;
  LinkNode *v_ls_next = nullptr;

  uint path_cost_accum = 0;

  BLI_assert(bm_edge_step_ok(e));

  *r_path_len = 0;
  *r_path_cost = 0;

  vn_1 = &vnet_info[BM_elem_index_get(e->v1)];
  vn_2 = &vnet_info[BM_elem_index_get(e->v2)];

  vn_1->pass = pass_nr;
  vn_2->pass = -pass_nr;

  vn_1->prev = e->v2;
  vn_2->prev = e->v1;

  vn_1->face = vn_2->face = f_index;

  vn_1->flag = vn_2->flag = (f_index == -1) ? VNINFO_FLAG_IS_MIXFACE : 0;

  /* Prime the search-list. */
  BLI_linklist_prepend_pool(&v_ls_prev, e->v1, path_pool);
  BLI_linklist_prepend_pool(&v_ls_prev, e->v2, path_pool);

  do {
    found = false;

    /* no point to continue, we're over budget */
    if (path_cost_accum >= path_cost_max) {
      BLI_linklist_free_pool(v_ls_next, nullptr, path_pool);
      BLI_linklist_free_pool(v_ls_prev, nullptr, path_pool);
      return nullptr;
    }

    while (v_ls_prev) {
      const LinkNode *v_ls_next_old = v_ls_next;
      BMVert *v = static_cast<BMVert *>(BLI_linklist_pop_pool(&v_ls_prev, path_pool));
      BMEdge *e_found = bm_edgenet_path_step(v, &v_ls_next, vnet_info, path_pool);

      if (e_found) {
        LinkNode *path = nullptr;
        uint path_len;
        BLI_linklist_free_pool(v_ls_next, nullptr, path_pool);
        BLI_linklist_free_pool(v_ls_prev, nullptr, path_pool);

        // BLI_assert(BLI_mempool_len(path_pool) == 0);

        path_len = bm_edgenet_path_from_pass(e_found->v1, &path, vnet_info, path_pool);
        BLI_linklist_reverse(&path);
        path_len += bm_edgenet_path_from_pass(e_found->v2, &path, vnet_info, path_pool);
        *r_path_len = path_len;
        *r_path_cost = path_cost_accum;
        return path;
      }

      /* check if a change was made */
      if (v_ls_next_old != v_ls_next) {
        found = true;
      }
    }
    BLI_assert(v_ls_prev == nullptr);

    path_cost_accum++;

    /* swap */
    v_ls_prev = v_ls_next;
    v_ls_next = nullptr;

  } while (found);

  BLI_assert(v_ls_prev == nullptr);
  BLI_assert(v_ls_next == nullptr);

  /* tag not to search again */
  BM_elem_flag_disable(e, BM_ELEM_TAG);

  return nullptr;
}

/**
 * Wrapper for #bm_edgenet_path_calc which ensures all included edges
 * _don't_ have a better option.
 */
static LinkNode *bm_edgenet_path_calc_best(BMEdge *e,
                                           int *pass_nr,
                                           uint path_cost_max,
                                           uint *r_path_len,
                                           uint *r_path_cost,
                                           VertNetInfo *vnet_info,
                                           BLI_mempool *path_pool)
{
  LinkNode *path;
  uint path_cost;

  path = bm_edgenet_path_calc(
      e, *pass_nr, path_cost_max, r_path_len, &path_cost, vnet_info, path_pool);
  (*pass_nr)++;

  if (path == nullptr) {
    return nullptr;
  }
  if (path_cost < 1) {
    /* any face that takes 1 iteration to find we consider valid */
    return path;
  }

  /* Check every edge to see if any can give a better path.
   * This avoids very strange/long paths from being created. */

  const uint path_len = *r_path_len;
  uint i, i_prev;
  BMVert **vert_arr = BLI_array_alloca(vert_arr, path_len);
  LinkNode *v_lnk;

  for (v_lnk = path, i = 0; v_lnk; v_lnk = v_lnk->next, i++) {
    vert_arr[i] = static_cast<BMVert *>(v_lnk->link);
  }

  i_prev = path_len - 1;
  for (i = 0; i < path_len; i++) {
    BMEdge *e_other = BM_edge_exists(vert_arr[i], vert_arr[i_prev]);
    if (e_other != e) {
      LinkNode *path_test;
      uint path_len_test;
      uint path_cost_test;

      path_test = bm_edgenet_path_calc(
          e_other, *pass_nr, path_cost, &path_len_test, &path_cost_test, vnet_info, path_pool);
      (*pass_nr)++;

      if (path_test) {
        BLI_assert(path_cost_test < path_cost);

        BLI_linklist_free_pool(path, nullptr, path_pool);
        path = path_test;
        *r_path_len = path_len_test;
        *r_path_cost = path_cost_test;
        path_cost = path_cost_test;
      }
    }

    i_prev = i;
  }

  return path;
}

void BM_mesh_edgenet(BMesh *bm, const bool use_edge_tag, const bool use_new_face_tag)
{
  VertNetInfo *vnet_info = MEM_calloc_arrayN<VertNetInfo>(size_t(bm->totvert), __func__);
  BLI_mempool *edge_queue_pool = BLI_mempool_create(sizeof(LinkNode), 0, 512, BLI_MEMPOOL_NOP);
  BLI_mempool *path_pool = BLI_mempool_create(sizeof(LinkNode), 0, 512, BLI_MEMPOOL_NOP);
  LinkNode *edge_queue = nullptr;

  BMEdge *e;
  BMIter iter;

  int pass_nr = 1;

  if (use_edge_tag == false) {
    BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
      BM_elem_flag_set(e, BM_ELEM_TAG, bm_edge_step_ok(e));
    }
  }

  BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);

  while (true) {
    LinkNode *path = nullptr;
    uint path_len;
    uint path_cost;

    e = bm_edgenet_edge_get_next(bm, &edge_queue, edge_queue_pool);
    if (e == nullptr) {
      break;
    }

    BLI_assert(bm_edge_step_ok(e) == true);

    path = bm_edgenet_path_calc_best(
        e, &pass_nr, UINT_MAX, &path_len, &path_cost, vnet_info, path_pool);

    if (path) {
      BMFace *f = bm_edgenet_face_from_path(bm, path, path_len);
      /* queue edges to operate on */
      BMLoop *l_first, *l_iter;
      l_iter = l_first = BM_FACE_FIRST_LOOP(f);
      do {
        if (bm_edge_step_ok(l_iter->e)) {
          BLI_linklist_prepend_pool(&edge_queue, l_iter->e, edge_queue_pool);
        }
      } while ((l_iter = l_iter->next) != l_first);

      if (use_new_face_tag) {
        BM_elem_flag_enable(f, BM_ELEM_TAG);
      }

      /* the face index only needs to be unique, not kept valid */
      BM_elem_index_set(f, bm->totface - 1); /* set_dirty */
    }

    BLI_linklist_free_pool(path, nullptr, path_pool);
    BLI_assert(BLI_mempool_len(path_pool) == 0);
  }

  bm->elem_index_dirty |= BM_FACE | BM_LOOP;

  BLI_mempool_destroy(edge_queue_pool);
  BLI_mempool_destroy(path_pool);
  MEM_freeN(vnet_info);
}
