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

// Copyright (c) 2014, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_TEST_DISTANCE_BRUTE_FORCE_HPP
#define BOOST_GEOMETRY_TEST_DISTANCE_BRUTE_FORCE_HPP

#include <iterator>

#include <boost/mpl/assert.hpp>
#include <boost/mpl/or.hpp>
#include <boost/range.hpp>

#include <boost/geometry/core/reverse_dispatch.hpp>
#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tag_cast.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/iterators/segment_iterator.hpp>

#include <boost/geometry/algorithms/distance.hpp>
#include <boost/geometry/algorithms/intersects.hpp>
#include <boost/geometry/algorithms/not_implemented.hpp>


namespace boost { namespace geometry
{

namespace unit_test
{

namespace detail { namespace distance_brute_force
{

struct distance_from_bg
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline
    typename distance_result<Geometry1, Geometry2, Strategy>::type
    apply(Geometry1 const& geometry1,
          Geometry2 const& geometry2,
          Strategy const& strategy)
    {
        BOOST_MPL_ASSERT
            ((typename boost::mpl::or_
                  <
                      boost::is_same<typename tag<Geometry1>::type, point_tag>,
                      boost::is_same<typename tag<Geometry1>::type, segment_tag>
                  >::type));

        BOOST_MPL_ASSERT
            ((typename boost::mpl::or_
                  <
                      boost::is_same<typename tag<Geometry2>::type, point_tag>,
                      boost::is_same<typename tag<Geometry2>::type, segment_tag>
                  >::type));

        return geometry::distance(geometry1, geometry2, strategy);
    }
};


template <typename Geometry1, typename Geometry2, typename Strategy>
inline
typename distance_result<Geometry1, Geometry2, Strategy>::type
bg_distance(Geometry1 const& geometry1,
            Geometry2 const& geometry2,
            Strategy const& strategy)
{
    return distance_from_bg::apply(geometry1, geometry2, strategy);
}


template <typename Policy>
struct one_to_many
{
    template <typename Geometry, typename Iterator, typename Strategy>
    static inline typename distance_result
        <
            Geometry,
            typename std::iterator_traits<Iterator>::value_type,
            Strategy
        >::type
    apply(Geometry const& geometry, Iterator begin, Iterator end,
          Strategy const& strategy)
    {
        typedef typename distance_result
        <
            Geometry,
            typename std::iterator_traits<Iterator>::value_type,
            Strategy
        >::type distance_type;

        bool first = true;
        distance_type d_min(0);
        for (Iterator it = begin; it != end; ++it, first = false)
        {
            distance_type d = Policy::apply(geometry, *it, strategy);

            if ( first || d < d_min )
            {
                d_min = d;
            }
        }
        return d_min;
    }
};



}} // namespace detail::distance_brute_force


namespace dispatch
{

template
<
    typename Geometry1,
    typename Geometry2,
    typename Strategy,
    typename Tag1 = typename tag_cast
        <
            typename tag<Geometry1>::type,
            segment_tag,
            linear_tag
        >::type,
    typename Tag2 = typename tag_cast
        <
            typename tag<Geometry2>::type,
            segment_tag,
            linear_tag
        >::type,
    bool Reverse = reverse_dispatch<Geometry1, Geometry2>::type::value
>
struct distance_brute_force
    : not_implemented<Geometry1, Geometry2>
{};


template
<
    typename Geometry1,
    typename Geometry2,
    typename Strategy,
    typename Tag1,
    typename Tag2
>
struct distance_brute_force<Geometry1, Geometry2, Strategy, Tag1, Tag2, true>
{
    static inline typename distance_result<Geometry1, Geometry2, Strategy>::type
    apply(Geometry1 const& geometry1,
          Geometry2 const& geometry2,
          Strategy const& strategy)
    {
        return distance_brute_force
            <
                Geometry2, Geometry1, Strategy
            >::apply(geometry2, geometry1, strategy);
    }
};


template 
<
    typename Point1,
    typename Point2,
    typename Strategy
>
struct distance_brute_force
<
    Point1, Point2, Strategy,
    point_tag, point_tag, false
> : detail::distance_brute_force::distance_from_bg
{};


template 
<
    typename Point,
    typename Segment,
    typename Strategy
>
struct distance_brute_force
<
    Point, Segment, Strategy,
    point_tag, segment_tag, false
> : detail::distance_brute_force::distance_from_bg
{};


template 
<
    typename Segment1,
    typename Segment2,
    typename Strategy
>
struct distance_brute_force
<
    Segment1, Segment2, Strategy,
    segment_tag, segment_tag, false
> : detail::distance_brute_force::distance_from_bg
{};


template 
<
    typename Point,
    typename Linear,
    typename Strategy
>
struct distance_brute_force
<
    Point, Linear, Strategy,
    point_tag, linear_tag, false
>
{
    typedef typename distance_result
        <
            Point, Linear, Strategy
        >::type distance_type;

    static inline distance_type apply(Point const& point,
                                      Linear const& linear,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(point,
                     geometry::segments_begin(linear),
                     geometry::segments_end(linear),
                     strategy);
    }
};


template 
<
    typename Point,
    typename MultiPoint,
    typename Strategy
>
struct distance_brute_force
<
    Point, MultiPoint, Strategy,
    point_tag, multi_point_tag, false
>
{
    typedef typename distance_result
        <
            Point, MultiPoint, Strategy
        >::type distance_type;

    static inline distance_type apply(Point const& p,
                                      MultiPoint const& mp,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(p, boost::begin(mp), boost::end(mp), strategy);
    }
};

template 
<
    typename MultiPoint1,
    typename MultiPoint2,
    typename Strategy
>
struct distance_brute_force
<
    MultiPoint1, MultiPoint2, Strategy,
    multi_point_tag, multi_point_tag, false
>
{
    typedef typename distance_result
        <
            MultiPoint1, MultiPoint2, Strategy
        >::type distance_type;

    static inline distance_type apply(MultiPoint1 const& mp1,
                                      MultiPoint2 const& mp2,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                distance_brute_force
                <
                    MultiPoint1,
                    typename boost::range_value<MultiPoint2>::type,
                    Strategy
                >
            >::apply(mp1, boost::begin(mp2), boost::end(mp2), strategy);
    }
};


template 
<
    typename MultiPoint,
    typename Linear,
    typename Strategy
>
struct distance_brute_force
<
    MultiPoint, Linear, Strategy,
    multi_point_tag, linear_tag, false
>
{
    typedef typename distance_result
        <
            MultiPoint, Linear, Strategy
        >::type distance_type;

    static inline distance_type apply(MultiPoint const& mp,
                                      Linear const& linear,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                distance_brute_force
                <
                    Linear,
                    typename boost::range_value<MultiPoint>::type,
                    Strategy
                >
            >::apply(linear, boost::begin(mp), boost::end(mp), strategy);
    }
};


template 
<
    typename Linear,
    typename MultiPoint,
    typename Strategy
>
struct distance_brute_force
<
    Linear, MultiPoint, Strategy,
    linear_tag, multi_point_tag, false
>
{
    typedef typename distance_result
        <
            Linear, MultiPoint, Strategy
        >::type distance_type;

    static inline distance_type apply(Linear const& linear,
                                      MultiPoint const& multipoint,
                                      Strategy const& strategy)
    {
        return distance_brute_force
            <
                MultiPoint, Linear, Strategy,
                multi_point_tag, linear_tag, false
            >::apply(multipoint, linear, strategy);
    }
};


template 
<
    typename MultiPoint,
    typename Segment,
    typename Strategy
>
struct distance_brute_force
<
    MultiPoint, Segment, Strategy,
    multi_point_tag, segment_tag, false
>
{
    typedef typename distance_result
        <
            MultiPoint, Segment, Strategy
        >::type distance_type;

    static inline distance_type apply(MultiPoint const& mp,
                                      Segment const& segment,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(segment, boost::begin(mp), boost::end(mp), strategy);
    }
};


template 
<
    typename Linear,
    typename Segment,
    typename Strategy
>
struct distance_brute_force
<
    Linear, Segment, Strategy,
    linear_tag, segment_tag, false
>
{
    typedef typename distance_result
        <
            Linear, Segment, Strategy
        >::type distance_type;

    static inline distance_type apply(Linear const& linear,
                                      Segment const& segment,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(segment,
                     geometry::segments_begin(linear),
                     geometry::segments_end(linear),
                     strategy);
    }
};


template 
<
    typename Linear1,
    typename Linear2,
    typename Strategy
>
struct distance_brute_force
<
    Linear1, Linear2, Strategy,
    linear_tag, linear_tag, false
>
{
    typedef typename distance_result
        <
            Linear1, Linear2, Strategy
        >::type distance_type;

    static inline distance_type apply(Linear1 const& linear1,
                                      Linear2 const& linear2,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                distance_brute_force
                    <
                        Linear1,
                        typename std::iterator_traits
                            <
                                segment_iterator<Linear2 const>
                            >::value_type,
                        Strategy
                    >
            >::apply(linear1,
                     geometry::segments_begin(linear2),
                     geometry::segments_end(linear2),
                     strategy);
    }
};

} // namespace dispatch





template <typename Geometry1, typename Geometry2, typename Strategy>
inline typename distance_result<Geometry1, Geometry2, Strategy>::type
distance_brute_force(Geometry1 const& geometry1,
                     Geometry2 const& geometry2,
                     Strategy const& strategy)
{
    return dispatch::distance_brute_force
        <
            Geometry1, Geometry2, Strategy
        >::apply(geometry1, geometry2, strategy);
}

} // namespace unit_test


}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_TEST_DISTANCE_BRUTE_FORCE_HPP
