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

// 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_ALGORITHMS_DETAIL_DISTANCE_SEGMENT_TO_BOX_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_SEGMENT_TO_BOX_HPP

#include <functional>
#include <vector>

#include <boost/assert.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/mpl/if.hpp>

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/coordinate_dimension.hpp>
#include <boost/geometry/core/point_type.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/util/calculation_type.hpp>
#include <boost/geometry/util/math.hpp>

#include <boost/geometry/strategies/distance.hpp>
#include <boost/geometry/strategies/distance_comparable_to_regular.hpp>
#include <boost/geometry/strategies/tags.hpp>

#include <boost/geometry/policies/compare.hpp>

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

#include <boost/geometry/algorithms/detail/assign_box_corners.hpp>
#include <boost/geometry/algorithms/detail/assign_indexed_point.hpp>
#include <boost/geometry/algorithms/detail/distance/default_strategies.hpp>
#include <boost/geometry/algorithms/detail/distance/point_to_geometry.hpp>

#include <boost/geometry/algorithms/dispatch/distance.hpp>



namespace boost { namespace geometry
{


#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace distance
{


template
<
    typename Segment,
    typename Box,
    typename Strategy,
    bool UsePointBoxStrategy = false
>
class segment_to_box_2D_generic
{
private:
    typedef typename point_type<Segment>::type segment_point;
    typedef typename point_type<Box>::type box_point;

    typedef typename strategy::distance::services::comparable_type
        <
            Strategy
        >::type comparable_strategy;

    typedef typename strategy::distance::services::return_type
        <
            comparable_strategy, segment_point, box_point
        >::type comparable_return_type;
    
    typedef point_to_range
        <
            segment_point, std::vector<box_point>, open, comparable_strategy
        > point_to_box_boundary;

public:
    typedef typename strategy::distance::services::return_type
        <
            Strategy, segment_point, box_point
        >::type return_type;

    static inline return_type apply(Segment const& segment,
                                    Box const& box,
                                    Strategy const& strategy,
                                    bool check_intersection = true)
    {
        if ( check_intersection && geometry::intersects(segment, box) )
        {
            return 0;
        }

        comparable_strategy cstrategy =
            strategy::distance::services::get_comparable
                <
                    Strategy
                >::apply(strategy);
        boost::ignore_unused_variable_warning(cstrategy);

        // get segment points
        segment_point p[2];
        detail::assign_point_from_index<0>(segment, p[0]);
        detail::assign_point_from_index<1>(segment, p[1]);

        // get box points
        std::vector<box_point> box_points(4);
        detail::assign_box_corners_oriented<true>(box, box_points);
 
        comparable_return_type cd[6];
        for (unsigned int i = 0; i < 4; ++i)
        {
            cd[i] = cstrategy.apply(box_points[i], p[0], p[1]);
        }
        cd[4] = point_to_box_boundary::apply(p[0], box_points, cstrategy);
        cd[5] = point_to_box_boundary::apply(p[1], box_points, cstrategy);

        return strategy::distance::services::comparable_to_regular
            <
                comparable_strategy, Strategy, Segment, Box
            >::apply( *std::min_element(cd, cd + 6) );
    }
};


template
<
    typename Segment,
    typename Box,
    typename Strategy
>
class segment_to_box_2D_generic<Segment, Box, Strategy, true>
{
private:
    typedef typename point_type<Segment>::type segment_point;
    typedef typename point_type<Box>::type box_point;

    typedef typename strategy::distance::services::comparable_type
        <
            Strategy
        >::type comparable_strategy;

    typedef typename strategy::distance::services::return_type
        <
            comparable_strategy, segment_point, box_point
        >::type comparable_return_type;

    typedef typename strategy::distance::services::comparable_type
        <
            typename detail::distance::default_strategy
            <
                segment_point, Box
            >::type
        >::type point_box_comparable_strategy;

public:
    typedef typename strategy::distance::services::return_type
        <
            Strategy, segment_point, box_point
        >::type return_type;

    static inline return_type apply(Segment const& segment,
                                    Box const& box,
                                    Strategy const& strategy,
                                    bool check_intersection = true)
    {
        if ( check_intersection && geometry::intersects(segment, box) )
        {
            return 0;
        }

        comparable_strategy cstrategy =
            strategy::distance::services::get_comparable
                <
                    Strategy
                >::apply(strategy);
        boost::ignore_unused_variable_warning(cstrategy);

        // get segment points
        segment_point p[2];
        detail::assign_point_from_index<0>(segment, p[0]);
        detail::assign_point_from_index<1>(segment, p[1]);

        // get box points
        std::vector<box_point> box_points(4);
        detail::assign_box_corners_oriented<true>(box, box_points);
 
        comparable_return_type cd[6];
        for (unsigned int i = 0; i < 4; ++i)
        {
            cd[i] = cstrategy.apply(box_points[i], p[0], p[1]);
        }

        point_box_comparable_strategy pb_cstrategy;
        boost::ignore_unused_variable_warning(pb_cstrategy);
        cd[4] = pb_cstrategy.apply(p[0], box);
        cd[5] = pb_cstrategy.apply(p[1], box);

        return strategy::distance::services::comparable_to_regular
            <
                comparable_strategy, Strategy, Segment, Box
            >::apply( *std::min_element(cd, cd + 6) );
    }
};




template
<
    typename ReturnType,
    typename SegmentPoint,
    typename BoxPoint,
    typename PPStrategy,
    typename PSStrategy
>
class segment_to_box_2D
{
private:
    template <typename Result>
    struct cast_to_result
    {
        template <typename T>
        static inline Result apply(T const& t)
        {
            return boost::numeric_cast<Result>(t);
        }
    };


    template <typename T, bool IsLess /* true */>
    struct compare_less_equal
    {
        typedef compare_less_equal<T, !IsLess> other;

        template <typename T1, typename T2>
        inline bool operator()(T1 const& t1, T2 const& t2) const
        {
            return std::less_equal<T>()(cast_to_result<T>::apply(t1),
                                        cast_to_result<T>::apply(t2));
        }
    };

    template <typename T>
    struct compare_less_equal<T, false>
    {
        typedef compare_less_equal<T, true> other;

        template <typename T1, typename T2>
        inline bool operator()(T1 const& t1, T2 const& t2) const
        {
            return std::greater_equal<T>()(cast_to_result<T>::apply(t1),
                                           cast_to_result<T>::apply(t2));
        }
    };


    template <typename LessEqual>
    struct other_compare
    {
        typedef typename LessEqual::other type;
    };


    // it is assumed here that p0 lies to the right of the box (so the
    // entire segment lies to the right of the box)
    template <typename LessEqual>
    struct right_of_box
    {
        static inline ReturnType apply(SegmentPoint const& p0,
                                       SegmentPoint const& p1,
                                       BoxPoint const& bottom_right,
                                       BoxPoint const& top_right,
                                       PPStrategy const& pp_strategy,
                                       PSStrategy const& ps_strategy)
        {
            // the implementation below is written for non-negative slope
            // segments
            //
            // for negative slope segments swap the roles of bottom_right
            // and top_right and use greater_equal instead of less_equal.

            typedef cast_to_result<ReturnType> cast;

            LessEqual less_equal;

            if ( less_equal(geometry::get<1>(top_right), geometry::get<1>(p0)) )
            {
                // closest box point is the top-right corner
                return cast::apply(pp_strategy.apply(p0, top_right));
            }
            else if ( less_equal(geometry::get<1>(bottom_right),
                                 geometry::get<1>(p0)) )
            {
                // distance is realized between p0 and right-most
                // segment of box
                ReturnType diff = cast::apply(geometry::get<0>(p0))
                    - cast::apply(geometry::get<0>(bottom_right));
                return diff * diff;
            }
            else
            {
                // distance is realized between the bottom-right
                // corner of the box and the segment
                return cast::apply(ps_strategy.apply(bottom_right, p0, p1));
            }
        }
    };


    // it is assumed here that p0 lies above the box (so the
    // entire segment lies above the box)
    template <typename LessEqual>
    struct above_of_box
    {
        static inline ReturnType apply(SegmentPoint const& p0,
                                       SegmentPoint const& p1,
                                       BoxPoint const& top_left,
                                       PSStrategy const& ps_strategy)
        {
            // the segment lies above the box

            typedef cast_to_result<ReturnType> cast;

            LessEqual less_equal;

            // p0 is above the upper segment of the box
            // (and inside its band)
            if ( less_equal(geometry::get<0>(top_left), geometry::get<0>(p0)) )
            {
                ReturnType diff = cast::apply(geometry::get<1>(p0))
                    - cast::apply(geometry::get<1>(top_left));
                return diff * diff;
            }

            // p0 is to the left of the box, but p1 is above the box
            // in this case the distance is realized between the
            // top-left corner of the box and the segment
            return cast::apply(ps_strategy.apply(top_left, p0, p1));
        }
    };


    template <typename LessEqual>
    struct check_right_left_of_box
    {
        static inline bool apply(SegmentPoint const& p0,
                                 SegmentPoint const& p1,
                                 BoxPoint const& top_left,
                                 BoxPoint const& top_right,
                                 BoxPoint const& bottom_left,
                                 BoxPoint const& bottom_right,
                                 PPStrategy const& pp_strategy,
                                 PSStrategy const& ps_strategy,
                                 ReturnType& result)
        {
            // p0 lies to the right of the box
            if ( geometry::get<0>(p0) >= geometry::get<0>(top_right) )
            {
                result = right_of_box
                    <
                        LessEqual
                    >::apply(p0, p1, bottom_right, top_right,
                             pp_strategy, ps_strategy);
                return true;
            }

            // p1 lies to the left of the box
            if ( geometry::get<0>(p1) <= geometry::get<0>(bottom_left) )
            {
                result = right_of_box
                    <
                        typename other_compare<LessEqual>::type
                    >::apply(p1, p0, top_left, bottom_left,
                             pp_strategy, ps_strategy);
                return true;
            }

            return false;
        }
    };


    template <typename LessEqual>
    struct check_above_below_of_box
    {
        static inline bool apply(SegmentPoint const& p0,
                                 SegmentPoint const& p1,
                                 BoxPoint const& top_left,
                                 BoxPoint const& top_right,
                                 BoxPoint const& bottom_left,
                                 BoxPoint const& bottom_right,
                                 PSStrategy const& ps_strategy,
                                 ReturnType& result)
        {
            // the segment lies below the box
            if ( geometry::get<1>(p1) < geometry::get<1>(bottom_left) )
            {
                result = above_of_box
                    <
                        typename other_compare<LessEqual>::type
                    >::apply(p1, p0, bottom_right, ps_strategy);
                return true;
            }

            // the segment lies above the box
            if ( geometry::get<1>(p0) > geometry::get<1>(top_right) )
            {
                result = above_of_box
                    <
                        LessEqual
                    >::apply(p0, p1, top_left, ps_strategy);
                return true;
            }
            return false;
        }
    };

    struct check_generic_position
    {
        static inline bool apply(SegmentPoint const& p0,
                                 SegmentPoint const& p1,
                                 BoxPoint const& bottom_left0,
                                 BoxPoint const& top_right0,
                                 BoxPoint const& bottom_left1,
                                 BoxPoint const& top_right1,
                                 BoxPoint const& corner1,
                                 BoxPoint const& corner2,
                                 PSStrategy const& ps_strategy,
                                 ReturnType& result)
        {
            typedef cast_to_result<ReturnType> cast;

            ReturnType diff0 = cast::apply(geometry::get<0>(p1))
                - cast::apply(geometry::get<0>(p0));
            ReturnType t_min0 = cast::apply(geometry::get<0>(bottom_left0))
                - cast::apply(geometry::get<0>(p0));
            ReturnType t_max0 = cast::apply(geometry::get<0>(top_right0))
                - cast::apply(geometry::get<0>(p0));

            ReturnType diff1 = cast::apply(geometry::get<1>(p1))
                - cast::apply(geometry::get<1>(p0));
            ReturnType t_min1 = cast::apply(geometry::get<1>(bottom_left1))
                - cast::apply(geometry::get<1>(p0));
            ReturnType t_max1 = cast::apply(geometry::get<1>(top_right1))
                - cast::apply(geometry::get<1>(p0));

            if ( diff1 < 0 )
            {
                diff1 = -diff1;
                t_min1 = -t_min1;
                t_max1 = -t_max1;
            }

            //  t_min0 > t_max1
            if ( t_min0 * diff1 > t_max1 * diff0 )
            {
                result = cast::apply(ps_strategy.apply(corner1, p0, p1));
                return true;
            }

            //  t_max0 < t_min1
            if ( t_max0 * diff1 < t_min1 * diff0 )
            {
                result = cast::apply(ps_strategy.apply(corner2, p0, p1));
                return true;
            }
            return false;
        }
    };

    static inline ReturnType
    non_negative_slope_segment(SegmentPoint const& p0,
                               SegmentPoint const& p1,
                               BoxPoint const& top_left,
                               BoxPoint const& top_right,
                               BoxPoint const& bottom_left,
                               BoxPoint const& bottom_right,
                               PPStrategy const& pp_strategy,
                               PSStrategy const& ps_strategy)
    {
        typedef compare_less_equal<ReturnType, true> less_equal;

        // assert that the segment has non-negative slope
        BOOST_ASSERT( ( math::equals(geometry::get<0>(p0), geometry::get<0>(p1))
                        && geometry::get<1>(p0) < geometry::get<1>(p1))
                      ||
                      ( geometry::get<0>(p0) < geometry::get<0>(p1)
                        && geometry::get<1>(p0) <= geometry::get<1>(p1) )
                      );

        ReturnType result(0);

        if ( check_right_left_of_box
                 <
                     less_equal
                 >::apply(p0, p1,
                          top_left, top_right, bottom_left, bottom_right,
                          pp_strategy, ps_strategy, result) )
        {
            return result;
        }

        if ( check_above_below_of_box
                 <
                     less_equal
                 >::apply(p0, p1,
                          top_left, top_right, bottom_left, bottom_right,
                          ps_strategy, result) )
        {
            return result;
        }

        if ( check_generic_position::apply(p0, p1,
                                           bottom_left, top_right,
                                           bottom_left, top_right,
                                           top_left, bottom_right,
                                           ps_strategy, result) )
        {
            return result;
        }

        // in all other cases the box and segment intersect, so return 0
        return result;
    }


    static inline ReturnType
    negative_slope_segment(SegmentPoint const& p0,
                           SegmentPoint const& p1,
                           BoxPoint const& top_left,
                           BoxPoint const& top_right,
                           BoxPoint const& bottom_left,
                           BoxPoint const& bottom_right,
                           PPStrategy const& pp_strategy,
                           PSStrategy const& ps_strategy)
    {
        typedef compare_less_equal<ReturnType, false> greater_equal;

        // assert that the segment has negative slope
        BOOST_ASSERT( geometry::get<0>(p0) < geometry::get<0>(p1)
                      && geometry::get<1>(p0) > geometry::get<1>(p1) );

        ReturnType result(0);

        if ( check_right_left_of_box
                 <
                     greater_equal
                 >::apply(p0, p1,
                          bottom_left, bottom_right, top_left, top_right,
                          pp_strategy, ps_strategy, result) )
        {
            return result;
        }

        if ( check_above_below_of_box
                 <
                     greater_equal
                 >::apply(p1, p0,
                          top_right, top_left, bottom_right, bottom_left,
                          ps_strategy, result) )
        {
            return result;
        }

        if ( check_generic_position::apply(p0, p1,
                                           bottom_left, top_right,
                                           top_right, bottom_left,
                                           bottom_left, top_right,
                                           ps_strategy, result) )
        {
            return result;
        }

        // in all other cases the box and segment intersect, so return 0
        return result;
    }

public:
    static inline ReturnType apply(SegmentPoint const& p0,
                                   SegmentPoint const& p1,
                                   BoxPoint const& top_left,
                                   BoxPoint const& top_right,
                                   BoxPoint const& bottom_left,
                                   BoxPoint const& bottom_right,
                                   PPStrategy const& pp_strategy,
                                   PSStrategy const& ps_strategy)
    {
        BOOST_ASSERT( geometry::less<SegmentPoint>()(p0, p1) );

        if ( geometry::get<0>(p0) < geometry::get<0>(p1)
             && geometry::get<1>(p0) > geometry::get<1>(p1) )
        {
            return negative_slope_segment(p0, p1,
                                          top_left, top_right,
                                          bottom_left, bottom_right,
                                          pp_strategy, ps_strategy);
        }

        return non_negative_slope_segment(p0, p1,
                                          top_left, top_right,
                                          bottom_left, bottom_right,
                                          pp_strategy, ps_strategy);
    }
};


//=========================================================================


template
<
    typename Segment,
    typename Box,
    typename std::size_t Dimension,
    typename PPStrategy,
    typename PSStrategy
>
class segment_to_box
    : not_implemented<Segment, Box>
{};


template
<
    typename Segment,
    typename Box,
    typename PPStrategy,
    typename PSStrategy
>
class segment_to_box<Segment, Box, 2, PPStrategy, PSStrategy>
{
private:
    typedef typename point_type<Segment>::type segment_point;
    typedef typename point_type<Box>::type box_point;

    typedef typename strategy::distance::services::comparable_type
        <
            PPStrategy
        >::type pp_comparable_strategy;

    typedef typename strategy::distance::services::comparable_type
        <
            PSStrategy
        >::type ps_comparable_strategy;

    typedef typename strategy::distance::services::return_type
        <
            ps_comparable_strategy, segment_point, box_point
        >::type comparable_return_type;
public:
    typedef typename strategy::distance::services::return_type
        <
            PSStrategy, segment_point, box_point
        >::type return_type;

    static inline return_type apply(Segment const& segment,
                                    Box const& box,
                                    PPStrategy const& pp_strategy,
                                    PSStrategy const& ps_strategy)
    {
        segment_point p[2];
        detail::assign_point_from_index<0>(segment, p[0]);
        detail::assign_point_from_index<1>(segment, p[1]);

        if ( geometry::equals(p[0], p[1]) )
        {
            typedef typename boost::mpl::if_
                <
                    boost::is_same
                        <
                            ps_comparable_strategy,
                            PSStrategy
                        >,
                    typename strategy::distance::services::comparable_type
                        <
                            typename detail::distance::default_strategy
                                <
                                    segment_point, Box
                                >::type
                        >::type,
                    typename detail::distance::default_strategy
                        <
                            segment_point, Box
                        >::type
                >::type point_box_strategy_type;

            return dispatch::distance
                <
                    segment_point,
                    Box,
                    point_box_strategy_type
                >::apply(p[0], box, point_box_strategy_type());
        }

        box_point top_left, top_right, bottom_left, bottom_right;
        detail::assign_box_corners(box, bottom_left, bottom_right,
                                   top_left, top_right);

        pp_comparable_strategy c_pp_strategy =
            strategy::distance::services::get_comparable
                <
                    PPStrategy
                >::apply(pp_strategy);

        ps_comparable_strategy c_ps_strategy =
            strategy::distance::services::get_comparable
                <
                    PSStrategy
                >::apply(ps_strategy);

        comparable_return_type cd;

        if ( geometry::less<segment_point>()(p[0], p[1]) )
        {
            cd = segment_to_box_2D
                <
                    return_type,
                    segment_point,
                    box_point,
                    pp_comparable_strategy,
                    ps_comparable_strategy
                >::apply(p[0], p[1],
                         top_left, top_right, bottom_left, bottom_right,
                         c_pp_strategy,
                         c_ps_strategy);
        }
        else
        {
            cd = segment_to_box_2D
                <
                    return_type,
                    segment_point,
                    box_point,
                    pp_comparable_strategy,
                    ps_comparable_strategy
                >::apply(p[1], p[0],
                         top_left, top_right, bottom_left, bottom_right,
                         c_pp_strategy,
                         c_ps_strategy);
        }

        return strategy::distance::services::comparable_to_regular
            <
                ps_comparable_strategy, PSStrategy, Segment, Box
            >::apply( cd );
    }
};


}} // namespace detail::distance
#endif // DOXYGEN_NO_DETAIL


#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{


template <typename Segment, typename Box, typename Strategy>
struct distance
    <
        Segment, Box, Strategy, segment_tag, box_tag,
        strategy_tag_distance_point_segment, false
    >
{
    typedef typename strategy::distance::services::return_type
        <
            Strategy,
            typename point_type<Segment>::type,
            typename point_type<Box>::type
        >::type return_type;


    static inline return_type apply(Segment const& segment,
                                    Box const& box,
                                    Strategy const& strategy)
    {
        assert_dimension_equal<Segment, Box>();

        typedef typename detail::distance::default_strategy
            <
                typename point_type<Segment>::type,
                typename point_type<Box>::type
            >::type pp_strategy_type;

        return detail::distance::segment_to_box
            <
                Segment,
                Box,
                dimension<Segment>::value,
                pp_strategy_type,
                Strategy
            >::apply(segment, box, pp_strategy_type(), strategy);
    }
};



} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH


}} // namespace boost::geometry


#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_SEGMENT_TO_BOX_HPP
