// Copyright (c) 2016  GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.0.3/Surface_mesh_parameterization/include/CGAL/Surface_mesh_parameterization/internal/validity.h $
// $Id: include/CGAL/Surface_mesh_parameterization/internal/validity.h cefe3007d59 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Mael Rouxel-Labbé

#ifndef CGAL_SURFACE_MESH_PARAMETERIZATION_INTERNAL_VALIDITY_H
#define CGAL_SURFACE_MESH_PARAMETERIZATION_INTERNAL_VALIDITY_H

#include <CGAL/license/Surface_mesh_parameterization.h>

#include <CGAL/Surface_mesh_parameterization/internal/Containers_filler.h>
#include <CGAL/Surface_mesh_parameterization/internal/kernel_traits.h>

#include <CGAL/Bbox_2.h>
#include <CGAL/box_intersection_d.h>

#include <CGAL/boost/graph/properties.h>
#include <CGAL/Kernel/global_functions.h>
#include <CGAL/intersections.h>
#include <CGAL/Polygon_mesh_processing/connected_components.h>

#include <boost/iterator/function_output_iterator.hpp>
#include <boost/range/has_range_iterator.hpp>
#include <vector>
#include <type_traits>

namespace CGAL {

namespace Surface_mesh_parameterization {

namespace internal {

template<typename TriangleMesh, typename VertexUVMap>
bool has_flips(const TriangleMesh& mesh,
               typename boost::graph_traits<TriangleMesh>::halfedge_descriptor bhd,
               const VertexUVMap uvmap)
{
  typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::face_descriptor      face_descriptor;

  // Kernel subtypes:
  typedef typename internal::Kernel_traits<TriangleMesh>::Kernel      Kernel;
  typedef typename Kernel::Point_2                                    Point_2;
  typedef typename Kernel::Point_3                                    Point_3;
  typedef typename Kernel::Vector_3                                   Vector_3;

  // Fill containers
  std::unordered_set<vertex_descriptor> vertices;
  std::vector<face_descriptor> faces;

  internal::Containers_filler<TriangleMesh> fc(mesh, vertices, &faces);
  Polygon_mesh_processing::connected_component(
                                    face(opposite(bhd, mesh), mesh),
                                    mesh,
                                    boost::make_function_output_iterator(fc));

  Vector_3 first_triangle_normal(0., 0., 0.);
  bool is_normal_set = false;

  for(face_descriptor fd : faces) {
    // Get 3 vertices of the facet
    halfedge_descriptor hd = halfedge(fd, mesh);
    vertex_descriptor vd0 = target(hd, mesh);
    vertex_descriptor vd1 = target(next(hd, mesh), mesh);
    vertex_descriptor vd2 = source(hd, mesh);

    // Get the 3 vertices position in 2D
    const Point_2& p0 = get(uvmap, vd0);
    const Point_2& p1 = get(uvmap, vd1);
    const Point_2& p2 = get(uvmap, vd2);

    // Compute the facet normal
    Point_3 p0_3D(p0.x(), p0.y(), 0.);
    Point_3 p1_3D(p1.x(), p1.y(), 0.);
    Point_3 p2_3D(p2.x(), p2.y(), 0.);
    Vector_3 v01_3D = p1_3D - p0_3D;
    Vector_3 v02_3D = p2_3D - p0_3D;
    Vector_3 normal = CGAL::cross_product(v01_3D, v02_3D);

    // Check that all normals are oriented the same way
    if (!is_normal_set) {
      first_triangle_normal = normal;
      is_normal_set = true;
    } else {
      if (first_triangle_normal * normal < 0)
        return true;
    }
  }
  return false;
}

template <typename TriangleMesh, typename VertexUVMap>
class Intersect_facets
{
  typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::face_descriptor      face_descriptor;

  // Kernel subtypes:
  typedef typename internal::Kernel_traits<TriangleMesh>::Kernel    Kernel;
  typedef typename Kernel::Point_2                                  Point_2;
  typedef typename Kernel::Segment_2                                Segment_2;
  typedef typename Kernel::Triangle_2                               Triangle_2;
  typedef typename Kernel::FT                                       NT;

  typename Kernel::Construct_segment_2 segment_functor;
  typename Kernel::Construct_triangle_2 triangle_functor;
  typename Kernel::Do_intersect_2 do_intersect_2_functor;

  typedef CGAL::Box_intersection_d::ID_FROM_BOX_ADDRESS Box_policy;
  typedef CGAL::Box_intersection_d::Box_with_info_d<NT, 2, face_descriptor, Box_policy> Box;

  const TriangleMesh& mesh;
  const VertexUVMap uvmap;
  unsigned int& self_intersection_counter;

public:
  unsigned int number_of_self_intersections() const { return self_intersection_counter; }

  // callback functor that reports all truly intersecting triangles
  void operator()(const Box* a, const Box* b) const
  {
    // Boxes intersect, need to check if there is actually an intersection
    // and if it is not a subface of the faces
    halfedge_descriptor h = halfedge(a->info(), mesh);
    halfedge_descriptor g = halfedge(b->info(), mesh);

    // check for shared edge
    if(face(opposite(h, mesh), mesh) == b->info() ||
       face(opposite(prev(h, mesh), mesh), mesh) == b->info() ||
       face(opposite(next(h, mesh), mesh), mesh) == b->info()) {
      // shared edge

      // intersection if the orientations are not identical
      if(CGAL::orientation(get(uvmap, target(h, mesh)),
                           get(uvmap, target(next(h, mesh), mesh)),
                           get(uvmap, source(h, mesh))) !=
         CGAL::orientation(get(uvmap, target(g, mesh)),
                           get(uvmap, target(next(g, mesh), mesh)),
                           get(uvmap, source(g, mesh)))) {
        ++self_intersection_counter;
      }
      return;
    }

    // check for shared vertex --> possible intersection
    halfedge_descriptor hd;

    if(target(h, mesh) == target(g, mesh))
      hd = g;
    if(target(h, mesh) == target(next(g, mesh), mesh))
      hd = next(g, mesh);
    if(target(h, mesh) == target(next(next(g, mesh), mesh), mesh))
      hd = next(next(g, mesh), mesh);

    if(target(h, mesh) == target(g, mesh))
      hd = g;
    if(target(h, mesh) == target(next(g, mesh), mesh))
      hd = next(g, mesh);
    if(target(h, mesh) == target(next(next(g, mesh), mesh), mesh))
      hd = next(next(g, mesh), mesh);

    if(hd == halfedge_descriptor()) {
      h = next(h, mesh);
      if(target(h, mesh) == target(g, mesh))
        hd = g;
      if(target(h, mesh) == target(next(g, mesh), mesh))
        hd = next(g, mesh);
      if(target(h, mesh) == target(next(next(g, mesh), mesh), mesh))
        hd = next(next(g, mesh), mesh);
      if(hd == halfedge_descriptor()) {
        h = next(h, mesh);
        if(target(h, mesh) == target(g, mesh))
          hd = g;
        if(target(h, mesh) == target(next(g, mesh), mesh))
          hd = next(g, mesh);
        if(target(h, mesh) == target(next(next(g, mesh), mesh), mesh))
          hd = next(next(g, mesh), mesh);
      }
    }

    if(hd != halfedge_descriptor()) {
      // shared vertex
      CGAL_assertion(target(h, mesh) == target(hd, mesh));

      // geometric check if the opposite segments intersect the triangles
      Triangle_2 t1 = triangle_functor(get(uvmap, target(h, mesh)),
                                       get(uvmap, target(next(h, mesh), mesh)),
                                       get(uvmap, target(next(next(h, mesh), mesh), mesh)));
      Triangle_2 t2 = triangle_functor(get(uvmap, target(hd, mesh)),
                                       get(uvmap, target(next(hd, mesh), mesh)),
                                       get(uvmap, target(next(next(hd, mesh), mesh), mesh)));

      Segment_2 s1 = segment_functor(get(uvmap, target(next(h, mesh), mesh)),
                                     get(uvmap, target(next(next(h, mesh), mesh), mesh)));
      Segment_2 s2 = segment_functor(get(uvmap, target(next(hd, mesh), mesh)),
                                     get(uvmap, target(next(next(hd, mesh), mesh), mesh)));

      if(do_intersect_2_functor(t1, s2)) {
        ++self_intersection_counter;
      } else if(do_intersect_2_functor(t2, s1)) {
        ++self_intersection_counter;
      }
      return;
    }

    // check for geometric intersection
    Triangle_2 t1 = triangle_functor(get(uvmap, target(h, mesh)),
                                     get(uvmap, target(next(h, mesh), mesh)),
                                     get(uvmap, target(next(next(h, mesh), mesh), mesh)));
    Triangle_2 t2 = triangle_functor(get(uvmap, target(g, mesh)),
                                     get(uvmap, target(next(g, mesh), mesh)),
                                     get(uvmap, target(next(next(g, mesh), mesh), mesh)));
    if(do_intersect_2_functor(t1, t2)) {
      ++self_intersection_counter;
    }
  }

  Intersect_facets(const TriangleMesh& mesh_,
                   const VertexUVMap uvmap_,
                   unsigned int& counter)
    :
      mesh(mesh_),
      uvmap(uvmap_),
      self_intersection_counter(counter)
  { }
};

/// returns whether the 3D -> 2D mapping is one-to-one.
/// This function is stronger than "has_flips()" because the parameterized
/// surface can loop over itself without creating any flips.
template <typename TriangleMesh,
          typename Faces_Container,
          typename VertexUVMap>
bool is_one_to_one_mapping(const TriangleMesh& mesh,
                           const Faces_Container& faces,
                           const VertexUVMap uvmap,
                           std::enable_if_t<
                              boost::has_range_iterator<Faces_Container>::value
                           >* = nullptr)
{
  typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor    vertex_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::halfedge_descriptor  halfedge_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::face_descriptor      face_descriptor;

  // Kernel subtypes:
  typedef typename internal::Kernel_traits<TriangleMesh>::Kernel      Kernel;
  typedef typename Kernel::FT                                         NT;
  typedef typename Kernel::Point_2                                    Point_2;

  typedef CGAL::Box_intersection_d::ID_FROM_BOX_ADDRESS Box_policy;
  typedef CGAL::Box_intersection_d::Box_with_info_d<NT, 2, face_descriptor, Box_policy> Box;

  // Create the corresponding vector of bounding boxes
  std::vector<Box> boxes;

  for(face_descriptor fd : faces) {
    halfedge_descriptor hd = halfedge(fd, mesh);
    vertex_descriptor vd0 = target(hd, mesh);
    vertex_descriptor vd1 = target(next(hd, mesh), mesh);
    vertex_descriptor vd2 = source(hd, mesh);

    // Get the 3 vertices position in 2D
    const Point_2& p0 = get(uvmap, vd0);
    const Point_2& p1 = get(uvmap, vd1);
    const Point_2& p2 = get(uvmap, vd2);

    NT bx[2] = { (std::min)(p0[0], (std::min)(p1[0], p2[0])),
                 (std::min)(p0[1], (std::min)(p1[1], p2[1])) };
    NT by[2] = { (std::max)(p0[0], (std::max)(p1[0], p2[0])),
                 (std::max)(p0[1], (std::max)(p1[1], p2[1])) };
    boxes.emplace_back(bx, by, fd);
  }

  std::vector<const Box*> boxes_ptr;
  boxes_ptr.reserve(boxes.size());

  for(Box& b : boxes)
    boxes_ptr.push_back(&b);

  // Run the self intersection algorithm with all defaults
  unsigned int counter = 0;
  Intersect_facets<TriangleMesh, VertexUVMap> intersect_facets(mesh, uvmap, counter);
  std::ptrdiff_t cutoff = 2000;
  CGAL::box_self_intersection_d(boxes_ptr.begin(), boxes_ptr.end(), intersect_facets, cutoff);
  return (counter == 0);
}

template <typename TriangleMesh,
          typename VertexUVMap>
bool is_one_to_one_mapping(const TriangleMesh& mesh,
                           typename boost::graph_traits<TriangleMesh>::halfedge_descriptor bhd,
                           const VertexUVMap uvmap)
{
  typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor  vertex_descriptor;
  typedef typename boost::graph_traits<TriangleMesh>::face_descriptor    face_descriptor;

  std::unordered_set<vertex_descriptor> vertices;
  std::vector<face_descriptor> faces;

  internal::Containers_filler<TriangleMesh> fc(mesh, vertices, &faces);
  Polygon_mesh_processing::connected_component(
                                    face(opposite(bhd, mesh), mesh),
                                    mesh,
                                    boost::make_function_output_iterator(fc));

  return is_one_to_one_mapping(mesh, faces, uvmap);
}

} // namespace internal

} // namespace Surface_mesh_parameterization

} // namespace CGAL

#endif // CGAL_SURFACE_MESH_PARAMETERIZATION_INTERNAL_VALIDITY_H
