// Copyright (c) 2012-2013, IGN France.
// Copyright (c) 2012-2024, Oslandia.
// Copyright (c) 2024-2026, SFCGAL team.
// SPDX-License-Identifier: LGPL-2.0-or-later

#include "SFCGAL/PolyhedralSurface.h"
#include "SFCGAL/Exception.h"
#include "SFCGAL/GeometryVisitor.h"
#include "SFCGAL/algorithm/meshToPolyhedralSurface.h"

#include <CGAL/Cartesian_converter.h>

using namespace SFCGAL::detail;

namespace SFCGAL {

PolyhedralSurface::PolyhedralSurface() = default;

PolyhedralSurface::PolyhedralSurface(const std::vector<Polygon> &polygons)
{
  _polygons.reserve(polygons.size());
  for (const auto &polygon : polygons) {
    _polygons.emplace_back(polygon.clone());
  }
}

PolyhedralSurface::PolyhedralSurface(const std::unique_ptr<Geometry> &geometry)
{
  if (geometry->is<PolyhedralSurface>()) {
    *this = static_cast<const PolyhedralSurface &>(*geometry);
  } else if (geometry->is<TriangulatedSurface>()) {
    const TriangulatedSurface &triangulatedSurface =
        geometry->as<TriangulatedSurface>();
    for (size_t i = 0; i < triangulatedSurface.numPatches(); ++i) {
      this->addPatch(triangulatedSurface.patchN(i));
    }
  } else if (geometry->is<Polygon>()) {
    this->addPatch(geometry->as<Polygon>());
  } else {
    throw std::invalid_argument("Cannot convert geometry to PolyhedralSurface");
  }
}

PolyhedralSurface::PolyhedralSurface(const PolyhedralSurface &other)
    : GeometryImpl(other)
{
  _polygons.reserve(other._polygons.size());
  for (const auto &poly : other._polygons) {
    _polygons.emplace_back(poly->clone());
  }
}

PolyhedralSurface::PolyhedralSurface(const Mesh &sm, bool simplifyTriangulation)
{
  using VertexIndex = Mesh::Vertex_index;

  if (!simplifyTriangulation) {
    // Directly convert all faces to patches
    for (auto face : sm.faces()) {
      auto new_face = std::make_unique<LineString>();
      for (VertexIndex const vertexDesc :
           vertices_around_face(sm.halfedge(face), sm)) {
        new_face->addPoint(Point(sm.point(vertexDesc)));
      }

      new_face->addPoint(new_face->startPoint().clone());
      _polygons.push_back(std::make_unique<Polygon>(std::move(new_face)));
    }
  } else {
    // Simplify triangulation and then convert faces to patches
    std::unique_ptr<PolyhedralSurface> phs =
        algorithm::meshToPolyhedralSurface(sm);
    swap(*phs);
  }
}

PolyhedralSurface::PolyhedralSurface(const InexactMesh &inexactMesh)
{
  using inexact_to_exact = CGAL::Cartesian_converter<InexactKernel, Kernel>;
  using vertexDescriptor = Mesh::Vertex_index;

  inexact_to_exact toExact;
  for (auto face : inexactMesh.faces()) {
    auto new_face = std::make_unique<LineString>();
    for (vertexDescriptor const vertexDesc :
         vertices_around_face(inexactMesh.halfedge(face), inexactMesh)) {
      new_face->addPoint(Point(toExact(inexactMesh.point(vertexDesc))));
    }

    new_face->addPoint(new_face->startPoint().clone());
    _polygons.push_back(std::make_unique<Polygon>(std::move(new_face)));
  }
}

auto
PolyhedralSurface::operator=(PolyhedralSurface other) -> PolyhedralSurface &
{
  swap(other);
  return *this;
}

PolyhedralSurface::~PolyhedralSurface() = default;

auto
PolyhedralSurface::geometryType() const -> std::string
{
  return "PolyhedralSurface";
}

auto
PolyhedralSurface::geometryTypeId() const -> GeometryType
{
  return TYPE_POLYHEDRALSURFACE;
}

auto
PolyhedralSurface::dimension() const -> int
{
  return 2;
}

auto
PolyhedralSurface::coordinateDimension() const -> int
{
  if (isEmpty()) {
    return 0;
  }
  return _polygons.front()->coordinateDimension();
}

auto
PolyhedralSurface::isEmpty() const -> bool
{
  return _polygons.empty();
}

auto
PolyhedralSurface::is3D() const -> bool
{
  if (isEmpty()) {
    return false;
  }
  return _polygons.front()->is3D();
}

auto
PolyhedralSurface::isMeasured() const -> bool
{
  if (isEmpty()) {
    return false;
  }
  return _polygons.front()->isMeasured();
}

auto
PolyhedralSurface::dropZ() -> bool
{
  if (!is3D()) {
    return false;
  }

  for (auto &polygon : _polygons) {
    polygon->dropZ();
  }

  return true;
}

auto
PolyhedralSurface::dropM() -> bool
{
  if (!isMeasured()) {
    return false;
  }

  for (auto &polygon : _polygons) {
    polygon->dropM();
  }

  return true;
}

auto
PolyhedralSurface::swapXY() -> void
{
  for (auto &polygon : _polygons) {
    polygon->swapXY();
  }
}

auto
PolyhedralSurface::toTriangulatedSurface() const -> TriangulatedSurface
{
  TriangulatedSurface result;
  triangulate::triangulatePolygon3D(*this, result);
  return result;
}

void
PolyhedralSurface::addPatch(const Polygon &patch)
{
  addPatch(patch.clone());
}

void
PolyhedralSurface::addPatch(Polygon *patch)
{
  addPatch(std::unique_ptr<Polygon>(patch));
}

void
PolyhedralSurface::addPatch(std::unique_ptr<Polygon> patch)
{
  BOOST_ASSERT(patch != nullptr);
  _polygons.emplace_back(std::move(patch));
}

void
PolyhedralSurface::addPatches(const PolyhedralSurface &polyhedralSurface)
{
  _polygons.reserve(_polygons.size() + polyhedralSurface.numPatches());
  for (size_t i = 0; i < polyhedralSurface.numPatches(); i++) {
    addPatch(polyhedralSurface.patchN(i));
  }
}

void
PolyhedralSurface::addPolygon(const Polygon &polygon)
{
  addPatch(polygon);
}

void
PolyhedralSurface::addPolygon(Polygon *polygon)
{
  addPatch(std::unique_ptr<Polygon>(polygon));
}

void
PolyhedralSurface::addPolygons(const PolyhedralSurface &polyhedralSurface)
{
  addPatches(polyhedralSurface);
}

void
PolyhedralSurface::setPatchN(Polygon *patch, size_t const &idx)
{
  setPatchN(std::unique_ptr<Polygon>(patch), idx);
}

void
PolyhedralSurface::setPatchN(std::unique_ptr<Polygon> patch, size_t const &idx)
{
  BOOST_ASSERT(patch != nullptr);

  if (idx >= numPatches()) {
    BOOST_THROW_EXCEPTION(
        Exception((boost::format("Cannot set geometry at position %s. "
                                 "PolyhedralSurface has only %d geometries.") %
                   idx % numPatches())
                      .str()));
  }

  _polygons[idx] = std::move(patch);
}

void
PolyhedralSurface::setPatchN(const Polygon &patch, size_t const &idx)
{
  setPatchN(patch.clone(), idx);
}

void
PolyhedralSurface::setPatchN(std::unique_ptr<Geometry> geometry,
                             size_t const             &idx)
{
  if (geometry->geometryTypeId() != TYPE_POLYGON) {
    std::ostringstream oss;
    oss << "try to set a '" << geometry->geometryType()
        << "' in a PolyhedralSurface\n";
    BOOST_THROW_EXCEPTION(InappropriateGeometryException(oss.str()));
  }

  setPatchN(geom_unique_ptr_as<Polygon>(std::move(geometry)), idx);
}

void
PolyhedralSurface::setPatchN(Geometry *geometry, size_t const &idx)
{
  setPatchN(std::unique_ptr<Geometry>(geometry), idx);
}

void
PolyhedralSurface::setPatchN(const Geometry &geometry, size_t const &idx)
{
  setPatchN(std::unique_ptr<Geometry>(geometry.clone()), idx);
}

void
PolyhedralSurface::accept(GeometryVisitor &visitor)
{
  visitor.visit(*this);
}

void
PolyhedralSurface::accept(ConstGeometryVisitor &visitor) const
{
  visitor.visit(*this);
}

auto
PolyhedralSurface::toSurfaceMesh() const -> Surface_mesh_3
{
  // Convert to triangulated surface first, then to surface mesh
  TriangulatedSurface triangulated = toTriangulatedSurface();
  return triangulated.toSurfaceMesh();
}

auto
PolyhedralSurface::numEdges() const -> size_t
{
  Surface_mesh_3 mesh = toSurfaceMesh();
  return mesh.number_of_edges();
}

// Explicit instantiations
#ifndef DOXYGEN_SHOULD_SKIP_THIS
template PolyhedralSurface::PolyhedralSurface(const detail::MarkedPolyhedron &);
template PolyhedralSurface::PolyhedralSurface(
    const CGAL::Polyhedron_3<Kernel> &);
#endif

} // namespace SFCGAL
