#include <Rcpp.h>
// Copyright (c) 2003-2007  INRIA Sophia-Antipolis (France).
// Copyright (c) 2008       GeometryFactory, Sophia Antipolis (France)
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1/Surface_mesher/include/CGAL/Surface_mesher/Surface_mesher_regular_edges.h $
// $Id: include/CGAL/Surface_mesher/Surface_mesher_regular_edges.h b26b07a1242 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Steve Oudot, David Rey, Mariette Yvinec, Laurent Rineau, Andreas Fabri

#ifndef CGAL_SURFACE_MESHER_REGULAR_EDGES_H
#define CGAL_SURFACE_MESHER_REGULAR_EDGES_H

#include <CGAL/license/Surface_mesher.h>

#define CGAL_DEPRECATED_HEADER "<CGAL/Surface_mesher/Surface_mesher_regular_edges.h>"
#define CGAL_DEPRECATED_MESSAGE_DETAILS \
  "The 3D Mesh Generation package (see https://doc.cgal.org/latest/Mesh_3/) should be used instead."
#include <CGAL/Installation/internal/deprecation_warning.h>

#include <CGAL/disable_warnings.h>

#include <CGAL/Surface_mesher/Surface_mesher.h>
#include <CGAL/utility.h>
#include <CGAL/circulator.h>
#include <set>
#include <vector>

namespace CGAL {

  namespace Surface_mesher {

  template <
    class C2T3,
    class Surface,
    class SurfaceMeshTraits,
    class Criteria,
    bool withBoundary = false
    >
  class Surface_mesher_regular_edges_base
    : public Surface_mesher_base<C2T3, Surface, SurfaceMeshTraits, Criteria>
  {
    public:

      typedef Surface_mesher_base<C2T3, Surface, SurfaceMeshTraits, Criteria> SMB ;

      typedef C2T3 C2t3;
      typedef typename C2T3::Triangulation Tr;
      typedef typename Tr::Geom_traits GT;
      typedef typename GT::FT FT;
      typedef typename Tr::Point Point;
      typedef typename Tr::Facet Facet;
      typedef typename Tr::Edge Edge;
      typedef typename Tr::Cell_handle Cell_handle;
      typedef typename Tr::Vertex_handle Vertex_handle;
      typedef std::pair<Vertex_handle, Vertex_handle> EdgeVV;
      typedef typename Triangulation_mesher_level_traits_3<Tr>::Zone Zone;
      typedef std::vector<Edge> Edges;
      typedef std::vector<Facet> Facets;
      typedef typename C2T3::Facet_circulator Facet_circulator;
      typedef typename Tr::Finite_edges_iterator Finite_edges_iterator;

  protected:
    mutable std::set <std::pair <Vertex_handle, Vertex_handle> > bad_edges;
    mutable bool bad_edges_initialized;

    protected:
      // computes and return an ordered pair of Vertex
      EdgeVV make_edgevv(const Vertex_handle vh1,
                         const Vertex_handle vh2) const {
        if (vh1 < vh2) {
          return std::make_pair(vh1, vh2);
        }
        else {
          return std::make_pair(vh2, vh1);
        }
      }

      // computes and returns the Edge type object from the EdgeVV object
      // use tr.is_edge(...)
      Edge edgevv_to_edge(const EdgeVV& arete) const {
            Vertex_handle sommet1 = arete.first;
            Vertex_handle sommet2 = arete.second;
            Cell_handle c;
        int index1 = -1, index2 = -1;
        // Initialize the variables `index1` and `index2` to quiet a
        // warning. Anyway, the function `tr.is_edge()` that is called
        // after is really slow, so the initialization will be completely
        // negligible in running time.

        CGAL_assume_code(bool is_edge = )
        SMB::tr.is_edge(sommet1, sommet2, c, index1, index2);
        CGAL_assume(is_edge);

            return make_triple( c, index1, index2 );
      }

      // computes and returns the EdgeVV type object from the Edge object
      EdgeVV edge_to_edgevv(const Edge& arete) const {
        return make_edgevv(arete.first->vertex(arete.second),
                           arete.first->vertex(arete.third) );
      }



      // computes and returns the list of Edges in a given Facet side
      Edges edges_in_facet(const Facet& cote) const {

        Edges loe;
        Cell_handle c = cote.first;
        int i = cote.second;

        for (int j = 0; j < 3; j++) {
          for (int k = j + 1; k < 4; k++) {
            if ( (i != j) && (i != k) ){
              loe.push_back(make_triple(c, j, k));
            }
          }
        }
        return loe;
      }

    FT compute_distance_to_facet_center(const Facet& f,
                                        const Vertex_handle v) const {
      const Point& fcenter = f.first->get_facet_surface_center(f.second);
      const Point& vpoint = v->point();

      return SMB::tr.geom_traits().compute_squared_distance_3_object()(fcenter,
                                                                  vpoint );
    }


      Facet biggest_incident_facet_in_complex(const Edge& arete) const {
        // Find the first facet in the incident facets
        // of the edge which is in the Complex
        // use the list of incident facets in the complex
        Facet_circulator fcirc = SMB::c2t3.incident_facets(arete);
        Facet first_facet = *fcirc;
        Facet biggest_facet = first_facet;

        for (++fcirc;
             (*fcirc != first_facet) &&
               (*fcirc != SMB::mirror_facet(first_facet));
             ++fcirc) {
          Vertex_handle fev = edge_to_edgevv(arete).first;
          // is the current facet bigger than the current biggest one
          if ( compute_distance_to_facet_center(*fcirc, fev) >
               compute_distance_to_facet_center(biggest_facet,
                                                fev) ) {
            biggest_facet = *fcirc;
          }
          else { // @TODO: il ne faut pas aller voir des deux cotes: c'est
                 // le meme centre de facet!!!
            Facet autre_cote = SMB::mirror_facet(*fcirc);
            // is the current facet bigger than the current biggest one
            if ( compute_distance_to_facet_center(autre_cote, fev) >
                 compute_distance_to_facet_center(biggest_facet, fev) ) {
              biggest_facet = autre_cote;
            }
          }
        }
        return biggest_facet;
      }

      ///////////////////////
      // For before_insertion

      // Actions to perform on a facet inside the conflict zone
      void
      before_insertion_handle_facet_inside_conflict_zone (const Facet& f) {
        Facet cote = f;
        //        Facet autre_cote = SMB::mirror_facet(cote);

        if ( SMB::c2t3.face_status(cote) !=
             C2t3::NOT_IN_COMPLEX ) {

          Edges loe = edges_in_facet(cote);
          // foreach edge of cote
          for ( typename Edges::iterator eit = loe.begin();
                eit != loe.end();
                ++eit) {
            bad_edges.erase( edge_to_edgevv(*eit) );
          }
        }
      }

      // Action to perform on a facet on the boundary of the conflict zone
      void
      before_insertion_handle_facet_on_boundary_of_conflict_zone(const Facet& f)
      {
        // perform the same operations as for an internal facet
        before_insertion_handle_facet_inside_conflict_zone (f);
      }

    public:
      Surface_mesher_regular_edges_base(C2T3& c2t3,
                                        const Surface& surface,
                                        const SurfaceMeshTraits& mesh_traits,
                                        const Criteria& criteria)
        : SMB(c2t3, surface, mesh_traits, criteria),
          bad_edges_initialized(false)
    {
#ifdef CGAL_SURFACE_MESHER_DEBUG_CONSTRUCTORS
      Rcpp::Rcerr << "CONS: Surface_mesher_regular_edges_base";
      if(withBoundary)
        Rcpp::Rcerr << " (with boundaries)\n";
      else
        Rcpp::Rcerr << " (without boundary)\n";
#endif
    }

    // Initialization function
    void initialize_bad_edges() const {
#ifdef CGAL_SURFACE_MESHER_VERBOSE
      Rcpp::Rcerr << "\r             \rscanning edges ";
      if(withBoundary)
        Rcpp::Rcerr << "(boundaries allowed)";
      Rcpp::Rcerr << "...\n";
      int n = 0;
#endif
      for (Finite_edges_iterator eit = SMB::tr.finite_edges_begin(); eit !=
             SMB::tr.finite_edges_end(); ++eit) {
        if ( (SMB::c2t3.face_status(*eit)
              == C2t3::SINGULAR) ||
             ( (!withBoundary) &&
               (SMB::c2t3.face_status(*eit)
                == C2t3::BOUNDARY) ) ) {
          bad_edges.insert( edge_to_edgevv(*eit) );
#ifdef CGAL_SURFACE_MESHER_VERBOSE
          ++n;
#endif
        }
      }
      bad_edges_initialized = true;
#ifdef CGAL_SURFACE_MESHER_VERBOSE
        Rcpp::Rcerr << "   -> found " << n << " bad edges\n";
#endif
    }

    void scan_triangulation_impl() {
      SMB::scan_triangulation_impl();
#ifdef CGAL_SURFACE_MESHER_VERBOSE
      Rcpp::Rcerr << "scanning edges (lazy)" << std::endl;
#endif
    }

    // Tells whether there remain elements to refine
    bool no_longer_element_to_refine_impl() const {
      if(SMB::no_longer_element_to_refine_impl())
      {
        if( ! bad_edges_initialized )
          initialize_bad_edges();
        return bad_edges.empty();
      }
      return false;
    }

    // Returns the next element to refine
    Facet get_next_element_impl() {

      if (!SMB::no_longer_element_to_refine_impl()) {
        return SMB::get_next_element_impl();
      }
      else {
        CGAL_assertion(bad_edges_initialized);
        Edge first_bad_edge = edgevv_to_edge(*(bad_edges.begin()));
        return biggest_incident_facet_in_complex(first_bad_edge);
      }
    }

    void before_insertion_impl(const Facet& f, const Point& s,
                               Zone& zone) {
      if( bad_edges_initialized )
      {
        for (typename Zone::Facets_iterator fit =
               zone.internal_facets.begin();
             fit != zone.internal_facets.end();
             ++fit)
          before_insertion_handle_facet_inside_conflict_zone (*fit);

        for (typename Zone::Facets_iterator fit =
               zone.boundary_facets.begin(); fit !=
               zone.boundary_facets.end(); ++fit)
          before_insertion_handle_facet_on_boundary_of_conflict_zone (*fit);
      }
      SMB::before_insertion_impl(f, s, zone);
    }

    void after_insertion_impl(const Vertex_handle v) {
      SMB::after_insertion_impl(v);

      if( ! bad_edges_initialized )
        return;

      // search for incident facets around v
      Facets facets;
      SMB::tr.incident_facets (v, std::back_inserter(facets));

      // foreach f in star(v)
      for (typename Facets::iterator fit = facets.begin();
           fit != facets.end();
           ++fit) {
        Facet cote = *fit;
        Edges loe = edges_in_facet(cote);

        // foreach edge of cote
        for ( typename Edges::iterator eit = loe.begin();
              eit != loe.end();
              ++eit ) {
          // test if edge is in Complex
          if ( SMB::c2t3.face_status(*eit)
               != C2t3::NOT_IN_COMPLEX ) {
            // test if edge is not regular to store it as a "bad_edge"
            // e.g. more than or equal to 3 incident facets (SINGULAR)
            // or less than or equal to 1
            // (BOUNDARY only, because ISOLATED is NA)
            // This test is not efficient because
            // edges are tried to be inserted several times
            // TODO one day: test if the edge is still singular
            if ( (SMB::c2t3.face_status(*eit)
                  == C2t3::SINGULAR) ||
                 ( (!withBoundary) &&
                   (SMB::c2t3.face_status(*eit)
                    == C2t3::BOUNDARY) ) ) {
              bad_edges.insert( edge_to_edgevv(*eit) );
            }
            else {
              bad_edges.erase( edge_to_edgevv(*eit) ); // @TODO: pourquoi?!
            }
          }
        }
      }
    }

    std::string debug_info() const
    {
      std::stringstream s;
      s << SMB::debug_info() << ",";
      if(bad_edges_initialized)
        s << bad_edges.size();
      else
        s << "non manifold edges not initialized";
      return s.str();
    }

    std::string debug_info_header() const
    {
      std::stringstream s;
      s << SMB::debug_info_header() << "," << "#bad edges";
      return s.str();
    }
  };  // end Surface_mesher_regular_edges_base

  }  // end namespace Surface_mesher

}  // end namespace CGAL

#include <CGAL/enable_warnings.h>

#endif // CGAL_SURFACE_MESHER_REGULAR_EDGES_H

