/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"

#include "BKE_mesh.h"

#include "node_geometry_util.hh"

namespace blender::nodes::node_geo_mesh_face_set_boundaries_cc {

static void node_declare(NodeDeclarationBuilder &b)
{
  b.add_input<decl::Int>(N_("Face Set"))
      .default_value(0)
      .hide_value()
      .supports_field()
      .description(N_("An identifier for the group of each face. All contiguous faces with the "
                      "same value are in the same region"));
  b.add_output<decl::Bool>(N_("Boundary Edges"))
      .field_source()
      .description(N_("The edges that lie on the boundaries between the different face sets"));
}

class BoundaryFieldInput final : public bke::MeshFieldInput {
 private:
  const Field<int> face_set;

 public:
  BoundaryFieldInput(const Field<int> face_set)
      : bke::MeshFieldInput(CPPType::get<bool>(), "Boundary Field"), face_set(face_set)
  {
    category_ = Category::Generated;
  }

  GVArray get_varray_for_context(const Mesh &mesh,
                                 const eAttrDomain domain,
                                 const IndexMask /*mask*/) const final
  {
    const bke::MeshFieldContext face_context{mesh, ATTR_DOMAIN_FACE};
    FieldEvaluator face_evaluator{face_context, mesh.totpoly};
    face_evaluator.add(face_set);
    face_evaluator.evaluate();
    const VArray<int> face_set = face_evaluator.get_evaluated<int>(0);

    Array<bool> boundary(mesh.totedge, false);
    Array<bool> edge_visited(mesh.totedge, false);
    Array<int> edge_face_set(mesh.totedge, 0);
    const Span<MPoly> polys = mesh.polys();
    const Span<MLoop> loops = mesh.loops();
    for (const int i : polys.index_range()) {
      const MPoly &poly = polys[i];
      for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
        const int edge = loop.e;
        if (edge_visited[edge]) {
          if (edge_face_set[edge] != face_set[i]) {
            /* This edge is connected to two faces on different face sets. */
            boundary[edge] = true;
          }
        }
        edge_visited[edge] = true;
        edge_face_set[edge] = face_set[i];
      }
    }
    return mesh.attributes().adapt_domain<bool>(
        VArray<bool>::ForContainer(std::move(boundary)), ATTR_DOMAIN_EDGE, domain);
  }

  std::optional<eAttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
  {
    return ATTR_DOMAIN_EDGE;
  }
};

static void node_geo_exec(GeoNodeExecParams params)
{
  const Field<int> face_set_field = params.extract_input<Field<int>>("Face Set");
  Field<bool> face_set_boundaries{std::make_shared<BoundaryFieldInput>(face_set_field)};
  params.set_output("Boundary Edges", std::move(face_set_boundaries));
}

}  // namespace blender::nodes::node_geo_mesh_face_set_boundaries_cc

void register_node_type_geo_mesh_face_set_boundaries()
{
  namespace file_ns = blender::nodes::node_geo_mesh_face_set_boundaries_cc;

  static bNodeType ntype;
  geo_node_type_base(
      &ntype, GEO_NODE_MESH_FACE_SET_BOUNDARIES, "Face Set Boundaries", NODE_CLASS_INPUT);
  ntype.declare = file_ns::node_declare;
  ntype.geometry_node_execute = file_ns::node_geo_exec;
  nodeRegisterType(&ntype);
}
