// Copyright (c) 2005,2006  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1/Filtered_kernel/include/CGAL/Lazy_kernel.h $
// $Id: include/CGAL/Lazy_kernel.h b26b07a1242 $
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// 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/EPIC_predicate_if_convertible.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/Filtered_kernel/internal/Static_filters/tools.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <boost/none.hpp>
#include <boost/mpl/or.hpp>
#include <CGAL/Lazy_exact_nt.h>

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

namespace CGAL {

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

// `Lazy_kernel_generic_base` applies the generic magic functor stupidly.
// `Lazy_kernel_base` fixes up a few special cases.
template < typename EK_, typename AK_, typename E2A_, typename Kernel_ >
class Lazy_kernel_generic_base
  // : public Filtered_kernel_base<EK_>
{
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 };
  typedef Boolean_tag<Has_filtered_predicates> Has_filtered_predicates_tag;

#ifdef CGAL_NO_STATIC_FILTERS_FOR_LAZY_KERNEL
  enum { Has_static_filters = false };
#else
  // @fixme, this should be 'true' but it's broken because EPIC_predicate_if_convertible
  // assumes the static filtered predicate and the (non-static) filtered predicate
  // have the same signature, which is not always the case, for example in
  //   Do_intersect_3(Sphere_3, Bbox_3, *bool*)
  enum { Has_static_filters = false };
#endif

  // 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, 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;

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
// - the first template parameter is because either it fits in a double, or not, so
//   we might as well use the approximate kernel directly rather than the complete lazy kernel
// - the second is the predicate to be called if EPICK is not usable
// - the third is the equivalent predicate in EPICK
#define CGAL_Kernel_pred(P, Pf) \
  typedef EPIC_predicate_if_convertible<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 Lazy_construction<Kernel, typename Approximate_kernel::C, typename Exact_kernel::C> C; \
  C Cf() const { return C(); }

#include <CGAL/Kernel/interface_macros.h>

  // Useless meta-function, added to workaround a bug with Visual C++ 2022 and before
  // See issue https://github.com/CGAL/cgal/issues/8140
  template < typename T >
  struct Handle { typedef T   type; };
};

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_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 CGAL::Lazy_construction_optional_for_polyhedral_envelope<
            Kernel,
            typename Approximate_kernel::Intersect_point_3_for_polyhedral_envelope,
            typename Exact_kernel::Intersect_point_3_for_polyhedral_envelope> Intersect_point_3_for_polyhedral_envelope;

  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_n<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_,
                         true,
                         Return_base_tag,
                         Point_2,
                         FT
                         > LR;

      typedef Lazy_rep_n<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_,
                         true,
                         Return_base_tag,
                         Point_2,
                         int
                         > LRint;

      auto& obj = *p.ptr();
      const char* tn = typeid(obj).name();

      if(tn == typeid(LR).name()){
        LR * lr = static_cast<LR*>(p.ptr());
        if(lr->is_lazy()){
          // Another thread could reset lr->l before this line, so we disable reset for Construct_weighted_point_2 in MT-mode.
          // We could also always disable reset for Construct_weighted_point_2 and return lr->l here even if update_exact has run.
          return std::get<2>(lr->l);
        }
      }else{
        if(tn == typeid(LRint).name()){
          LRint* lrint = static_cast<LRint*>(p.ptr());
          if(lrint->is_lazy()){
            return std::get<2>(lrint->l);
          }
        }
      }

      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_n<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_,
                         true,
                         Return_base_tag,
                         Point_3,
                         FT
                         > LR;

      typedef Lazy_rep_n<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_,
                         true,
                         Return_base_tag,
                         Point_3,
                         int
                         > LRint;

      auto& obj = *p.ptr();
      const char* tn = typeid(obj).name();

      if(tn == typeid(LR).name()){
        LR * lr = static_cast<LR*>(p.ptr());
        if(lr->is_lazy()){
          return std::get<2>(lr->l);
        }
      }else{
        if(tn == typeid(LRint).name()){
          LRint* lrint = static_cast<LRint*>(p.ptr());
          if(lrint->is_lazy()){
            return std::get<2>(lrint->l);
          }
        }
      }

      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;

    using BaseClass::Construct_point_2::operator();

    const Point_2& operator()(const Point_2& p) const
    {
      return p;
    }


    Point_2 operator()(const Weighted_point_2& p) const
    {
      typedef Lazy_rep_n<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_,
                         true,
                         Return_base_tag,
                         Point_2,
                         FT
                         > LR;

      typedef Lazy_rep_n<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_,
                         true,
                         Return_base_tag,
                         Point_2,
                         int
                         > LRint;

      auto& obj = *p.ptr();
      const char* tn = typeid(obj).name();

      if(tn == typeid(LR).name()){
        LR * lr = static_cast<LR*>(p.ptr());
        if(lr->is_lazy()){
          return std::get<1>(lr->l);
        }
      }else{
        if(tn == typeid(LRint).name()){
          LRint* lrint = static_cast<LRint*>(p.ptr());
          if(lrint->is_lazy()){
            return std::get<1>(lrint->l);
          }
        }
      }

      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;

    using BaseClass::Construct_point_3::operator();

    const Point_3& operator()(const Point_3& p) const
    {
      return p;
    }

    Point_3 operator()(const Weighted_point_3& p) const
    {
      typedef Lazy_rep_n<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_,
                         true,
                         Return_base_tag,
                         Point_3,
                         FT
                         > LR;

      typedef Lazy_rep_n<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_,
                         true,
                         Return_base_tag,
                         Point_3,
                         int
                         > LRint;

      auto& obj = *p.ptr();
      const char* tn = typeid(obj).name();

      if(tn == typeid(LR).name()){
        LR * lr = static_cast<LR*>(p.ptr());
        if(lr->is_lazy()){
          return std::get<1>(lr->l);
        }
      }else{
        if(tn == typeid(LRint).name()){
          LRint* lrint = static_cast<LRint*>(p.ptr());
          if(lrint->is_lazy()){
            return std::get<1>(lrint->l);
          }
        }
      }

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

  };

  struct Less_xyz_3 : public BaseClass::Less_xyz_3
  {
    typedef typename Kernel_::Point_3 Point_3;

    bool operator()(const Point_3& p, const Point_3& q) const
    {
      if (p.rep().identical(q.rep())) { return false; }
      return BaseClass::Less_xyz_3::operator()(p,q);
    }
  };

  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_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(); }

  Intersect_point_3_for_polyhedral_envelope
  intersect_point_3_for_polyhedral_envelope_object() const
  { return Intersect_point_3_for_polyhedral_envelope(); }

  Less_xyz_3
  less_xyz_3_object() const
  { return Less_xyz_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

