// Boost.Geometry (aka GGL, Generic Geometry Library)
//
// Copyright (c) 2012-2014 Barend Gehrels, 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)

#include <geometry_test_common.hpp>

#include <boost/algorithm/string/trim.hpp>
#include <boost/assign/list_of.hpp>

#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/algorithms/detail/get_left_turns.hpp>

#if defined(TEST_WITH_SVG)
# include <boost/geometry/io/svg/svg_mapper.hpp>
#endif

namespace bglt = boost::geometry::detail::left_turns;

#if defined(TEST_WITH_SVG)
template <typename Point>
inline Point further_than(Point const& p, Point const& origin, int mul, int div)
{
    typedef Point vector_type;

    vector_type v = p;
    bg::subtract_point(v, origin);

    bg::divide_value(v, div);
    bg::multiply_value(v, mul);
    Point result = origin;
    bg::add_point(result, v);
    return result;
}

inline std::string get_color(int index)
{
    switch (index)
    {
        case 0 : return "rgb(0,192,0)";
        case 1 : return "rgb(0,0,255)";
        case 2 : return "rgb(255,0,0)";
        case 3 : return "rgb(255,255,0)";
    }
    return "rgb(128,128,128)";
}
#endif


template <typename Point>
void test_one(std::string const& caseid,
                Point const& p,
                std::vector<bglt::turn_angle_info<Point> > const& angles,
                std::string const& expected_sorted_indices,
                std::string const& expected_left_indices)
{
    typedef Point vector_type;

    std::vector<bglt::angle_info<Point> > sorted;
    for (typename std::vector<bglt::turn_angle_info<Point> >::const_iterator it =
        angles.begin(); it != angles.end(); ++it)
    {
        for (int i = 0; i < 2; i++)
        {
            bglt::angle_info<Point> info(it->seg_id, i == 0, it->points[i]);
            sorted.push_back(info);
        }
    }

    // Sort on angle
    std::sort(sorted.begin(), sorted.end(), bglt::angle_less<Point>(p));

    // Block all turns on the right side of any turn
    bglt::block_turns_on_right_sides(angles, sorted);

    // Check the sorting
    {
        std::ostringstream out;
        out << std::boolalpha;
        for (typename std::vector<bglt::angle_info<Point> >::const_iterator it =
            sorted.begin(); it != sorted.end(); ++it)
        {
            out << " " << it->seg_id.segment_index
                << "-" << it->incoming;
        }
        std::string detected = boost::trim_copy(out.str());
        BOOST_CHECK_EQUAL(expected_sorted_indices, detected);
    }


    // Check outgoing lines
    std::vector<bglt::left_turn> seg_ids;
    bglt::get_left_turns(sorted, p, seg_ids);
    {
        std::ostringstream out;
        out << std::boolalpha;
        for (std::vector<bglt::left_turn>::const_iterator it =
            seg_ids.begin(); it != seg_ids.end(); ++it)
        {
            out
                << " " << it->from.segment_index
                << "->" << it->to.segment_index
                ;
        }
        std::string detected = boost::trim_copy(out.str());
        BOOST_CHECK_EQUAL(expected_left_indices, detected);
    }

#if defined(TEST_WITH_SVG)
    {
        std::ostringstream filename;
        filename << "get_left_turns_" << caseid
            << "_" << string_from_type<typename bg::coordinate_type<Point>::type>::name()
            << ".svg";

        std::ofstream svg(filename.str().c_str());

        bg::svg_mapper<Point> mapper(svg, 500, 500);
        mapper.add(p);
        for (typename std::vector<bglt::turn_angle_info<Point> >::const_iterator it =
            angles.begin(); it != angles.end(); ++it)
        {
            // Add a point further then it->to_point, just for the mapping
            for (int i = 0; i < 2; i++)
            {
                mapper.add(further_than(it->points[i], p, 12, 10));
            }
        }

        int color_index = 0;
        typedef bg::model::referring_segment<Point const> segment_type;
        for (typename std::vector<bglt::turn_angle_info<Point> >::const_iterator it =
            angles.begin(); it != angles.end(); ++it, color_index++)
        {
            for (int i = 0; i < 2; i++)
            {
                std::string style = "opacity:0.5;stroke-width:1;stroke:rgb(0,0,0);fill:" + get_color(color_index);

                bool const incoming = i == 0;
                Point const& pf = incoming ? it->points[i] : p;
                Point const& pt = incoming ? p : it->points[i];
                vector_type v = pt;
                bg::subtract_point(v, pf);

                bg::divide_value(v, 10.0);

                // Generate perpendicular vector to right-side
                vector_type perpendicular;
                bg::set<0>(perpendicular, bg::get<1>(v));
                bg::set<1>(perpendicular, -bg::get<0>(v));

                bg::model::ring<Point> ring;
                ring.push_back(pf);
                ring.push_back(pt);

                // Extra point at 9/10
                {
                    Point pe = pt;
                    bg::add_point(pe, perpendicular);
                    ring.push_back(pe);
                }
                {
                    Point pe = pf;
                    bg::add_point(pe, perpendicular);
                    ring.push_back(pe);
                }
                ring.push_back(pf);

                mapper.map(ring, style);

                segment_type s(pf, pt);
                mapper.map(s, "opacity:0.9;stroke-width:4;stroke:rgb(0,0,0);");
            }
        }

        // Output angles for left-turns
        for (std::vector<bglt::left_turn>::const_iterator ltit =
            seg_ids.begin(); ltit != seg_ids.end(); ++ltit)
        {
            for (typename std::vector<bglt::angle_info<Point> >::const_iterator sit =
                sorted.begin(); sit != sorted.end(); ++sit, color_index++)
            {
                Point pf, pt;
                int factor = 0;
                if (sit->seg_id == ltit->from && sit->incoming)
                {
                    pf = sit->point;
                    pt = p;
                    factor = -1; // left side
                }
                else if (sit->seg_id == ltit->to && ! sit->incoming)
                {
                    pf = p;
                    pt = sit->point;
                    factor = -1; // left side
                }
                if (factor != 0)
                {
                    vector_type v = pt;
                    bg::subtract_point(v, pf);
                    bg::divide_value(v, 10.0);

                    // Generate perpendicular vector to right-side
                    vector_type perpendicular;
                    bg::set<0>(perpendicular, factor * bg::get<1>(v));
                    bg::set<1>(perpendicular, -factor * bg::get<0>(v));

                    bg::add_point(pf, v);
                    bg::subtract_point(pt, v);

                    bg::add_point(pf, perpendicular);
                    bg::add_point(pt, perpendicular);

                    segment_type s(pf, pt);
                    mapper.map(s, "opacity:0.9;stroke-width:4;stroke:rgb(255,0,0);");
                }
            }
        }

        // Output texts with info about sorted/blocked
        int index = 0;
        for (typename std::vector<bglt::angle_info<Point> >::const_iterator it =
            sorted.begin(); it != sorted.end(); ++it, ++index)
        {
            std::ostringstream out;
            out << std::boolalpha;
            out << " seg:" << it->seg_id.segment_index
                << " " << (it->incoming ? "in" : "out")
                << " idx:" << index
                << (it->blocked ? " blocked" : "")
                ;
            mapper.text(further_than(it->point, p, 11, 10), out.str(), "fill:rgb(0,0,0);font-family='Verdana'");
        }

        mapper.map(p, "fill:rgb(255,0,0)");

    }

#endif

}


template <typename P>
void test_all()
{
    using bglt::turn_angle_info;

    test_one<P>("cross",
        bg::make<P>(50, 50),   // ip
        boost::assign::list_of
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 1), bg::make<P>(100, 100), bg::make<P>(0, 0)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 2), bg::make<P>(100, 0), bg::make<P>(0, 100)))
            , "1-true 2-true 1-false 2-false"
            , "2->1"
        );

    test_one<P>("occupied",
        bg::make<P>(50, 50),   // ip
        boost::assign::list_of
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 1), bg::make<P>(100, 100), bg::make<P>(0, 0)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 2), bg::make<P>(100, 0), bg::make<P>(0, 100)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 3), bg::make<P>(0, 30), bg::make<P>(100, 70)))
            , "1-true 3-false 2-true 1-false 3-true 2-false"
            , ""
        );

    test_one<P>("uu",
        bg::make<P>(50, 50),   // ip
        boost::assign::list_of
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 1), bg::make<P>(0, 0), bg::make<P>(100, 0)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 2), bg::make<P>(100, 100), bg::make<P>(0, 100)))
            , "2-true 1-false 1-true 2-false"
            , "2->1 1->2"
        );

    test_one<P>("uu2",
        bg::make<P>(50, 50),   // ip
        boost::assign::list_of
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 1), bg::make<P>(0, 0), bg::make<P>(100, 0)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 2), bg::make<P>(100, 100), bg::make<P>(0, 100)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 3), bg::make<P>(0, 50), bg::make<P>(100, 50)))
            , "2-true 3-false 1-false 1-true 3-true 2-false"
            , "2->3 3->2"
        );

    test_one<P>("uu3",
        bg::make<P>(50, 50),   // ip
        boost::assign::list_of
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 1), bg::make<P>(0, 0), bg::make<P>(100, 0)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 2), bg::make<P>(100, 100), bg::make<P>(0, 100)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 3), bg::make<P>(50, 0), bg::make<P>(50, 100)))
            , "3-false 2-true 1-false 3-true 1-true 2-false"
            , "1->2"
        );

    test_one<P>("longer",
        bg::make<P>(50, 50),   // ip
        boost::assign::list_of
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 1), bg::make<P>(100, 100), bg::make<P>(0, 0)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 2), bg::make<P>(100, 0), bg::make<P>(0, 100)))
            (turn_angle_info<P>(bg::segment_identifier(0, -1, -1, 3), bg::make<P>(90, 10), bg::make<P>(10, 10)))
            , "1-true 2-true 3-true 1-false 3-false 2-false"
            , "3->1"
        );
}

int test_main( int , char* [] )
{
    test_all<bg::model::d2::point_xy<int> >();

    return 0;
}
