// Copyright (c) 1997  Tel-Aviv University (Israel).
// 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.
//
// $Source: /CVSROOT/CGAL/Packages/Planar_map/include/CGAL/Pm_naive_point_location.C,v $
// $Revision: 1.15 $ $Date: 2004/08/08 16:14:07 $
// $Name:  $
//
// Author(s)     : Iddo Hanniel <hanniel@post.tau.ac.il>
//                 Oren Nechushtan <theoren@post.tau.ac.il>
//                 Efi Fogel <efif@post.tau.ac.il>
#ifndef CGAL_PM_NAIVE_POINT_LOCATION_C
#define CGAL_PM_NAIVE_POINT_LOCATION_C

#include <CGAL/Pm_naive_point_location.h>

CGAL_BEGIN_NAMESPACE

//IMPLEMENTATION
//if unbounded face - returns NULL or some edge on unbounded face 
//if its a vertex returns a halfedge pointing _at_ it
template <class Planar_map>
typename Pm_naive_point_location<Planar_map>::Halfedge_const_handle
Pm_naive_point_location<Planar_map>::locate(const Point & p, 
                                            Locate_type & lt) const
{
  typename Planar_map::Vertex_const_iterator vit=pm->vertices_begin();
  for (; vit != pm->vertices_end(); ++vit) {
    if (traits->point_equal(p,vit->point()) ) {
      lt = Planar_map::VERTEX; 
      Halfedge_const_handle h(vit->incident_halfedges());
      return h;
    }
  }

  typename Planar_map::Halfedge_const_iterator hit=pm->halfedges_begin();
  for (; hit != pm->halfedges_end(); ++hit) {
    if (traits->point_in_x_range(hit->curve(),p) &&
	traits->curve_compare_y_at_x(p, hit->curve()) == EQUAL) {
      lt = Planar_map::EDGE; 
      return hit;
    }
  }

  lt = Planar_map::UNBOUNDED_FACE;
  Locate_type temp;
  Halfedge_const_handle h = vertical_ray_shoot(p,temp,true);
  if (temp != Planar_map::UNBOUNDED_FACE) {
    if (temp == Planar_map::VERTEX) {  
      //since h points at the vertex and is the first 
      //halfedge after the ray clockwise! then the  face
      //is to its _right_ (maybe the specs will change in the future) 
      h = h->twin();        
    }        

    if (!(h->face()->is_unbounded())) lt = Planar_map::FACE;
    return h;
  }

  //==the vertical ray shoot returned the halfedges_end() iterator.
  if (pm->unbounded_face()->holes_begin() == 
      pm->unbounded_face()->holes_end() ) //an empty map
    return h; //return halfedges_end()

  //- returns a halfedge on an inner ccb of the unbounded face
  typename Planar_map::Holes_iterator hot =
    pm->unbounded_face()->holes_begin();
  return (*hot);
}

template <class Planar_map>
typename Pm_naive_point_location<Planar_map>::Halfedge_handle
Pm_naive_point_location<Planar_map>::locate(const Point & p, Locate_type & lt)
{
  ((Bounding_box*)get_bounding_box())->insert(p);
  Halfedge_handle h =
    Halfedge_handle_unconst(((const_Self_ptr)this)->locate(p,lt));
  if (!((Bounding_box*)get_bounding_box())->locate(p,lt,h))
    h = Halfedge_handle_unconst(((const_Self_ptr)this)->locate(p,lt));
  return h;
}

template <class Planar_map>
typename Pm_naive_point_location<Planar_map>::Halfedge_const_handle
Pm_naive_point_location<Planar_map>::
vertical_ray_shoot(const Point & p, Locate_type & lt, bool up) const
{
  Comparison_result point_above_under, res;
  Comparison_result curve_above_under;
  bool              in_x_range;

  lt = Planar_map::EDGE;

  // set the flags for comparison acording to the ray 
  // direction (up/down)
  if (up) {
    point_above_under = SMALLER;
    curve_above_under = LARGER;
  } else {
    point_above_under = LARGER;
    curve_above_under = SMALLER;
  }

  typename Planar_map::Halfedge_const_iterator it  = pm->halfedges_begin(),
                                         eit = pm->halfedges_end(),
                                         closest_edge = eit;
  bool first = false;
  // For each halfedge
  while (it != eit) {
    // Find if p is in the x-range of the curve and above or below it
    // according to the direction of the shoot.
    in_x_range = traits->point_in_x_range(it->curve(), p);
    if (in_x_range)
      res = traits->curve_compare_y_at_x(p, it->curve());

    if (in_x_range && res == point_above_under) {
      // If the first curve in the x-range was not found yet
      if (!first) {
        closest_edge = it;
        first = true;
      } else {
        // We found another curve in the x-range and we want to remember
        // the closest
        if (traits->curves_compare_y_at_x(closest_edge->curve(), it->curve(),
                                          p) == curve_above_under) 
        {
          closest_edge = it;
        }
      }
    }
    if (in_x_range && res == EQUAL &&
	traits->curve_is_vertical(it->curve()))
    {
      // The vertical ray shoot is not including p itself,
      // thus we are interested only in vertical curves that
      // extend upwards (downwards, resp.)
      // In this case the Locate type is always EDGE
      // Remark: This treatment was originally written in the walk PL.
      //
      if (up && 
	  traits->point_is_right_top
	      (traits->curve_righttop_most(it->curve()), p) ||
	  ! up &&
	  traits->point_is_left_low
              (traits->curve_leftlow_most(it->curve()), p))
	/*
	  x       x
	  |       |
	  p=x  or  p
	  |
	  x
	*/
      {
        lt = Planar_map::EDGE;
        if (up == traits->point_is_left_low(it->target()->point(),
                                            it->source()->point()))
          return it;
        else 
          return it->twin();
      }
    }
    ++it; ++it;
  }
  
  // if we didn't find any edge above p then it is the empty face
  if ( ! first) {
    lt = Planar_map::UNBOUNDED_FACE;
    Halfedge_const_handle h = pm->halfedges_end();
    return h; //==NULL
  }

  // if the closest point is a vertex then find the first clockwise 
  // edge from the vertical segment
  typename Planar_map::Vertex_const_handle v = pm->vertices_end();
  bool maybe_vertical = false; // BUG fix (Oren)
  if (traits->point_equal_x(closest_edge->target()->point(), p)) {
    v = closest_edge->target();
    maybe_vertical=true; // BUG fix (Oren)
  }

  if ( traits->point_equal_x( closest_edge->source()->point(), p) ) 
    {
      if (!maybe_vertical || 
	  traits->point_is_right_top(closest_edge->target()->point(),
				     closest_edge->source()->point())==up) 
                                  // BUG fix (Oren)
	v = closest_edge->source();
      /*
	special care for the vertical cases:
		  
	x             p
	|
	x     and     x
	|
	p             x
      */
    }
	
  //if (closest_is_vertex)
  if (v != pm->vertices_end()) {
    lt = Planar_map::VERTEX;
    closest_edge = (up) ? find_lowest(v, false) : find_lowest(v, true);
  }

  if (lt == Planar_map::VERTEX) return closest_edge;
  if (up) {
    // return the edge that is going from right to left
    // such that p is to the left of this edge

    return (traits->point_is_right(closest_edge->source()->point(),
                                   closest_edge->target()->point())) ?
      closest_edge : closest_edge->twin();
  } 
  return (traits->point_is_left(closest_edge->source()->point(),
                                closest_edge->target()->point())) ?
    closest_edge : closest_edge->twin();
}

template <class Planar_map>
typename Pm_naive_point_location<Planar_map>::Halfedge_handle
Pm_naive_point_location<Planar_map>::vertical_ray_shoot(const Point & p, 
                                                        Locate_type & lt, 
                                                        bool up)
{
  /* Make sure the source point is in the bounding box on the output */
  ((Bounding_box*)get_bounding_box())->insert(p);
  Halfedge_handle h =
    Halfedge_handle_unconst(((const_Self_ptr)this)->
                            vertical_ray_shoot(p,lt,up));
  /* Apply the bounding box on the output */
  if (!((Bounding_box*)get_bounding_box())->vertical_ray_shoot(p,lt,up,h)) {
    h = Halfedge_handle_unconst(((const_Self_ptr)this)->
                                vertical_ray_shoot(p,lt,up));
    CGAL_assertion(lt!=Planar_map::UNBOUNDED_FACE);
  }
  return h;
}


//find the first halfedge pointing at v, when going clockwise
//if highest==true - start from 12 oclock, else start from 6 oclock
template <class Planar_map>
typename Pm_naive_point_location<Planar_map>::Halfedge_const_handle 
Pm_naive_point_location<Planar_map>::
find_lowest(typename Pm_naive_point_location<Planar_map>::Vertex_const_handle v,
            bool highest) const
{
  Halfedge_const_handle lowest_left = pm->halfedges_end();
  Halfedge_const_handle lowest_right = pm->halfedges_end();
  Halfedge_const_handle vertical_up = pm->halfedges_end();
  Halfedge_const_handle vertical_down = pm->halfedges_end();

  typename Planar_map::Halfedge_around_vertex_const_circulator first = 
    v->incident_halfedges();
  typename Planar_map::Halfedge_around_vertex_const_circulator curr = first;

  do {
    if (traits->point_is_left(curr->source()->point(), v->point())) {
      if (lowest_left == pm->halfedges_end())
        lowest_left = curr;
      else if (traits->curves_compare_y_at_x_left(curr->curve(),
                                               lowest_left->curve(), 
                                               v->point()) == SMALLER)
        lowest_left = curr;
    }

    if (traits->point_is_right(curr->source()->point(), v->point())) {
      if (lowest_right == pm->halfedges_end())
        lowest_right = curr;
      else if (traits->curves_compare_y_at_x_right(curr->curve(),
                                                lowest_right->curve(), 
                                                v->point()) == LARGER)
        lowest_right = curr;
    }

    if (traits->curve_is_vertical(curr->curve())) 
    {
      if (traits->compare_xy(v->point(),
			     curr->source()->point()) == LARGER)
	vertical_up=curr; 
			
      if (traits->compare_xy(v->point(),
			     curr->source()->point()) == SMALLER)
	vertical_down=curr;			
    }        
        
  } while (++curr != first);
        
  /*
     vertical_down  
     |
     v   <- lowest_right      
     'v'  
     lowest_left->  ^ 
     |
     vertical_up
  */
        
  if (!highest) {
    if (lowest_left != pm->halfedges_end()) return lowest_left;
    if (vertical_down != pm->halfedges_end()) return vertical_down;
    return lowest_right;
  }
  if (lowest_right != pm->halfedges_end()) return lowest_right;
  if (vertical_up != pm->halfedges_end()) return vertical_up;
  return lowest_left;
}

CGAL_END_NAMESPACE

#endif
