// Copyright (c) 2005,2006  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 Lesser 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: https://github.com/CGAL/cgal/blob/releases/CGAL-4.14.1/Filtered_kernel/include/CGAL/Lazy_kernel.h $
// $Id: Lazy_kernel.h 449e649 %aI Andreas Fabri
// SPDX-License-Identifier: LGPL-3.0+
//
//
// Author(s)     : Andreas Fabri, Sylvain Pion

#ifndef CGAL_LAZY_KERNEL_H
#define CGAL_LAZY_KERNEL_H

#include <CGAL/basic.h>
//#include <CGAL/Filtered_predicate.h>
#include <CGAL/Static_filtered_predicate.h>
#include <CGAL/Filtered_kernel.h>
#include <CGAL/Cartesian_converter.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Interval_nt.h>
#include <CGAL/Kernel/Type_equality_wrapper.h>
#include <CGAL/Filtered_kernel/Cartesian_coordinate_iterator_2.h>
#include <CGAL/Filtered_kernel/Cartesian_coordinate_iterator_3.h>
#include <CGAL/Lazy.h>
#include <CGAL/internal/Static_filters/tools.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <boost/none.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/or.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include <boost/type_traits/remove_cv.hpp>

#if defined(BOOST_MSVC)
#  pragma warning(push)
#  pragma warning(disable:4348) // redefinition of default parameter in nested template class
#endif

namespace CGAL {

namespace internal {

// SFINAE way to detect result_type typedefs.
template<typename T>
class Has_result_type_helper
{
  typedef char one;
  typedef struct { char arr[2]; } two;

  template<typename _Up>
  struct Wrapper {};

  template<typename U>
  static one test(Wrapper<typename U::result_type>*);

  template<typename U>
  static two test(...);

public:
  static const bool value = sizeof(test<T>(0)) == 1;
};

template<typename T>
struct Has_result_type 
  : boost::integral_constant< bool, 
                              Has_result_type_helper< typename boost::remove_cv<T>::type>::value>
{};

template <typename T>
struct Get_result_type {
  typedef typename T::result_type type;
};

template <typename T>
struct Lazy_result_type 
  : boost::mpl::eval_if< Has_result_type<T>, 
                         Get_result_type<T>, 
                         boost::mpl::identity<void> > 
{};

class Enum_holder {
protected:
  enum { NONE, NT, VARIANT, OBJECT, BBOX };
};

} // internal

// Exact_kernel = exact kernel that will be made lazy
// Kernel = lazy kernel

// the Generic base simplies applies the generic magic functor stupidly.
// then the real base fixes up a few special cases.
template < typename EK_, typename AK_, typename E2A_, typename Kernel_ >
class Lazy_kernel_generic_base : protected internal::Enum_holder
  // : public Filtered_kernel_base<EK_>
    // TODO : Static_filters_base too ?  Check performance
{
public:

  typedef AK_   Approximate_kernel;
  typedef EK_   Exact_kernel;
  typedef E2A_  E2A;
  typedef Kernel_ Kernel;

  typedef Lazy_kernel_generic_base<EK_, AK_, E2A_, Kernel_> Self;
  
  // synonym identical to Filtered_kernel
  typedef AK_   FK;

  // Note: Approx_converter and Exact_converter are defined in <CGAL/Lazy.h>
  typedef Approx_converter<Kernel, Approximate_kernel>   C2F;
  typedef Exact_converter<Kernel, Exact_kernel>    C2E;

  template < typename Kernel2 >
  struct Base { typedef Lazy_kernel_generic_base<Exact_kernel, Approximate_kernel, E2A, Kernel2>  Type; };

  template < typename T >
  struct Ambient_dimension {
    typedef typename T::Ambient_dimension type;
  };

  template < typename T >
  struct Feature_dimension {
    typedef typename T::Feature_dimension type;
  };

  typedef typename Exact_kernel::Kernel_tag                       Kernel_tag;
  typedef typename Exact_kernel::Rep_tag                          Rep_tag;

  enum { Has_filtered_predicates = true };
  enum { Has_static_filters = false };
  typedef Boolean_tag<Has_filtered_predicates> Has_filtered_predicates_tag;

  // Types
  typedef CGAL::Lazy_exact_nt<typename Exact_kernel::FT>  FT;
  typedef FT RT;

  typedef typename Same_uncertainty_nt<bool, FT>::type
	                                                              Boolean;
  typedef typename Same_uncertainty_nt<CGAL::Sign, FT>::type
	                                                              Sign;
  typedef typename Same_uncertainty_nt<CGAL::Comparison_result, FT>::type
	                                                              Comparison_result;
  typedef typename Same_uncertainty_nt<CGAL::Orientation, FT>::type
		                                                      Orientation;
  typedef typename Same_uncertainty_nt<CGAL::Oriented_side, FT>::type
	                                                              Oriented_side;
  typedef typename Same_uncertainty_nt<CGAL::Bounded_side, FT>::type
	                                                              Bounded_side;
  typedef typename Same_uncertainty_nt<CGAL::Angle, FT>::type
	                                                              Angle;

  typedef CGAL::Object Object_2;
  typedef CGAL::Object Object_3;

#define CGAL_Kernel_obj(X) \
  typedef Lazy<typename Approximate_kernel::X, typename Exact_kernel::X, typename Exact_kernel::FT, E2A>  X;

  CGAL_Kernel_obj(Data_accessor_2)
  CGAL_Kernel_obj(Conic_2)

  typedef Cartesian_coordinate_iterator_2<Kernel> Cartesian_const_iterator_2;
  typedef Cartesian_coordinate_iterator_3<Kernel> Cartesian_const_iterator_3;

  // Aff_transformation_2/3 operations are not functorized, so treat it as
  // an exterior object for now.
  // CGAL_Kernel_obj(Aff_transformation_2)
  // CGAL_Kernel_obj(Aff_transformation_3)
  typedef CGAL::Aff_transformationC2<Kernel>              Aff_transformation_2;
  typedef CGAL::Aff_transformationC3<Kernel>              Aff_transformation_3;

private:
  // We use a combination of partial and logic to extract the right
  // construction. Constructions without a result_type always have to
  // be done through specializations.
  // 
  // The case distinction goes as follows: 
  // result_type == FT                              => NT
  // result_type == Object                          => Object
  // result_type == Bbox_2 || result_type == Bbox_3 => BBOX
  // default                                        => NONE
  // no result_type                                 => NONE
  // 
  //
  // we require a Dummy because we cannot have complete
  // specializations inside a non-namespace scope.
  // The default implementation does some default handling,
  // the special cases are filtered by partial specializations.
  template <typename Construction, typename Dummy = boost::none_t>
  struct Lazy_wrapper_traits : 
    boost::mpl::eval_if< internal::Has_result_type<Construction>,
                         boost::mpl::eval_if< boost::is_same< typename boost::remove_cv< 
                                                                typename boost::remove_reference< 
                                                                  typename internal::Lazy_result_type<Construction>::type 
                                                                  >::type >::type,
                                                              typename Approximate_kernel::FT>,
                                              boost::mpl::int_<NT>,
                                              boost::mpl::eval_if< boost::is_same< typename internal::Lazy_result_type<Construction>::type,
                                                                                   CGAL::Object >,
                                                                   boost::mpl::int_<OBJECT>,
                                                                   boost::mpl::eval_if< boost::mpl::or_< 
                                                                                          boost::is_same< typename internal::Lazy_result_type<Construction>::type, CGAL::Bbox_2 >, 
                                                                                          boost::is_same< typename internal::Lazy_result_type<Construction>::type, CGAL::Bbox_3 > >,
                                                                                        boost::mpl::int_<BBOX>,
                                                                                        boost::mpl::int_<NONE> > > >,
                         boost::mpl::int_<NONE> >::type {};
  
#define CGAL_WRAPPER_TRAIT(NAME, WRAPPER)                               \
  template<typename Dummy>                                              \
  struct Lazy_wrapper_traits<typename Approximate_kernel::NAME, Dummy>  \
    : boost::mpl::int_<WRAPPER> {};

#if CGAL_INTERSECTION_VERSION > 1
  CGAL_WRAPPER_TRAIT(Intersect_2, VARIANT)
  CGAL_WRAPPER_TRAIT(Intersect_3, VARIANT)
#else
  CGAL_WRAPPER_TRAIT(Intersect_2, OBJECT)
  CGAL_WRAPPER_TRAIT(Intersect_3, OBJECT)
#endif
  CGAL_WRAPPER_TRAIT(Compute_squared_radius_2, NT)
  CGAL_WRAPPER_TRAIT(Compute_x_3, NT)
  CGAL_WRAPPER_TRAIT(Compute_y_3, NT)
  CGAL_WRAPPER_TRAIT(Compute_z_3, NT)

#undef CGAL_WRAPPER_TRAIT

  template <typename Construction, int Type = Lazy_wrapper_traits<Construction>::value>
  struct Select_wrapper_impl;

  template <typename Construction>
  struct Select_wrapper_impl<Construction, NONE> {
    template<typename Kernel, typename AKC, typename EKC>
    struct apply { typedef Lazy_construction<Kernel, AKC, EKC> type; };
  };

  template <typename Construction>
  struct Select_wrapper_impl<Construction, NT> {
    template<typename Kernel, typename AKC, typename EKC>
    struct apply { typedef Lazy_construction_nt<Kernel, AKC, EKC> type; };
  };

  template <typename Construction>
  struct Select_wrapper_impl<Construction, VARIANT> {
    template<typename Kernel, typename AKC, typename EKC>
    struct apply { typedef Lazy_construction_variant<Kernel, AKC, EKC> type; };
  };

  template <typename Construction>
  struct Select_wrapper_impl<Construction, OBJECT> {
    template<typename Kernel, typename AKC, typename EKC>
    struct apply { typedef Lazy_construction_object<Kernel, AKC, EKC> type; };
  };

  template <typename Construction>
  struct Select_wrapper_impl<Construction, BBOX> {
    template<typename Kernel, typename AKC, typename EKC>
    struct apply { typedef Lazy_construction_bbox<Kernel, AKC, EKC> type; };
  };
  
  template <typename Construction>
  struct Select_wrapper : Select_wrapper_impl<Construction> {};

public:


#ifdef CGAL_NO_STATIC_FILTERS_FOR_LAZY_KERNEL
#define CGAL_Kernel_pred(P, Pf)                                         \
    typedef Filtered_predicate<typename Exact_kernel::P, typename Approximate_kernel::P, C2E, C2F> P; \
    P Pf() const { return P(); }
#else
#define CGAL_Kernel_pred(P, Pf)  \
  typedef Static_filtered_predicate<Approximate_kernel, Filtered_predicate<typename Exact_kernel::P, typename Approximate_kernel::P, C2E, C2F>, Exact_predicates_inexact_constructions_kernel::P> P; \
    P Pf() const { return P(); }
#endif

#define CGAL_Kernel_cons(C, Cf) \
  typedef typename Select_wrapper<typename Approximate_kernel::C>::template apply<Kernel, typename Approximate_kernel::C, typename Exact_kernel::C>::type C; \
  C Cf() const { return C(); }

#include <CGAL/Kernel/interface_macros.h>
};




  
template < typename EK_, typename AK_, typename E2A_, typename Kernel_ >
class Lazy_kernel_base
  : public Lazy_kernel_generic_base<EK_, AK_, E2A_, Kernel_>
{
public:
  typedef Kernel_ Kernel;
  typedef AK_   Approximate_kernel;
  typedef EK_   Exact_kernel;
  typedef E2A_  E2A;

  typedef Lazy_kernel_generic_base<EK_, AK_, E2A_, Kernel_> BaseClass;
  template < typename Kernel2 >
  struct Base { typedef Lazy_kernel_base<Exact_kernel, Approximate_kernel, E2A, Kernel2>  Type; };

  typedef CommonKernelFunctors::Assign_2<Kernel>        Assign_2;
  typedef CommonKernelFunctors::Assign_3<Kernel>        Assign_3;
  typedef Lazy_construction_bbox<Kernel, typename Approximate_kernel::Construct_bbox_2, typename Exact_kernel::Construct_bbox_2>             Construct_bbox_2;
  typedef Lazy_construction_bbox<Kernel, typename Approximate_kernel::Construct_bbox_3, typename Exact_kernel::Construct_bbox_3>             Construct_bbox_3;
  typedef Lazy_cartesian_const_iterator_2<Kernel, typename Approximate_kernel::Construct_cartesian_const_iterator_2, typename Exact_kernel::Construct_cartesian_const_iterator_2>   Construct_cartesian_const_iterator_2;
  typedef Lazy_cartesian_const_iterator_3<Kernel, typename Approximate_kernel::Construct_cartesian_const_iterator_3, typename Exact_kernel::Construct_cartesian_const_iterator_3>   Construct_cartesian_const_iterator_3;

  typedef CGAL::CartesianKernelFunctors::Compute_approximate_squared_length_3<Kernel>  Compute_approximate_squared_length_3;
  typedef CGAL::CartesianKernelFunctors::Compute_approximate_area_3<Kernel>  Compute_approximate_area_3;

  // typedef void Compute_z_3; // to detect where .z() is called
  // typedef void Construct_point_3; // to detect where the ctor is called
  
  struct Compute_weight_2 : public BaseClass::Compute_weight_2
  {
    typedef typename Kernel_::FT FT;
    typedef typename Kernel_::Point_2 Point_2;
    typedef typename Kernel_::Weighted_point_2 Weighted_point_2;

    FT operator()(const Weighted_point_2& p) const
    {

      typedef Lazy_rep_3<typename Approximate_kernel::Weighted_point_2,
                         typename Exact_kernel::Weighted_point_2,
                         typename Approximate_kernel::Construct_weighted_point_2,
                         typename Exact_kernel::Construct_weighted_point_2,
                         E2A_,
                         Return_base_tag,
                         Point_2,
                         FT
                         > LR;

            
      LR * lr = dynamic_cast<LR*>(p.ptr());
      if(lr && (! lr->et)){
        return lr->l2;
      }
      return BaseClass().compute_weight_2_object()(p);
    }
    
  };
  
  
  struct Compute_weight_3 : public BaseClass::Compute_weight_3
  {
    typedef typename Kernel_::FT FT;
    typedef typename Kernel_::Point_3 Point_3;
    typedef typename Kernel_::Weighted_point_3 Weighted_point_3;

    FT operator()(const Weighted_point_3& p) const
    {

      typedef Lazy_rep_3<typename Approximate_kernel::Weighted_point_3,
                         typename Exact_kernel::Weighted_point_3,
                         typename Approximate_kernel::Construct_weighted_point_3,
                         typename Exact_kernel::Construct_weighted_point_3,
                         E2A_,
                         Return_base_tag,
                         Point_3,
                         FT
                         > LR;

            
      LR * lr = dynamic_cast<LR*>(p.ptr());
      if(lr && (! lr->et)){
        return lr->l2;
      }
      return BaseClass().compute_weight_3_object()(p);
    }
    
  };

  
  struct Construct_point_2 : public BaseClass::Construct_point_2
  {
    typedef typename Kernel_::FT FT;
    typedef typename Kernel_::Point_2 Point_2;
    typedef typename Kernel_::Weighted_point_2 Weighted_point_2;
    
#ifndef CGAL_CFG_MATCHING_BUG_6
    using BaseClass::Construct_point_2::operator();
#else // CGAL_CFG_MATCHING_BUG_6

   
    template <class ...  T> 
    Point_2 operator()(const T& ...t) const
    {
      return BaseClass().construct_point_2_object()(t...);
    }
    
#endif // CGAL_CFG_MATCHING_BUG_6
    
    const Point_2& operator()(const Point_2& p) const
    {
      return p;
    }

    
    Point_2 operator()(const Weighted_point_2& p) const
    {
      typedef Lazy_rep_3<typename Approximate_kernel::Weighted_point_2,
                         typename Exact_kernel::Weighted_point_2,
                         typename Approximate_kernel::Construct_weighted_point_2,
                         typename Exact_kernel::Construct_weighted_point_2,
                         E2A_,
                         Return_base_tag,
                         Point_2,
                         FT
                         > LR;

      typedef Lazy_rep_3<typename Approximate_kernel::Weighted_point_2,
                         typename Exact_kernel::Weighted_point_2,
                         typename Approximate_kernel::Construct_weighted_point_2,
                         typename Exact_kernel::Construct_weighted_point_2,
                         E2A_,
                         Return_base_tag,
                         Point_2,
                         int
                         > LRint;

      LR * lr = dynamic_cast<LR*>(p.ptr());
      if(lr && (! lr->et)){
        return lr->l1;
      } else {
        LRint* lrint = dynamic_cast<LRint*>(p.ptr());
        if(lrint && (! lrint->et)){
          return lrint->l1;
        }
      }

      return BaseClass().construct_point_2_object()(p);
    }
    
  };


  
  struct Construct_point_3 : public BaseClass::Construct_point_3
  {
    typedef typename Kernel_::FT FT;
    typedef typename Kernel_::Point_3 Point_3;
    typedef typename Kernel_::Weighted_point_3 Weighted_point_3;
    
#ifndef CGAL_CFG_MATCHING_BUG_6
  using BaseClass::Construct_point_3::operator();
#else // CGAL_CFG_MATCHING_BUG_6
 
    template <class ...  T> 
    Point_3 operator()(const T& ...t) const
    {
      return BaseClass().construct_point_3_object()(t...);
    }
    
#endif // CGAL_CFG_MATCHING_BUG_6
    
    const Point_3& operator()(const Point_3& p) const
    {
      return p;
    }
    
    Point_3 operator()(const Weighted_point_3& p) const
    {
      typedef Lazy_rep_3<typename Approximate_kernel::Weighted_point_3,
                         typename Exact_kernel::Weighted_point_3,
                         typename Approximate_kernel::Construct_weighted_point_3,
                         typename Exact_kernel::Construct_weighted_point_3,
                         E2A_,
                         Return_base_tag,
                         Point_3,
                         FT
                         > LR;
      
      typedef Lazy_rep_3<typename Approximate_kernel::Weighted_point_3,
                         typename Exact_kernel::Weighted_point_3,
                         typename Approximate_kernel::Construct_weighted_point_3,
                         typename Exact_kernel::Construct_weighted_point_3,
                         E2A_,
                         Return_base_tag,
                         Point_3,
                         int
                         > LRint;

      
      LR * lr = dynamic_cast<LR*>(p.ptr());
      if(lr && (! lr->et)){
        return lr->l1;
      }else{
        LRint* lrint = dynamic_cast<LRint*>(p.ptr());
        if(lrint && (! lrint->et)){
          return lrint->l1;
        }
      }
      return BaseClass().construct_point_3_object()(p);
    }
    
  };

  
  Construct_point_2 construct_point_2_object() const
  {
    return Construct_point_2();
  }

  Construct_point_3 construct_point_3_object() const
  {
    return Construct_point_3();
  }
  
  
  Compute_weight_2 compute_weight_2_object() const
  {
    return Compute_weight_2();
  }
  
  Compute_weight_3 compute_weight_3_object() const
  {
    return Compute_weight_3();
  }
  

  Assign_2
  assign_2_object() const
  { return Assign_2(); }

  Assign_3
  assign_3_object() const
  { return Assign_3(); }

  Construct_bbox_2
  construct_bbox_2_object() const
  { return Construct_bbox_2(); }

  Construct_bbox_3
  construct_bbox_3_object() const
  { return Construct_bbox_3(); }

  Construct_cartesian_const_iterator_2
  construct_cartesian_const_iterator_2_object() const
  { return Construct_cartesian_const_iterator_2(); }

  Construct_cartesian_const_iterator_3
  construct_cartesian_const_iterator_3_object() const
  { return Construct_cartesian_const_iterator_3(); }

  Compute_approximate_squared_length_3
  compute_approximate_squared_length_3_object() const
  { return Compute_approximate_squared_length_3(); }

  Compute_approximate_area_3
  compute_approximate_area_3_object() const
  { return Compute_approximate_area_3(); }
}; // end class Lazy_kernel_base<EK_, AK_, E2A_, Kernel_2>


template <class Exact_kernel, class Approximate_kernel, class E2A>
struct Lazy_kernel_without_type_equality
  : public Lazy_kernel_base< Exact_kernel, Approximate_kernel, E2A, Lazy_kernel_without_type_equality<Exact_kernel,Approximate_kernel, E2A> >
{};

template <class Exact_kernel,
	  class Approximate_kernel = Simple_cartesian<Interval_nt_advanced>,
          class E2A = Cartesian_converter<Exact_kernel, Approximate_kernel> >
struct Lazy_kernel
  : public Type_equality_wrapper<
             Lazy_kernel_base< Exact_kernel, Approximate_kernel, E2A, Lazy_kernel<Exact_kernel, Approximate_kernel, E2A> >,
             Lazy_kernel<Exact_kernel, Approximate_kernel, E2A> >
{
// WARNING: If you change the definition of Lazy_kernel, then you need to
// change also the definition of Epeck in
// <CGAL/Exact_predicate_exact_constructions_kernel.h>.
};

} //namespace CGAL


#if defined(BOOST_MSVC)
#  pragma warning(pop)
#endif

#endif // CGAL_LAZY_KERNEL_H
