// Copyright (c) 2014
// INRIA Saclay-Ile de France (France)
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/NewKernel_d/include/CGAL/NewKernel_d/functor_tags.h $
// $Id: include/CGAL/NewKernel_d/functor_tags.h 08b27d3db14 $
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Marc Glisse

#ifndef CGAL_FUNCTOR_TAGS_H
#define CGAL_FUNCTOR_TAGS_H
#include <CGAL/tags.h> // for Null_tag
#include <CGAL/NewKernel_d/utils.h>
#include <type_traits>
#include <utility>
#include <boost/type_traits.hpp>
#include <boost/mpl/has_xxx.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/pop_front.hpp>
namespace CGAL {

  // Find a better place for this later

  template <class K, class T, class=void> struct Get_type
    : K::template Type<T> {};
  template <class K, class F, class O=void, class=void> struct Get_functor
    : K::template Functor<F, O> {};
  template <class K, class T> using Type = typename Get_type<K, T>::type;
  template <class K, class T> using Functor = typename Get_functor<K, T>::type;

  class Null_type {~Null_type();}; // no such object should be created

        // To construct iterators
        struct Begin_tag {};
        struct End_tag {};

        // Functor category
        struct Predicate_tag {};
        struct Construct_tag {};
        struct Construct_iterator_tag {};
        struct Compute_tag {};
        struct Misc_tag {};

        struct No_filter_tag {};

        template<class>struct Construct_ttag {};
        template<class>struct Convert_ttag {};

        template <class K, class F, class=void, class=void> struct Get_functor_category { typedef Misc_tag type; };
        template<class Tg, class Obj, class Base> struct Typedef_tag_type;
        //template<class Kernel, class Tg> struct Read_tag_type {};

        template<class Kernel, class Tg>
        struct Provides_type
          : Has_type_different_from<Get_type<Kernel, Tg>, Null_type> {};

        template<class Kernel, class Tg, class O=void>
        struct Provides_functor
          : Has_type_different_from<Get_functor<Kernel, Tg, O>, Null_functor> {};

        template<class K, class List, bool=boost::mpl::empty<List>::type::value>
        struct Provides_functors : std::bool_constant<
                                   Provides_functor<K, typename boost::mpl::front<List>::type>::value &&
                                   Provides_functors<K, typename boost::mpl::pop_front<List>::type>::value > {};
        template<class K, class List>
        struct Provides_functors<K, List, true> : std::true_type {};

        template<class K, class List, bool=boost::mpl::empty<List>::type::value>
        struct Provides_types : std::bool_constant<
                                   Provides_type<K, typename boost::mpl::front<List>::type>::value &&
                                   Provides_types<K, typename boost::mpl::pop_front<List>::type>::value > {};
        template<class K, class List>
        struct Provides_types<K, List, true> : std::true_type {};

        namespace internal { BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(has_Type,template Type<Null_tag>,false) }
        template<class Kernel, class Tg,
          bool = internal::has_Type<Kernel>::value /* false */>
        struct Provides_type_i : std::false_type {};
        template<class Kernel, class Tg>
        struct Provides_type_i <Kernel, Tg, true>
          : Has_type_different_from<typename Kernel::template Type<Tg>, Null_type> {};

        //// This version does not like Functor<T,bool=false>
        //namespace internal { BOOST_MPL_HAS_XXX_TEMPLATE_NAMED_DEF(has_Functor,Functor,false) }
        // This version lets us use non-type template parameters, but fails with older EDG-based compilers (Intel 14).
        namespace internal { BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(has_Functor,template Functor<Null_tag>,false) }

        template<class Kernel, class Tg, class O=void,
          bool = internal::has_Functor<Kernel>::value /* false */>
        struct Provides_functor_i : std::false_type {};
        template<class Kernel, class Tg, class O>
        struct Provides_functor_i <Kernel, Tg, O, true>
          : Has_type_different_from<typename Kernel::template Functor<Tg, O>, Null_functor> {};

        // TODO: Refine this a bit.
        template <class K, class T, class D=void,
                  //bool=Provides_functor<K,T>::value,
                  //bool=Provides_functor_i<K,T>::value,
                  bool = internal::has_Functor<K>::value
                  >
        struct Inherit_functor : K::template Functor<T> {};
        template <class K, class T, class D>
        struct Inherit_functor <K, T, D, false> {};

        template <class K, class T, bool=internal::has_Type<K>::value>
        struct Inherit_type : K::template Type<T> {};
        template <class K, class T>
        struct Inherit_type <K, T, false> {};

        struct Number_tag {};
        struct Discrete_tag {};
        struct Object_tag {};
        template <class K, class T, class=void> struct Get_type_category {
          // The lazy kernel uses it too eagerly,
          // so it currently needs a default.
          typedef Null_tag type;
        };

#define CGAL_DECL_OBJ_(X,Y) \
  template<class Obj,class Base> \
  struct Typedef_tag_type<X##_tag, Obj, Base> : Base { typedef Obj X; }; \
  template<class K, class D> \
  struct Get_type_category <K, X##_tag, D> { typedef Y##_tag type; }
#define CGAL_DECL_OBJ(X,Y) struct X##_tag {}; \
  CGAL_DECL_OBJ_(X,Y)

  //namespace has_object { BOOST_MPL_HAS_XXX_TRAIT_DEF(X) }
  //template<class Kernel>
  //struct Provides_tag_type<Kernel, X##_tag> : has_object::has_##X<Kernel> {};
  //template<class Kernel>
  //struct Read_tag_type<Kernel, X##_tag> { typedef typename Kernel::X type; }

        // Not exactly objects, but the extras can't hurt.
        CGAL_DECL_OBJ(FT, Number);
        CGAL_DECL_OBJ(RT, Number);

        CGAL_DECL_OBJ(Bool, Discrete); // Boolean_tag is already taken, and is a template :-(
        CGAL_DECL_OBJ(Comparison_result, Discrete);
        CGAL_DECL_OBJ(Sign, Discrete);
        CGAL_DECL_OBJ(Orientation, Discrete); // Note: duplicate with the functor tag!
        CGAL_DECL_OBJ(Oriented_side, Discrete);
        CGAL_DECL_OBJ(Bounded_side, Discrete);
        CGAL_DECL_OBJ(Angle, Discrete);
        CGAL_DECL_OBJ(Flat_orientation, Discrete);

        CGAL_DECL_OBJ(Vector, Object);
        CGAL_DECL_OBJ(Point, Object);
        CGAL_DECL_OBJ(Segment, Object);
        CGAL_DECL_OBJ(Sphere, Object);
        CGAL_DECL_OBJ(Line, Object);
        CGAL_DECL_OBJ(Direction, Object);
        CGAL_DECL_OBJ(Hyperplane, Object);
        CGAL_DECL_OBJ(Ray, Object);
        CGAL_DECL_OBJ(Iso_box, Object);
        CGAL_DECL_OBJ(Aff_transformation, Object);
        CGAL_DECL_OBJ(Weighted_point, Object);
#undef CGAL_DECL_OBJ_
#undef CGAL_DECL_OBJ

// Intel fails with those, and they are not so useful.
//        CGAL_KD_DEFAULT_TYPE(RT_tag,(typename Get_type<K, FT_tag>::type),(),());
//        CGAL_KD_DEFAULT_TYPE(FT_tag,(CGAL::Quotient<typename Get_type<K, RT_tag>::type>),(),());

#define CGAL_SMURF2(A,B) CGAL_KD_DEFAULT_TYPE(A##_tag,(typename Same_uncertainty_nt<B, typename Get_type<K,RT_tag>::type>::type),(RT_tag),())
#define CGAL_SMURF1(A) CGAL_SMURF2(A,CGAL::A)
        CGAL_SMURF2(Bool, bool);
        CGAL_SMURF1(Sign);
        CGAL_SMURF1(Comparison_result);
        CGAL_SMURF1(Orientation);
        CGAL_SMURF1(Oriented_side);
        CGAL_SMURF1(Bounded_side);
        CGAL_SMURF1(Angle);
#undef CGAL_SMURF1
#undef CGAL_SMURF2

        // TODO: replace with Get_type_category
        template<class> struct is_NT_tag { enum { value = false }; };
        template<> struct is_NT_tag<FT_tag> { enum { value = true }; };
        template<> struct is_NT_tag<RT_tag> { enum { value = true }; };

        template<class> struct iterator_tag_traits {
          enum { is_iterator = false, has_nth_element = false };
          typedef Null_tag value_tag;
        };

        template<class>struct map_result_tag{typedef Null_type type;};
        template<class T>struct map_result_tag<Construct_ttag<T> >{typedef T type;};

#define CGAL_DECL_COMPUTE(X) struct X##_tag {}; \
        template<>struct map_result_tag<X##_tag>{typedef FT_tag type;}; \
        template<class A,class B,class C>struct Get_functor_category<A,X##_tag,B,C>{typedef Compute_tag type;}
        CGAL_DECL_COMPUTE(Compute_point_cartesian_coordinate);
        CGAL_DECL_COMPUTE(Compute_vector_cartesian_coordinate);
        CGAL_DECL_COMPUTE(Compute_homogeneous_coordinate);
        CGAL_DECL_COMPUTE(Squared_distance);
        CGAL_DECL_COMPUTE(Squared_distance_to_origin);
        CGAL_DECL_COMPUTE(Squared_length);
        CGAL_DECL_COMPUTE(Squared_radius);
        CGAL_DECL_COMPUTE(Squared_circumradius);
        CGAL_DECL_COMPUTE(Scalar_product);
        CGAL_DECL_COMPUTE(Hyperplane_translation);
        CGAL_DECL_COMPUTE(Value_at);
        CGAL_DECL_COMPUTE(Point_weight);
        CGAL_DECL_COMPUTE(Power_distance);
        CGAL_DECL_COMPUTE(Power_distance_to_point);
#undef CGAL_DECL_COMPUTE

#define CGAL_DECL_ITER_OBJ(X,Y,Z,C) struct X##_tag {}; \
  template<>struct iterator_tag_traits<X##_tag> { \
    enum { is_iterator = true, has_nth_element = true }; \
    typedef Y##_tag value_tag; \
    typedef Z##_tag nth_element; \
    typedef C##_tag container; \
  }; \
  template<class Obj,class Base> \
  struct Typedef_tag_type<X##_tag, Obj, Base> : Base { typedef Obj X; }

  //namespace has_object { BOOST_MPL_HAS_XXX_TRAIT_DEF(X) }
  //template<class Kernel>
  //struct Provides_tag_type<Kernel, X##_tag> : has_object::has_##X<Kernel> {};
  //template<class Kernel>
  //struct Read_tag_type<Kernel, X##_tag> { typedef typename Kernel::X type; }

        CGAL_DECL_ITER_OBJ(Vector_cartesian_const_iterator, FT, Compute_vector_cartesian_coordinate, Vector);
        CGAL_DECL_ITER_OBJ(Point_cartesian_const_iterator, FT, Compute_point_cartesian_coordinate, Point);
#undef CGAL_DECL_ITER_OBJ

        template<class A,class T,class B,class C>struct Get_functor_category<A,Construct_ttag<T>,B,C> :
          std::conditional<iterator_tag_traits<T>::is_iterator,
                           Construct_iterator_tag,
                           Construct_tag> {};

        // Really?
        template<class A,class T,class B,class C>struct Get_functor_category<A,Convert_ttag<T>,B,C>{typedef Misc_tag type;};

#define CGAL_DECL_CONSTRUCT(X,Y) struct X##_tag {}; \
        template<>struct map_result_tag<X##_tag>{typedef Y##_tag type;}; \
        template<class A,class B,class C>struct Get_functor_category<A,X##_tag,B,C>{typedef Construct_tag type;}
        CGAL_DECL_CONSTRUCT(Midpoint,Point);
        CGAL_DECL_CONSTRUCT(Center_of_sphere,Point);
        CGAL_DECL_CONSTRUCT(Point_of_sphere,Point);
        CGAL_DECL_CONSTRUCT(Segment_extremity,Point);
        CGAL_DECL_CONSTRUCT(Sum_of_vectors,Vector);
        CGAL_DECL_CONSTRUCT(Difference_of_vectors,Vector);
        CGAL_DECL_CONSTRUCT(Opposite_vector,Vector);
        CGAL_DECL_CONSTRUCT(Scaled_vector,Vector);
        CGAL_DECL_CONSTRUCT(Orthogonal_vector,Vector);
        CGAL_DECL_CONSTRUCT(Difference_of_points,Vector);
        CGAL_DECL_CONSTRUCT(Translated_point,Point);
        CGAL_DECL_CONSTRUCT(Point_to_vector,Vector);
        CGAL_DECL_CONSTRUCT(Vector_to_point,Point);
        CGAL_DECL_CONSTRUCT(Construct_min_vertex,Point);
        CGAL_DECL_CONSTRUCT(Construct_max_vertex,Point);
        CGAL_DECL_CONSTRUCT(Construct_circumcenter,Point);
        CGAL_DECL_CONSTRUCT(Point_drop_weight,Point);
        CGAL_DECL_CONSTRUCT(Power_center,Weighted_point);
#undef CGAL_DECL_CONSTRUCT
#if 0
#define CGAL_DECL_ITER_CONSTRUCT(X,Y) struct X##_tag {}; \
        template<>struct map_result_tag<X##_tag>{typedef Y##_tag type;}; \
        template<>struct map_functor_type<X##_tag>{typedef Construct_iterator_tag type;}
        CGAL_DECL_ITER_CONSTRUCT(Construct_point_cartesian_const_iterator,Point_cartesian_const_iterator);
        CGAL_DECL_ITER_CONSTRUCT(Construct_vector_cartesian_const_iterator,Vector_cartesian_const_iterator);
#undef CGAL_DECL_ITER_CONSTRUCT
#endif

        //FIXME: choose a convention: prefix with Predicate_ ?
#define CGAL_DECL_PREDICATE_(X) \
        template<class A,class B,class C>struct Get_functor_category<A,X##_tag,B,C>{typedef Predicate_tag type;}
#define CGAL_DECL_PREDICATE(X) struct X##_tag {}; \
        CGAL_DECL_PREDICATE_(X)
        CGAL_DECL_PREDICATE(Less_point_cartesian_coordinate);
        CGAL_DECL_PREDICATE(Compare_point_cartesian_coordinate);
        CGAL_DECL_PREDICATE(Compare_distance);
        CGAL_DECL_PREDICATE(Compare_lexicographically);
        CGAL_DECL_PREDICATE(Compare_squared_distance);
        CGAL_DECL_PREDICATE(Less_lexicographically);
        CGAL_DECL_PREDICATE(Less_or_equal_lexicographically);
        CGAL_DECL_PREDICATE(Equal_points);
        CGAL_DECL_PREDICATE(Has_on_positive_side);
        CGAL_DECL_PREDICATE_(Orientation); // duplicate with the type
        CGAL_DECL_PREDICATE_(Oriented_side); // duplicate with the type
        CGAL_DECL_PREDICATE(Orientation_of_points);
        CGAL_DECL_PREDICATE(Orientation_of_vectors);
        CGAL_DECL_PREDICATE(Side_of_oriented_sphere);
        CGAL_DECL_PREDICATE(Side_of_bounded_sphere);
        CGAL_DECL_PREDICATE(Side_of_bounded_circumsphere);
        CGAL_DECL_PREDICATE(Contained_in_affine_hull);
        CGAL_DECL_PREDICATE(In_flat_orientation);
        CGAL_DECL_PREDICATE(In_flat_side_of_oriented_sphere);
        CGAL_DECL_PREDICATE(Construct_flat_orientation); // Making it a predicate is a questionable choice, it should be possible to let it be a construction for some implementations. Not sure how to do that... TODO
        CGAL_DECL_PREDICATE(Linear_rank);
        CGAL_DECL_PREDICATE(Affine_rank);
        CGAL_DECL_PREDICATE(Linearly_independent);
        CGAL_DECL_PREDICATE(Affinely_independent);
        CGAL_DECL_PREDICATE(Contained_in_linear_hull);
        CGAL_DECL_PREDICATE(Contained_in_simplex);
        CGAL_DECL_PREDICATE(Power_side_of_power_sphere_raw);
        CGAL_DECL_PREDICATE(Power_side_of_power_sphere);
        CGAL_DECL_PREDICATE(Power_side_of_bounded_power_circumsphere);
        CGAL_DECL_PREDICATE(In_flat_power_side_of_power_sphere_raw);
        CGAL_DECL_PREDICATE(In_flat_power_side_of_power_sphere);
#undef CGAL_DECL_PREDICATE

#define CGAL_DECL_MISC(X) struct X##_tag {}; \
        template<class A,class B,class C>struct Get_functor_category<A,X##_tag,B,C>{typedef Misc_tag type;}
        //TODO: split into _begin and _end ?
        //CGAL_DECL_MISC(Construct_point_cartesian_const_iterator);
        //CGAL_DECL_MISC(Construct_vector_cartesian_const_iterator);
        CGAL_DECL_MISC(Point_dimension);
        CGAL_DECL_MISC(Vector_dimension);
        CGAL_DECL_MISC(Construct_bbox);
        CGAL_DECL_MISC(Linear_base); // Find a more appropriate category?
#undef CGAL_DECL_MISC


        // Properties for LA
        struct Has_extra_dimension_tag {};
        struct Has_vector_plus_minus_tag {};
        struct Has_vector_scalar_ops_tag {};
        struct Has_dot_product_tag {};
        struct Has_determinant_of_vectors_tag {};
        struct Has_determinant_of_points_tag {};
        struct Has_determinant_of_iterator_to_vectors_tag {};
        struct Has_determinant_of_iterator_to_points_tag {};
        struct Has_determinant_of_vectors_omit_last_tag {};
        struct Stores_squared_norm_tag {};

        template<class> struct Preserved_by_non_linear_extra_coordinate
          : std::false_type {};
        template<> struct Preserved_by_non_linear_extra_coordinate
          <Has_extra_dimension_tag> : std::true_type {};
        template<> struct Preserved_by_non_linear_extra_coordinate
          <Has_determinant_of_vectors_tag> : std::true_type {};
        template<> struct Preserved_by_non_linear_extra_coordinate
          <Has_determinant_of_points_tag> : std::true_type {};
        template<> struct Preserved_by_non_linear_extra_coordinate
          <Has_determinant_of_iterator_to_vectors_tag> : std::true_type {};
        template<> struct Preserved_by_non_linear_extra_coordinate
          <Has_determinant_of_iterator_to_points_tag> : std::true_type {};
        template<> struct Preserved_by_non_linear_extra_coordinate
          <Has_determinant_of_vectors_omit_last_tag> : std::true_type {};

        // Kernel properties
        struct Point_stores_squared_distance_to_origin_tag {};

}
#endif // CGAL_FUNCTOR_TAGS_H
