// Copyright (c) 2007,2008,2009,2010,2011 Max-Planck-Institute Saarbruecken (Germany), 
// and Tel-Aviv University (Israel).  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$
// $Id$
//
//
// Author(s)     : Eric Berberich <eric@mpi-inf.mpg.de>
//                 Arno Eigenwillig <arno@mpi-inf.mpg.de>
//                 Pavel Emeliyanenko <asm@mpi-sb.mpg.de> 
//
// ============================================================================

#ifndef CGAL_CURVED_KERNEL_VIA_ANALYSIS_2_MAKE_X_MONOTONE_2_H
#define CGAL_CURVED_KERNEL_VIA_ANALYSIS_2_MAKE_X_MONOTONE_2_H

/*!\file include/CGAL/Curved_kernel_via_analysis_2/Make_x_monotone_2.h
 * \brief Defines \c Make_x_monotone_2 functor
 */

#include <CGAL/config.h>
#include <CGAL/Handle_with_policy.h>

// TODO remove polynomial_traits
#include <CGAL/Polynomial_traits_d.h>

namespace CGAL {

namespace internal {

/*!\brief 
 * Splits a curve that can be analyzed 
 * into connected x-monotone sweepable arcs and isolated points.
 *
 * Arcs are stored as CurvedKernelViaAnalysis_2::Arc_2 objects, and
 * each is either vertical or consists of an x-monotone piece 
 * of constant arc number wrt to the curve at every interior x-coordinate.
 * Isolated points are stored as \c CurvedKernelViaAnalysis_2::Point_2 objects.
 * 
 * The resulting arcs and points are written to the output iterator as
 * polymorphic \c CGAL::Object. Past-the-end value of the iterator is returned.
 */
template < class CurvedKernelViaAnalysis_2, 
           class ConstructArc_2 = 
           typename CurvedKernelViaAnalysis_2::Construct_arc_2 >
struct Make_x_monotone_2 :
    public std::binary_function< typename CurvedKernelViaAnalysis_2::Curve_2,
            std::iterator<std::output_iterator_tag, CGAL::Object>,
            std::iterator<std::output_iterator_tag, CGAL::Object> > {
            
    //!\name Public types
    //!@{
    
    //! this instance's first template parameter
    typedef CurvedKernelViaAnalysis_2 Curved_kernel_via_analysis_2;
    
    //! this instance's second template parameter
    typedef ConstructArc_2 Construct_arc_2;

    //! type of curve kernel
    typedef typename Curved_kernel_via_analysis_2::Curve_kernel_2
    Curve_kernel_2;
    
    //! type of x-coordinate
    typedef typename Curve_kernel_2::Coordinate_1 Coordinate_1;
    
    //! type of xy-coordinate
    typedef typename Curve_kernel_2::Coordinate_2 Coordinate_2;
    
    //! type of curve analysis
    typedef typename Curve_kernel_2::Curve_analysis_2 Curve_analysis_2;
    
    //! type of vertical line
    typedef typename Curve_analysis_2::Status_line_1 Status_line_1;
    
    //! type of point on curve
    typedef typename Curved_kernel_via_analysis_2::Point_2 Point_2;
    
    //! type of curve arc
    typedef typename Curved_kernel_via_analysis_2::Arc_2 Arc_2;

    //! type of not necessarily x-monotone curve arc
    typedef typename Curved_kernel_via_analysis_2::Non_x_monotone_arc_2
        Non_x_monotone_arc_2;
    
    //!@}

    //!\name Constructors
    //!@{

    /*!\brief 
     * Standard constructor
     *
     * \param kernel The kernel instance to use
     */
    Make_x_monotone_2(Curved_kernel_via_analysis_2 *kernel) :
        _m_curved_kernel(kernel) {
        CGAL_assertion(kernel != NULL);
    }

    //!@}

    //!\name Functor invokation
    //!@{

    // TODO add operator for non-x-monotone arc
    
    /*!\brief
     * Splits a curve into x-monotone arcs and isolated points
     *
     * \param curve The input curve
     * \param oi Output iterator that stores CGAL::Object, which either
     *           encapsulates Point_2 or Arc_2
     * \return Past-the-end iterator of \c oi
     */
    template <class OutputIterator>
    OutputIterator operator()(Curve_analysis_2 curve, OutputIterator oi) {

        Construct_arc_2 construct_arc_2 = 
            _m_curved_kernel->construct_arc_2_object();
        // use CGAL::Total_degree ?
        if (typename CGAL::Polynomial_traits_d< 
            typename Curve_analysis_2::Polynomial_2 >::
            Total_degree()(curve.polynomial_2()) < 1) {
            return oi;
        }
        
        Status_line_1 evt_line1, evt_line2,
            int_line = curve.status_line_of_interval(0);
        int total_events = curve.number_of_status_lines_with_event();
        // handle special case of a curve without any events
        if(total_events == 0) {
            for(int k = 0; k < int_line.number_of_events(); k++) 
                *oi++ = CGAL::make_object(construct_arc_2(curve, k));
            return oi;
        }
        _m_curve = curve;
        typedef typename Curved_kernel_via_analysis_2::
            Curve_interval_arcno_cache CIA_cache;
        const CIA_cache& map_interval_arcno =
            _m_curved_kernel->interval_arcno_cache();
        
        typename Curved_kernel_via_analysis_2::Construct_point_2 
            construct_point =
            _m_curved_kernel->construct_point_2_object();

        typename CIA_cache::result_type info1, info2;
        std::vector<Point_2> min_pts, max_pts;
        Coordinate_1 min_x, max_x;
        int i, k, n;
        Arc_2 arc;
        // first handle segments before first event
        evt_line1 = curve.status_line_at_event(0);
        max_x = evt_line1.x();
        
        for(k = 0; k < evt_line1.number_of_events(); k++) 
            max_pts.push_back(construct_point(max_x, curve, k));
                        
        //std::cout << "handling events over the 1st interval\n";    
        for(k = 0; k < int_line.number_of_events(); k++) {
            
            info1 = map_interval_arcno(evt_line1, 1, k); 
            if (info1.second != CGAL::ARR_INTERIOR) {
                arc = construct_arc_2(CGAL::ARR_MIN_END, max_x, 
                                      (info1.second == 
                                       CGAL::ARR_BOTTOM_BOUNDARY ?
                                       CGAL::ARR_MIN_END : CGAL::ARR_MAX_END), 
                                      curve, k);
            } else {
                arc = construct_arc_2(max_pts[info1.first], CGAL::ARR_MIN_END, 
                                      curve, k, info1.first
                );
            }
            *oi++ = CGAL::make_object(arc);
        }
        min_pts = max_pts;
        max_pts.clear();
        min_x = max_x;
               
        // next handle arcs between events, including isolated points
        for (i = 0; i < total_events-1; i++) {
            evt_line1 = curve.status_line_at_event(i);
            evt_line2 = curve.status_line_at_event(i+1);
            max_x = evt_line2.x();
            oi = _handle_vertical_and_isolated(evt_line1, min_x, min_pts, oi);
                                
            n = evt_line2.number_of_events();
            for(k = 0; k < n; k++) 
                max_pts.push_back(construct_point(max_x, curve, k));
            
            n = curve.status_line_of_interval(i+1).number_of_events();
            CGAL::Arr_curve_end inf1_end, inf2_end;
            for (k = 0; k < n; k++) {
                
                info1 = map_interval_arcno(evt_line1, 0, k); 
                info2 = map_interval_arcno(evt_line2, 1, k); 
                inf2_end = (info2.second == CGAL::ARR_BOTTOM_BOUNDARY ? 
                    CGAL::ARR_MIN_END : CGAL::ARR_MAX_END);
                
                if (info1.second != CGAL::ARR_INTERIOR) {
                    inf1_end = (info1.second == CGAL::ARR_BOTTOM_BOUNDARY ? 
                        CGAL::ARR_MIN_END : CGAL::ARR_MAX_END);
                    if (info2.second != CGAL::ARR_INTERIOR) {
                        arc = construct_arc_2(min_x, inf1_end, max_x, inf2_end,
                                              curve, k);
                    } else {
                        arc = construct_arc_2(max_pts[info2.first], min_x, 
                                              inf1_end, curve, k, info2.first);
                    }
                } else if (info2.second != CGAL::ARR_INTERIOR) {
                    arc = construct_arc_2(min_pts[info1.first],  max_x,
                                          inf2_end, curve, k, info1.first);
                } else {
                    arc = construct_arc_2(min_pts[info1.first], 
                                          max_pts[info2.first],
                                          curve, k, info1.first, info2.first);
                }
                *oi++ = CGAL::make_object(arc);
            }
            min_pts = max_pts;
            max_pts.clear();
            min_x = max_x;
        }
        
        // here: min_x/min_pts hold information about the last event line
        // event_line2 - points to the last event line
        // vertical line or isolated points at last event?
        evt_line2 = curve.status_line_at_event(total_events-1);
        min_x = evt_line2.x();
        oi = _handle_vertical_and_isolated(evt_line2, min_x, min_pts, oi);
        
        n = curve.status_line_of_interval(total_events).number_of_events();
        for (k = 0; k < n; k++) {
        
            info1 = map_interval_arcno(evt_line2, 0, k); 
            if (info1.second != CGAL::ARR_INTERIOR) {
                arc = construct_arc_2(
                        CGAL::ARR_MAX_END, min_x, 
                        (info1.second == CGAL::ARR_BOTTOM_BOUNDARY ? 
                         CGAL::ARR_MIN_END : CGAL::ARR_MAX_END), curve, k
                );
            } else {
                arc = construct_arc_2(min_pts[info1.first], 
                                      CGAL::ARR_MAX_END, curve, k,
                                      info1.first);
            }
            *oi++ = CGAL::make_object(arc);
        }
        return oi;
    }
    //!@}

private:
    //!\name Private members
    //!@{
    
    /*!\brief
     * Constructs vertical arcs and isolated points at event line
     *
     * \param cv_line The event line in focus
     * \param x x-coordinate of event
     * \param pts Points at event line
     * \param oi Output iterator that stores CGAL::Object, which either
     *           encapsulates Point_2 or Arc_2
     * \return Past-the-end iterator of \c oi
     */
    template <class OutputIterator>
    OutputIterator _handle_vertical_and_isolated(
            Status_line_1 cv_line,
            Coordinate_1 x, std::vector<Point_2> pts, 
            OutputIterator oi) const {
        
        Construct_arc_2 construct_arc_2 = 
            _m_curved_kernel->construct_arc_2_object();
        
        int n = cv_line.number_of_events(), j;
        if(cv_line.covers_line()) { // look for vertical arcs
            if(n > 0) {
                // the first vertical ray
                *oi++ = CGAL::make_object(
                        construct_arc_2(pts[0], CGAL::ARR_MIN_END, _m_curve)
                );
                for(j = 0; j < n-1; j++)  // interior bounded arcs
                    *oi++ = CGAL::make_object(construct_arc_2(pts[j], pts[j+1],
                                                              _m_curve));
                    // the last vertical ray
                    *oi++ = CGAL::make_object(construct_arc_2(pts[n-1], 
                        CGAL::ARR_MAX_END, _m_curve));
            } else // unbounded vertical line
                *oi++ = CGAL::make_object(construct_arc_2(x, _m_curve));
            return oi;
        } 
        // look for isolated points
        std::pair<int, int> ipair;
        for(j = 0; j < n; j++) {
            ipair = cv_line.number_of_incident_branches(j);
            if(ipair.first == 0&&ipair.second == 0) {
                //std::cout << "isolated point found\n";
                typename Curved_kernel_via_analysis_2::Construct_point_2 
                    construct_point =
                    _m_curved_kernel->construct_point_2_object();
                
                *oi++ = CGAL::make_object(construct_point(x, _m_curve, j));
            }
        }
        return oi;
    }

    //!@}

    //!\name Private data
    //!@{
            
    //! pointer to \c Curved_kernel_via_analysis_2 
    Curved_kernel_via_analysis_2 *_m_curved_kernel;

    //! to avoid passing curve as a parameter
    Curve_analysis_2 _m_curve;
    
    //!@}
};  // struct Make_x_monotone

} // namespace internal

} //namespace CGAL

#endif // CGAL_CURVED_KERNEL_VIA_ANALYSIS_2_MAKE_X_MONOTONE_2_H
//EOF
