// 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/Polygon.h"
#include "SFCGAL/GeometryVisitor.h"

#include "SFCGAL/LineString.h"
#include "SFCGAL/Triangle.h"
#include "SFCGAL/algorithm/orientation.h"

namespace SFCGAL {

Polygon::Polygon() { _rings.push_back(std::make_unique<LineString>()); }

Polygon::Polygon(const std::vector<LineString> &rings)
{
  if (rings.empty()) {
    _rings.resize(1);
    _rings[0] = std::make_unique<LineString>();
  } else {
    for (const auto &ring : rings) {
      _rings.push_back(ring.clone());
    }
  }
}

Polygon::Polygon(const LineString &exteriorRing) : Polygon(exteriorRing.clone())
{
}

Polygon::Polygon(LineString *exteriorRing)
    : Polygon(std::unique_ptr<LineString>(exteriorRing))
{
}

Polygon::Polygon(std::unique_ptr<LineString> exteriorRing)
{
  _rings.emplace_back(std::move(exteriorRing));
}

Polygon::Polygon(const Triangle &triangle)
{
  _rings.push_back(std::make_unique<LineString>());

  if (!triangle.isEmpty()) {
    for (size_t i = 0; i < 4; i++) {
      exteriorRing().addPoint(triangle.vertex(i));
    }
  }
}

Polygon::Polygon(const Polygon &other) : GeometryImpl(other)
{
  _rings.reserve(other._rings.size());
  for (const auto &ring : other._rings) {
    _rings.emplace_back(ring->clone());
  }
}

Polygon::Polygon(const CGAL::Polygon_2<Kernel> &other)
{
  _rings.push_back(std::make_unique<LineString>());
  CGAL::Polygon_2<Kernel>::Edge_const_iterator ei;

  for (ei = other.edges_begin(); ei != other.edges_end(); ++ei) {
    _rings.back()->addPoint(ei->source());
  }
}

Polygon::Polygon(const CGAL::Polygon_with_holes_2<Kernel> &poly)
{
  _rings.push_back(std::make_unique<LineString>());
  const CGAL::Polygon_2<Kernel>               &outer = poly.outer_boundary();
  CGAL::Polygon_2<Kernel>::Edge_const_iterator ei;

  for (ei = outer.edges_begin(); ei != outer.edges_end(); ++ei) {
    _rings.back()->addPoint(ei->source());
  }

  _rings.back()->addPoint(_rings.back()->startPoint());

  for (auto hit = poly.holes_begin(); hit != poly.holes_end(); ++hit) {
    _rings.push_back(std::make_unique<LineString>());
    CGAL::Polygon_2<Kernel>::Edge_const_iterator ei;

    for (ei = hit->edges_begin(); ei != hit->edges_end(); ++ei) {
      _rings.back()->addPoint(ei->source());
    }

    _rings.back()->addPoint(_rings.back()->startPoint());
  }
}

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

Polygon::~Polygon() = default;

auto
Polygon::coordinateDimension() const -> int
{
  return _rings[0]->coordinateDimension();
}

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

auto
Polygon::geometryTypeId() const -> GeometryType
{
  return TYPE_POLYGON;
}

auto
Polygon::isEmpty() const -> bool
{
  return exteriorRing().isEmpty();
}

auto
Polygon::is3D() const -> bool
{
  return exteriorRing().is3D();
}

auto
Polygon::isMeasured() const -> bool
{
  return exteriorRing().isMeasured();
}

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

  for (auto &_ring : _rings) {
    _ring->dropZ();
  }

  return true;
}

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

  for (auto &_ring : _rings) {
    _ring->dropM();
  }

  return true;
}

auto
Polygon::swapXY() -> void
{
  for (auto &_ring : _rings) {
    _ring->swapXY();
  }
}

void
Polygon::reverse()
{
  for (size_t i = 0; i < numRings(); i++) {
    ringN(i).reverse();
  }
}

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

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

auto
Polygon::isCounterClockWiseOriented() const -> bool
{
  return algorithm::isCounterClockWiseOriented(*this);
}

auto
Polygon::toPolygon_2(bool fixOrientation) const -> CGAL::Polygon_2<Kernel>
{
  return exteriorRing().toPolygon_2(fixOrientation);
}

auto
Polygon::toPolygon_with_holes_2(bool fixOrientation) const
    -> CGAL::Polygon_with_holes_2<Kernel>
{
  std::list<CGAL::Polygon_2<Kernel>> holes;

  for (size_t i = 0; i < numInteriorRings(); ++i) {
    // note that the orientation is fixed here to avoid double reverse for
    // interior rings
    CGAL::Polygon_2<Kernel> inner = interiorRingN(i).toPolygon_2(false);

    if (fixOrientation && inner.orientation() == CGAL::COUNTERCLOCKWISE) {
      inner.reverse_orientation();
    }

    holes.push_back(inner);
  }

  CGAL::Polygon_2<Kernel> const outer =
      exteriorRing().toPolygon_2(fixOrientation);
  return CGAL::Polygon_with_holes_2<Kernel>(outer, holes.begin(), holes.end());
}

} // namespace SFCGAL
