// Copyright (c) 2005-2006  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org); you may redistribute it under
// the terms of the Q Public License version 1.0.
// See the file LICENSE.QPL distributed with CGAL.
//
// 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: svn+ssh://scm.gforge.inria.fr/svn/cgal/trunk/Surface_mesher/include/CGAL/Surface_mesher/Combining_oracle.h $
// $Id: Combining_oracle.h 40923 2007-11-18 14:19:48Z lrineau $
//
//
// Author(s)     : Laurent Rineau


#ifndef CGAL_SURFACE_MESHER_COMBINING_ORACLE_H
#define CGAL_SURFACE_MESHER_COMBINING_ORACLE_H

#include <list>
#include <algorithm>

#include <CGAL/Multi_surface_3.h>

#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>

namespace CGAL {

  namespace Surface_mesher {

  /** Combine two oracles
      This class is a model of Oracle that combines two models of Oracle,
      so that the corresponding surface is the union of the two surfaces.
      \param Oracle_a First oracle type.
      \param Oracle_b Second oracle type.
  */
  template <class Oracle_a, class Oracle_b>
  class Combining_oracle
  {
    Oracle_a& oracle_a;
    Oracle_b& oracle_b;
  public:
    // Public types
    typedef typename Oracle_a::Point_3 Point_3;
    typedef typename Oracle_a::Segment_3 Segment_3;
    typedef typename Oracle_a::Ray_3 Ray_3;
    typedef typename Oracle_a::Line_3 Line_3;

    typedef typename Oracle_a::Intersection_point Intersection_point;

    BOOST_STATIC_ASSERT((::boost::is_same<
                         Intersection_point,
                         typename Oracle_b::Intersection_point>::value));
                        

    typedef ::CGAL::Multi_surface_3<typename Oracle_a::Surface_3,
      typename Oracle_b::Surface_3> Surface_3;

    typedef typename Oracle_a::Intersect_3 Intersect_a;
    typedef typename Oracle_b::Intersect_3 Intersect_b;

  public:
    Combining_oracle(Oracle_a& oracle_a, Oracle_b& oracle_b)
      : oracle_a(oracle_a), oracle_b(oracle_b)
    {
    }

    class Intersect_3 
    {
      Oracle_a& oracle_a;
      Oracle_b& oracle_b;
    public:
      Intersect_3(Oracle_a& oracle_a, Oracle_b& oracle_b)
        : oracle_a(oracle_a), oracle_b(oracle_b)
      {
      }
      
      Object operator()(const Surface_3& surface, Segment_3 s) const
      {
        const Object obj = oracle_a.intersect_3_object()(surface.surface_a(), s);
        if( obj.is_empty() )
          return oracle_b.intersect_3_object()(surface.surface_b(), s);
        return obj;
      }

      Object operator()(const Surface_3& surface, const Ray_3& r) const {
        const Object obj = oracle_a.intersect_3_object()(surface.surface_a(), r);
        if( obj.is_empty() )
          return oracle_b.intersect_3_object()(surface.surface_b(), r);
        return obj;  
      }
      
      Object operator()(const Surface_3& surface, const Line_3& l) const {
        const Object obj = oracle_a.intersect_3_object()(surface.surface_a(), l);
        if( obj.is_empty() )
          return oracle_b.intersect_3_object()(surface.surface_b(), l);
        return obj;  
      }
    }; // end nested class Intersect_3

    class Construct_initial_points
    {
      Oracle_a& oracle_a;
      Oracle_b& oracle_b;
    public:
      Construct_initial_points(Oracle_a& oracle_a, Oracle_b& oracle_b)
        : oracle_a(oracle_a), oracle_b(oracle_b)
      {
      }

      // Random points
      template <typename OutputIteratorPoints>
      OutputIteratorPoints operator() (const Surface_3& surface, 
                                       OutputIteratorPoints out, 
                                       int n = 20) // WARNING: why 20?
      {
        OutputIteratorPoints out2 = 
          oracle_a.construct_initial_points_object()(surface.surface_a(),
                                                     out,
                                                     n);
        return oracle_b.construct_initial_points_object()(surface.surface_b(),
                                                          out2,
                                                          n);
      }
    }; // end nested class Construct_initial_points
     
    Intersect_3 intersect_3_object() const
    {
      return Intersect_3(oracle_a, oracle_b);
    }

    Construct_initial_points construct_initial_points_object() const
    {
      return Construct_initial_points(oracle_a, oracle_b);
    }

    bool is_in_volume(const Surface_3& surface, const Point_3& p) const
    {
      return( oracle_a.is_in_volume(surface.surface_a(), p) || 
              oracle_b.is_in_volume(surface.surface_b(), p) );
    }
  };  // end Combining_oracle

  }  // namespace Surface_mesher

} // namespace CGAL


#endif  // CGAL_SURFACE_MESHER_COMBINING_ORACLE_H
