// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 1995, 2007-2015 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 1995 Maarten Hilferink, Amsterdam, the Netherlands

// This file was modified by Oracle on 2015-2020.
// Modifications copyright (c) 2015-2020, Oracle and/or its affiliates.
// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_STRATEGY_AGNOSTIC_SIMPLIFY_DOUGLAS_PEUCKER_HPP
#define BOOST_GEOMETRY_STRATEGY_AGNOSTIC_SIMPLIFY_DOUGLAS_PEUCKER_HPP


#include <cstddef>
#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
#include <iostream>
#endif
#include <vector>

#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/strategies/distance.hpp>


#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
#include <boost/geometry/io/dsv/write.hpp>
#endif


namespace boost { namespace geometry
{

namespace strategy { namespace simplify
{


#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

    /*!
        \brief Small wrapper around a point, with an extra member "included"
        \details
            It has a const-reference to the original point (so no copy here)
        \tparam the enclosed point type
    */
    template<typename Point>
    struct douglas_peucker_point
    {
        Point const& p;
        bool included;

        inline douglas_peucker_point(Point const& ap)
            : p(ap)
            , included(false)
        {}

        // Necessary for proper compilation
        inline douglas_peucker_point<Point> operator=(douglas_peucker_point<Point> const& )
        {
            return douglas_peucker_point<Point>(*this);
        }
    };

    template
    <
        typename Point,
        typename PointDistanceStrategy,
        typename LessCompare
            = std::less
                <
                    typename strategy::distance::services::return_type
                        <
                            PointDistanceStrategy,
                            Point, Point
                        >::type
                >
    >
    class douglas_peucker
        : LessCompare // for empty base optimization
    {
    public :

        // See also ticket 5954 https://svn.boost.org/trac/boost/ticket/5954
        // Comparable is currently not possible here because it has to be compared to the squared of max_distance, and more.
        // For now we have to take the real distance.
        typedef PointDistanceStrategy distance_strategy_type;
        // typedef typename strategy::distance::services::comparable_type<PointDistanceStrategy>::type distance_strategy_type;

        typedef typename strategy::distance::services::return_type
                         <
                             distance_strategy_type,
                             Point, Point
                         >::type distance_type;

        douglas_peucker()
        {}

        douglas_peucker(LessCompare const& less_compare)
            : LessCompare(less_compare)
        {}

    private :
        typedef detail::douglas_peucker_point<Point> dp_point_type;
        typedef typename std::vector<dp_point_type>::iterator iterator_type;


        LessCompare const& less() const
        {
            return *this;
        }

        inline void consider(iterator_type begin,
                             iterator_type end,
                             distance_type const& max_dist,
                             int& n,
                             distance_strategy_type const& ps_distance_strategy) const
        {
            std::size_t size = end - begin;

            // size must be at least 3
            // because we want to consider a candidate point in between
            if (size <= 2)
            {
#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
                if (begin != end)
                {
                    std::cout << "ignore between " << dsv(begin->p)
                        << " and " << dsv((end - 1)->p)
                        << " size=" << size << std::endl;
                }
                std::cout << "return because size=" << size << std::endl;
#endif
                return;
            }

            iterator_type last = end - 1;

#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
            std::cout << "find between " << dsv(begin->p)
                << " and " << dsv(last->p)
                << " size=" << size << std::endl;
#endif


            // Find most far point, compare to the current segment
            //geometry::segment<Point const> s(begin->p, last->p);
            distance_type md(-1.0); // any value < 0
            iterator_type candidate;
            for(iterator_type it = begin + 1; it != last; ++it)
            {
                distance_type dist = ps_distance_strategy.apply(it->p, begin->p, last->p);

#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
                std::cout << "consider " << dsv(it->p)
                    << " at " << double(dist)
                    << ((dist > max_dist) ? " maybe" : " no")
                    << std::endl;

#endif
                if ( less()(md, dist) )
                {
                    md = dist;
                    candidate = it;
                }
            }

            // If a point is found, set the include flag
            // and handle segments in between recursively
            if ( less()(max_dist, md) )
            {
#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
                std::cout << "use " << dsv(candidate->p) << std::endl;
#endif

                candidate->included = true;
                n++;

                consider(begin, candidate + 1, max_dist, n, ps_distance_strategy);
                consider(candidate, end, max_dist, n, ps_distance_strategy);
            }
        }


    public :

        template <typename Range, typename OutputIterator>
        inline OutputIterator apply(Range const& range,
                                    OutputIterator out,
                                    distance_type max_distance) const
        {
#ifdef BOOST_GEOMETRY_DEBUG_DOUGLAS_PEUCKER
                std::cout << "max distance: " << max_distance
                          << std::endl << std::endl;
#endif
            distance_strategy_type strategy;

            // Copy coordinates, a vector of references to all points
            std::vector<dp_point_type> ref_candidates(boost::begin(range),
                            boost::end(range));

            // Include first and last point of line,
            // they are always part of the line
            int n = 2;
            ref_candidates.front().included = true;
            ref_candidates.back().included = true;

            // Get points, recursively, including them if they are further away
            // than the specified distance
            consider(boost::begin(ref_candidates), boost::end(ref_candidates), max_distance, n, strategy);

            // Copy included elements to the output
            for(typename std::vector<dp_point_type>::const_iterator it
                            = boost::begin(ref_candidates);
                it != boost::end(ref_candidates);
                ++it)
            {
                if (it->included)
                {
                    // copy-coordinates does not work because OutputIterator
                    // does not model Point (??)
                    //geometry::convert(it->p, *out);
                    *out = it->p;
                    out++;
                }
            }
            return out;
        }

    };
}
#endif // DOXYGEN_NO_DETAIL


/*!
\brief Implements the simplify algorithm.
\ingroup strategies
\details The douglas_peucker strategy simplifies a linestring, ring or
    vector of points using the well-known Douglas-Peucker algorithm.
\tparam Point the point type
\tparam PointDistanceStrategy point-segment distance strategy to be used
\note This strategy uses itself a point-segment-distance strategy which
    can be specified
\author Barend and Maarten, 1995/1996
\author Barend, revised for Generic Geometry Library, 2008
*/

/*
For the algorithm, see for example:
 - http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
 - http://www2.dcs.hull.ac.uk/CISRG/projects/Royal-Inst/demos/dp.html
*/
template
<
    typename Point,
    typename PointDistanceStrategy
>
class douglas_peucker
{
public :

    typedef PointDistanceStrategy distance_strategy_type;

    typedef typename detail::douglas_peucker
        <
            Point,
            PointDistanceStrategy
        >::distance_type distance_type;

    template <typename Range, typename OutputIterator>
    static inline OutputIterator apply(Range const& range,
                                       OutputIterator out,
                                       distance_type const& max_distance)
    {
        namespace services = strategy::distance::services;

        typedef typename services::comparable_type
            <
                PointDistanceStrategy
            >::type comparable_distance_strategy_type;

        return detail::douglas_peucker
            <
                Point, comparable_distance_strategy_type
            >().apply(range, out,
                      services::result_from_distance
                          <
                              comparable_distance_strategy_type, Point, Point
                          >::apply(comparable_distance_strategy_type(),
                                   max_distance)
                      );
    }

};

}} // namespace strategy::simplify


namespace traits {

template <typename P>
struct point_type<geometry::strategy::simplify::detail::douglas_peucker_point<P> >
{
    typedef P type;
};

} // namespace traits


}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGY_AGNOSTIC_SIMPLIFY_DOUGLAS_PEUCKER_HPP
