// -*- tab-width: 2 -*-

// Copyright (c) 2006-2007  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
// You can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Licensees holding a valid commercial license may use this file in
// accordance with the commercial license agreement provided with the software.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0+
//
// Author(s)     : Pierre Alliez

///////////////////////////////////////////////////////////////////////////
//                                                                       //
//  Class: Enriched_polyhedron                                           //
//                                                                       //
///////////////////////////////////////////////////////////////////////////

#ifndef _POLYGON_MESH_
#define _POLYGON_MESH_

// CGAL stuff
#include <CGAL/Cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/HalfedgeDS_default.h>
#include <list>

// a refined facet with a normal and a tag
template <class Refs, class T, class P, class Norm>
class Enriched_facet : public CGAL::HalfedgeDS_face_base<Refs, T>
{
  // tag
  int m_tag; 

  // normal
  Norm m_normal;

public:

  // life cycle
  // no constructors to repeat, since only
  // default constructor mandatory

  Enriched_facet()
  {
  }

  // tag
  const int& tag() const { return m_tag; }
  int& tag() { return m_tag; }
	void tag(const int& i) { m_tag = i; }

  // normal
  typedef Norm Normal_3;
  Normal_3& normal() { return m_normal; }
  const Normal_3& normal() const { return m_normal; }
};

// a refined halfedge with a general tag and 
// a binary tag to indicate wether it belongs 
// to the control mesh or not
template <class Refs, class Tprev, class Tvertex, class Tface, class Norm>
class Enriched_halfedge : public CGAL::HalfedgeDS_halfedge_base<Refs,Tprev,Tvertex,Tface>
{
private:

  // general purpose tag
  int m_tag; 

  // option for edge superimposing
  bool m_control_edge; 
  bool m_sharp;

public:

  // life cycle
  Enriched_halfedge()
  {
    m_control_edge = true;
    m_sharp = false;
  }

  // tag
  const int& tag() const { return m_tag;  }
  int& tag() { return m_tag;  }
  void tag(const int& t)  { m_tag = t; }

  // control edge 
  bool& control_edge()  { return m_control_edge; }
  const bool& control_edge()  const { return m_control_edge; }

  // sharp
  bool& sharp()  { return m_sharp; }
  const bool& sharp()  const { return m_sharp; }
};



// a refined vertex with a normal and a tag
template <class Refs, class T, class P, class Norm>
class Enriched_vertex : public CGAL::HalfedgeDS_vertex_base<Refs, T, P>
{
  // tag
  int m_tag; 

  // normal
  Norm m_normal;

public:
  // life cycle
  Enriched_vertex()  {}
  // repeat mandatory constructors
  Enriched_vertex(const P& pt)
    : CGAL::HalfedgeDS_vertex_base<Refs, T, P>(pt)
  {
  }

  // normal
  typedef Norm Normal_3;
  Normal_3& normal() { return m_normal; }
  const Normal_3& normal() const { return m_normal; }

  // tag
  int& tag() {  return m_tag; }
  const int& tag() const {  return m_tag; }
  void tag(const int& t)  { m_tag = t; }
};

// A redefined items class for the Polyhedron_3 
// with a refined vertex class that contains a 
// member for the normal vector and a refined
// facet with a normal vector instead of the 
// plane equation (this is an alternative 
// solution instead of using 
// Polyhedron_traits_with_normals_3).

struct Enriched_items : public CGAL::Polyhedron_items_3
{
  // wrap vertex
  template<class Refs, class Traits> struct Vertex_wrapper
  {
    typedef typename Traits::Point_3 Point;
    typedef typename Traits::Vector_3 Normal;
    typedef Enriched_vertex<Refs,
    CGAL::Tag_true,
    Point,
    Normal> Vertex;
  };

  // wrap face
  template<class Refs, class Traits> struct Face_wrapper
  {
    typedef typename Traits::Point_3 Point;
    typedef typename Traits::Vector_3 Normal;
    typedef Enriched_facet<Refs,
    CGAL::Tag_true,
    Point,
    Normal> Face;
  };

  // wrap halfedge
  template<class Refs, class Traits> struct Halfedge_wrapper
  {
    typedef typename Traits::Vector_3 Normal;
    typedef Enriched_halfedge<Refs,
    CGAL::Tag_true,
    CGAL::Tag_true,
    CGAL::Tag_true,
    Normal> Halfedge;
  };
};

static const double PI = 3.1415926535897932384626;

// compute facet normal 
struct Facet_normal // (functor)
{
  template<class Facet> void operator()(Facet& f)
  {
    typename Facet::Normal_3 sum = CGAL::NULL_VECTOR;
    typename Facet::Halfedge_around_facet_circulator h = f.facet_begin();
    do
    {
      typename Facet::Normal_3 normal = CGAL::cross_product(h->next()->vertex()->point() - h->vertex()->point(), h->next()->next()->vertex()->point() - h->next()->vertex()->point());
      double sqnorm = normal * normal;
      if (sqnorm != 0)
        normal = normal / (float)std::sqrt(sqnorm);
      sum = sum + normal;
    } while (++h != f.facet_begin());
    float sqnorm = sum * sum;
    if (sqnorm != 0.0)
      f.normal() = sum / std::sqrt(sqnorm);
    else
    {
      f.normal() = CGAL::NULL_VECTOR;
      //      TRACE("degenerate face\n");
    }
  }
};

// compute vertex normal 
struct Vertex_normal // (functor)
{
  template<class Vertex> void operator()(Vertex& v)
  {
    typename Vertex::Normal_3 normal = CGAL::NULL_VECTOR;
    typename Vertex::Halfedge_around_vertex_const_circulator pHalfedge =
        v.vertex_begin();
    typename Vertex::Halfedge_around_vertex_const_circulator begin =
        pHalfedge;
    CGAL_For_all(pHalfedge,begin)
    if(!pHalfedge->is_border())
    normal = normal + pHalfedge->facet()->normal();
    float sqnorm = normal * normal;
    if (sqnorm != 0.0f)
      v.normal() = normal / (float)std::sqrt(sqnorm);
    else
      v.normal() = CGAL::NULL_VECTOR;
  }
};

//*********************************************************
template <class kernel,
          class items,
          template < class T, class I, class A>
          class HDS = CGAL::HalfedgeDS_default >
class Enriched_polyhedron : public CGAL::Polyhedron_3<kernel,items,HDS>
{
public :
  typedef typename kernel::FT FT;
  typedef typename kernel::Point_3 Point;
  typedef typename kernel::Vector_3 Vector;
  typedef typename kernel::Iso_cuboid_3 Iso_cuboid;
  typedef CGAL::Polyhedron_3<kernel,items,HDS> Base;
  
  typedef typename Base::Vertex_handle Vertex_handle;
  typedef typename Base::Vertex_iterator Vertex_iterator;
  typedef typename Base::Halfedge_handle Halfedge_handle;
  typedef typename Base::Halfedge_iterator Halfedge_iterator;
  typedef typename Base::Halfedge_around_facet_circulator Halfedge_around_facet_circulator;
  typedef typename Base::Halfedge_around_vertex_circulator Halfedge_around_vertex_circulator;
  typedef typename Base::Edge_iterator Edge_iterator;
  typedef typename Base::Facet_iterator Facet_iterator;
  typedef typename Base::Facet_handle Facet_handle;
  typedef typename Base::Facet Facet;
  
  using Base::vertices_begin;
  using Base::vertices_end;
  using Base::edges_begin;
  using Base::edges_end;
  using Base::halfedges_begin;
  using Base::halfedges_end;
  using Base::facets_begin;
  using Base::facets_end;
  using Base::size_of_halfedges;
  using Base::size_of_facets;
  using Base::size_of_vertices;
private :

  // bounding box
  Iso_cuboid m_bbox;

  // type
  bool m_pure_quad;
  bool m_pure_triangle;

public :
  enum Vertex_type { SMOOTH,           // 0 sharp edge
                     DART,             // 1 
                     CREASE_REGULAR,   // 2 - with two non-sharp edges on each side
                     CREASE_IRREGULAR, // 2
                     CORNER };         // 3 and more

public :

  // life cycle
  Enriched_polyhedron() 
  {
    m_pure_quad = false;
    m_pure_triangle = false;
  }
  virtual ~Enriched_polyhedron() 
  {
  }

  // type
  bool is_pure_triangle() { return m_pure_triangle; }
  bool is_pure_quad() { return m_pure_quad; }

  // normals (per facet, then per vertex)
  void compute_normals_per_facet()
  {
    std::for_each(facets_begin(),facets_end(),::Facet_normal());
  }
  void compute_normals_per_vertex()
  {
    std::for_each(vertices_begin(),vertices_end(),::Vertex_normal());
  }
  void compute_normals()
  {
    compute_normals_per_facet();
    compute_normals_per_vertex();
  }

  // bounding box
  Iso_cuboid& bbox() { return m_bbox; }
  const Iso_cuboid bbox() const { return m_bbox; }

  // compute bounding box
  void compute_bounding_box()
  {
    CGAL_assertion(size_of_vertices() != 0);

    FT xmin,xmax,ymin,ymax,zmin,zmax;
    Vertex_iterator pVertex = vertices_begin();
    xmin = xmax = pVertex->point().x();
    ymin = ymax = pVertex->point().y();
    zmin = zmax = pVertex->point().z();
    for(;
        pVertex !=  vertices_end();
        pVertex++)
    {
      const Point& p = pVertex->point();

      xmin =  std::min(xmin,p.x());
      ymin =  std::min(ymin,p.y());
      zmin =  std::min(zmin,p.z());

      xmax =  std::max(xmax,p.x());
      ymax =  std::max(ymax,p.y());
      zmax =  std::max(zmax,p.z());
    }
    m_bbox = Iso_cuboid(xmin,ymin,zmin,
                        xmax,ymax,zmax);
  }

  // bounding box
  FT xmin() { return m_bbox.xmin(); }
  FT xmax() { return m_bbox.xmax(); }
  FT ymin() { return m_bbox.ymin(); }
  FT ymax() { return m_bbox.ymax(); }
  FT zmin() { return m_bbox.zmin(); }
  FT zmax() { return m_bbox.zmax(); }

  Point center()
  {
    FT cx = (FT)0.5 * (xmin() + xmax());
    FT cy = (FT)0.5 * (ymin() + ymax());
    FT cz = (FT)0.5 * (zmin() + zmax());
    return Point(cx,cy,cz);
  }

  FT size()
  {
    FT dx = xmax() - xmin();
    FT dy = ymax() - ymin();
    FT dz = zmax() - zmin();
    return std::max(dx,std::max(dy,dz));
  }

  // degree of a face
  static unsigned int degree(Facet_handle pFace)
  {
    return CGAL::circulator_size(pFace->facet_begin());    
  }

  // valence of a vertex
  static unsigned int valence(Vertex_handle pVertex)
  {
    return CGAL::circulator_size(pVertex->vertex_begin());
  }

  // check wether a vertex is on a boundary or not
  static bool is_border(Vertex_handle pVertex)
  {
    Halfedge_around_vertex_circulator pHalfEdge = pVertex->vertex_begin();
    if(pHalfEdge == NULL) // isolated vertex
      return true;
    Halfedge_around_vertex_circulator d = pHalfEdge;
    CGAL_For_all(pHalfEdge,d)
      if(pHalfEdge->is_border())
        return true;
    return false;
  }

  // get any border halfedge attached to a vertex
  Halfedge_handle get_border_halfedge(Vertex_handle pVertex)
  {
    Halfedge_around_vertex_circulator pHalfEdge = pVertex->vertex_begin();
    Halfedge_around_vertex_circulator d = pHalfEdge;
    CGAL_For_all(pHalfEdge,d)
      if(pHalfEdge->is_border())
        return pHalfEdge;
    return NULL;
  }

  // tag all vertices
  void tag_vertices(const int tag)
  {
    for(Vertex_iterator vit = vertices_begin(),
                        end = vertices_end();
        vit != end;
        ++vit)
      vit->tag(tag);
  }

  // tag all halfedges
  void tag_halfedges(const int tag)
  {
    for(Halfedge_iterator pHalfedge = halfedges_begin();
        pHalfedge != halfedges_end();
        pHalfedge++)
      pHalfedge->tag(tag);
  }

  // tag all facets
  void tag_facets(const int tag)
  {
    for(Facet_iterator pFacet = facets_begin();
        pFacet  != facets_end();
        pFacet++)
      pFacet->tag() = tag;
  }

  // set index for all vertices
  void set_index_vertices()
  {
    int index = 0;
    for(Vertex_iterator pVertex = vertices_begin();
        pVertex != vertices_end();
        pVertex++)
      pVertex->tag(index++);
  }

  // set index for all edges
  void set_index_edges()
  {
    int index = 0;
    for(Edge_iterator he = edges_begin();
        he != edges_end();
        he++)
    {
      he->tag(index);
      he->opposite()->tag(index);
      index++;
    }
  }

  // is pure degree ?
  bool is_pure_degree(unsigned int d)
  {
    for(Facet_iterator pFace  = facets_begin();
        pFace != facets_end();
        pFace++)
      if(degree(pFace) != d)
        return false;
    return true;
  }

  // compute type
  void compute_type()
  {
    m_pure_quad = is_pure_degree(4);
    m_pure_triangle = is_pure_degree(3);
  }

  // compute facet center
  void compute_facet_center(Facet_handle pFace,
                            Point& center)
  {
    Halfedge_around_facet_circulator pHalfEdge = pFace->facet_begin();
    Halfedge_around_facet_circulator end = pHalfEdge;
    Vector vec(0.0,0.0,0.0);
    int degree = 0;
    CGAL_For_all(pHalfEdge,end)
    {
      vec = vec + (pHalfEdge->vertex()->point()-CGAL::ORIGIN);
      degree++;
    }
    center = CGAL::ORIGIN + (vec/FT(degree));
  }

  bool is_sharp(Halfedge_handle he,
                const double angle_sharp)
  {
    Facet_handle f1 = he->facet();
    Facet_handle f2 = he->opposite()->facet();
    if(f1 == Facet_handle() || f2 == Facet_handle() )
      return false;
    const Vector& n1 = f1->normal();
    const Vector& n2 = f2->normal();
    if(angle_deg(n1,n2) > angle_sharp)
      return true;
    else
      return false;
  }

  Vertex_type type(Vertex_handle v)
  {
    unsigned int nb = nb_sharp_edges(v);
    switch(nb)
    {
      case 0:
        return SMOOTH;
      case 1:
        return DART;
      case 2: // crease vertex - may be regular or not
        return crease_type(v);
      default: // 3 and more
        return CORNER;
    }
  }

  // regular crease vertex must have valence 6, with
  // exactly two smooth edges on each side.
  Vertex_type crease_type(Vertex_handle v)
  {
    if(valence(v) != 6)
      return CREASE_IRREGULAR;

    // valence = 6 - let us check regularity
    
    // pick first sharp edge
    Halfedge_around_vertex_circulator he = v->vertex_begin();
    Halfedge_around_vertex_circulator end = he;
    CGAL_For_all(he,end)
      if(he->sharp())
        break;

    // next two must be smooth
    for(int i=0;i<2;i++)
      if(++he->sharp())
        return CREASE_IRREGULAR;

    // next one must be sharp
    if(!++he->sharp())
      return CREASE_IRREGULAR;

    // next two must be smooth
    for(int i=0;i<2;i++)
      if(++he->sharp())
        return CREASE_IRREGULAR;

    return CREASE_REGULAR;
  }

  // return true if succeds
  void incident_points_on_crease(Vertex_handle v,
                                 Point& a,
                                 Point& b)
  {
#ifdef _DEBUG
    Vertex_type vertex_type = type(v,angle_sharp);
    ASSERT(vertex_type == CREASE_IRREGULAR || vertex_type == CREASE_REGULAR);
#endif // _DEBUG

    // pick first sharp edge
    Halfedge_around_vertex_circulator he = v->vertex_begin();
    Halfedge_around_vertex_circulator end = he;
    CGAL_For_all(he,end)
      if(he->sharp())
        break;
    a = he->opposite()->vertex()->point();

    // pick next sharp edge
    he++;
    CGAL_For_all(he,end)
      if(he->sharp())
        break;
    b = he->opposite()->vertex()->point();
  }

  // return nb of sharp edges incident to v
  unsigned int nb_sharp_edges(Vertex_handle v) const
  {
    Halfedge_around_vertex_circulator he = v->vertex_begin();
    Halfedge_around_vertex_circulator end = he;
    unsigned int nb_sharp_edges = 0;
    CGAL_For_all(he,end)
      if(he->sharp())
        nb_sharp_edges++;
    return nb_sharp_edges;
  }

  // Angle between two vectors (in degrees)
  // we use this formula
  // uv = |u||v| cos(u,v)
  // u  ^ v  = w
  // |w| = |u||v| |sin(u,v)|
  //**************************************************
  static double angle_deg(const Vector &u,
                          const Vector &v)
  {
    static const double conv = 1.0/PI*180.0;
    return conv * angle_rad(u,v);
  }

  static FT len(const Vector &v) 
  { 
    return (FT)std::sqrt(CGAL_NTS to_double(v*v));
  }

  // Angle between two vectors (in rad)
  // uv = |u||v| cos(u,v)
  // u  ^ v  = w
  // |w| = |u||v| |sin(u,v)|
  //**************************************************
  static double angle_rad(const Vector &u,
                          const Vector &v)
  {
    // check
    double product = len(u)*len(v);
    if(product == 0)
      return 0.0;

    // cosine
    double dot = (u*v);
    double cosine = dot / product;

    // sine
    Vector w = CGAL::cross_product(u,v);
    double AbsSine = len(w) / product;

    if(cosine >= 0)
      return std::asin(fix_sine(AbsSine));
    else
      return PI-std::asin(fix_sine(AbsSine));
  }

  //**********************************************
  // fix sine
  //**********************************************
  static double fix_sine(double sine)
  {
    if(sine >= 1)
      return 1;
    else
      if(sine <= -1)
        return -1;
      else
        return sine;
  }

  unsigned int tag_sharp_edges(const double angle_sharp)
  {
    unsigned int nb = 0;
    for(Halfedge_iterator he = edges_begin();
        he != edges_end();
        he++)
    {
      const bool tag = is_sharp(he,angle_sharp);
      he->sharp() = tag;
      he->opposite()->sharp() = tag;
      nb += tag ? 1 : 0;
    }
    return nb;
  }


  // count #boundaries
  unsigned int nb_boundaries()
  {
    unsigned int nb = 0;
    tag_halfedges(0);
    for(Halfedge_iterator he = halfedges_begin();
        he != halfedges_end();
        he++)
    {
      if(he->is_border() && he->tag() == 0)
      {
        nb++;
        Halfedge_handle curr = he;
        do
        {
          curr  = curr->next();
          curr->tag(1);
        }
        while(curr != he);
      }
    }
    return nb;
  }

  // tag component 
  void tag_component(Facet_handle pSeedFacet,
                     const int tag_free,
                     const int tag_done)
{
    pSeedFacet->tag() = tag_done;
    std::list<Facet_handle> facets;
    facets.push_front(pSeedFacet);
    while(!facets.empty())
    {
      Facet_handle pFacet = facets.front();
      facets.pop_front();
      pFacet->tag() = tag_done;
      Halfedge_around_facet_circulator pHalfedge = pFacet->facet_begin();
      Halfedge_around_facet_circulator end = pHalfedge;
      CGAL_For_all(pHalfedge,end)
      {
        Facet_handle pNFacet = pHalfedge->opposite()->facet();
        if(pNFacet != NULL && pNFacet->tag() == tag_free)
        {
          facets.push_front(pNFacet);
          pNFacet->tag() = tag_done;
        }
      }
    }
}

  // count #components
  unsigned int nb_components()
  {
    unsigned int nb = 0;
    tag_facets(0);
    for(Facet_iterator pFacet = facets_begin();
        pFacet != facets_end();
        pFacet++)
    {
      if(pFacet->tag() == 0)
      {
        nb++;
        tag_component(pFacet,0,1);
      }
    }
    return nb;
  }

  // compute the genus
  // V - E + F + B = 2 (C - G)
  // C -> #connected components
  // G : genus
  // B : #boundaries
  int genus()
  {
    int c = nb_components();
    int b = nb_boundaries();
    int v = size_of_vertices();
    int e = size_of_halfedges()/2;
    int f = size_of_facets();
    return genus(c,v,f,e,b);
  }
  int genus(int c,
            int v,
            int f,
            int e,
            int b)
  {
    return (2*c+e-b-f-v)/2;
  }

};


#endif // _POLYGON_MESH_
