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

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// This file was modified by Oracle on 2014.
// Modifications copyright (c) 2014 Oracle and/or its affiliates.

// 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)

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

#ifndef BOOST_GEOMETRY_TEST_ALGORITHMS_OVERLAY_TEST_GET_TURNS_HPP
#define BOOST_GEOMETRY_TEST_ALGORITHMS_OVERLAY_TEST_GET_TURNS_HPP

#include <iostream>
#include <iomanip>

#include <geometry_test_common.hpp>

#include <boost/geometry/strategies/strategies.hpp>

#include <boost/geometry/algorithms/detail/overlay/get_turns.hpp>

#include <boost/geometry/algorithms/detail/overlay/debug_turn_info.hpp>

#include <boost/geometry/io/wkt/read.hpp>
#include <boost/geometry/io/wkt/write.hpp>

template <int Version = 0>
struct expected_pusher
{
    static const int version = Version;

    void push_back(std::string const& ex) { vec.push_back(ex); }
    expected_pusher & operator()(std::string const& ex)
    {
        push_back(ex);
        return *this;
    }

    typedef std::vector<std::string>::iterator iterator;
    typedef std::vector<std::string>::const_iterator const_iterator;

    iterator begin() { return vec.begin(); }
    iterator end() { return vec.end(); }
    const_iterator begin() const { return vec.begin(); }
    const_iterator end() const { return vec.end(); }

    std::vector<std::string> vec;
};

expected_pusher<1> expected(std::string const& ex)
{
    expected_pusher<1> res;
    return res(ex);
}

template <int Version>
struct equal_turn
{
    equal_turn(std::string const& s) : turn_ptr(&s) {}
    
    template <typename T>
    bool operator()(T const& t) const
    {
        BOOST_ASSERT(turn_ptr && turn_ptr->size() == 3);
        return bg::method_char(t.method) == (*turn_ptr)[0]
            && bg::operation_char(t.operations[0].operation) == (*turn_ptr)[1]
            && bg::operation_char(t.operations[1].operation) == (*turn_ptr)[2];
    }

    const std::string * turn_ptr;
};

template <>
struct equal_turn<1>
{
    equal_turn(std::string const& s) : turn_ptr(&s) {}
    
    template <typename T>
    bool operator()(T const& t) const
    {
        std::string::size_type count = turn_ptr->size();
        //BOOST_ASSERT(turn_ptr && count >= 1);
        return ( count > 0
               ? bg::method_char(t.method) == (*turn_ptr)[0]
               : true )
            && ( count > 1
               ? bg::operation_char(t.operations[0].operation) == (*turn_ptr)[1]
               : true )
            && ( count > 2
               ? bg::operation_char(t.operations[1].operation) == (*turn_ptr)[2]
               : true )
            && ( count > 3
               ? is_colinear_char(t.operations[0].is_collinear) == (*turn_ptr)[3]
               : true )
            && ( count > 4
               ? is_colinear_char(t.operations[1].is_collinear) == (*turn_ptr)[4]
               : true );
    }

    static inline char is_colinear_char(bool is_collinear)
    {
        return is_collinear ? '=' : '+';
    }

    const std::string * turn_ptr;
};

template <typename Geometry1, typename Geometry2, typename Expected>
void check_geometry_range(
    Geometry1 const& g1,
    Geometry2 const& g2,
    std::string const& wkt1,
    std::string const& wkt2,
    Expected const& expected)
{
    typedef bg::detail::no_rescale_policy robust_policy_type;
    typedef typename bg::point_type<Geometry2>::type point_type;

    typedef typename bg::segment_ratio_type<point_type, robust_policy_type>::type segment_ratio_type;

    typedef bg::detail::overlay::turn_info
        <
            typename bg::point_type<Geometry2>::type,
            segment_ratio_type,
            typename bg::detail::get_turns::turn_operation_type
            <
                Geometry1,
                Geometry2,
                segment_ratio_type
            >::type
        > turn_info;
    typedef bg::detail::overlay::assign_null_policy assign_policy_t;
    typedef bg::detail::get_turns::no_interrupt_policy interrupt_policy_t;

    std::vector<turn_info> turns;
    interrupt_policy_t interrupt_policy;
    robust_policy_type robust_policy;
    
    // Don't switch the geometries
    typedef bg::detail::get_turns::get_turn_info_type<Geometry1, Geometry2, assign_policy_t> turn_policy_t;
    bg::dispatch::get_turns
        <
            typename bg::tag<Geometry1>::type, typename bg::tag<Geometry2>::type,
            Geometry1, Geometry2, false, false,
            turn_policy_t
        >::apply(0, g1, 1, g2, robust_policy, turns, interrupt_policy);

    bool ok = boost::size(expected) == turns.size();

#ifdef BOOST_GEOMETRY_TEST_DEBUG
    std::vector<turn_info> turns_dbg = turns;
#endif

    BOOST_CHECK_MESSAGE(ok,
        "get_turns: " << wkt1 << " and " << wkt2
        << " -> Expected turns #: " << boost::size(expected) << " detected turns #: " << turns.size());

    for ( typename boost::range_iterator<Expected const>::type sit = boost::begin(expected) ;
          sit != boost::end(expected) ; ++sit)
    {
        typename std::vector<turn_info>::iterator
            it = std::find_if(turns.begin(), turns.end(), equal_turn<Expected::version>(*sit));

        if ( it != turns.end() )
            turns.erase(it);
        else
        {
            ok = false;
            BOOST_CHECK_MESSAGE(false,
                "get_turns: " << wkt1 << " and " << wkt2
                << " -> Expected turn: " << *sit << " not found");
        }
    }

#ifdef BOOST_GEOMETRY_TEST_DEBUG
    if ( !ok )
    {
        std::cout << "Coordinates: "
                  << typeid(typename bg::coordinate_type<Geometry1>::type).name()
                  << ", "
                  << typeid(typename bg::coordinate_type<Geometry2>::type).name()
                  << std::endl;
        std::cout << "Detected: ";
        if ( turns_dbg.empty() )
        {
            std::cout << "{empty}";
        }
        else
        {
            for ( typename std::vector<turn_info>::const_iterator it = turns_dbg.begin() ;
                  it != turns_dbg.end() ; ++it )
            {
                if ( it != turns_dbg.begin() )
                    std::cout << ", ";
                std::cout << bg::method_char(it->method);
                std::cout << bg::operation_char(it->operations[0].operation);
                std::cout << bg::operation_char(it->operations[1].operation);
                std::cout << equal_turn<1>::is_colinear_char(it->operations[0].is_collinear);
                std::cout << equal_turn<1>::is_colinear_char(it->operations[1].is_collinear);
            }
        }
        std::cout << std::endl;
    }
#endif
}

template <typename Geometry1, typename Geometry2, typename Expected>
void test_geometry_range(std::string const& wkt1, std::string const& wkt2,
                         Expected const& expected)
{
    Geometry1 geometry1;
    Geometry2 geometry2;
    bg::read_wkt(wkt1, geometry1);
    bg::read_wkt(wkt2, geometry2);
    check_geometry_range(geometry1, geometry2, wkt1, wkt2, expected);
}

template <typename G1, typename G2>
void test_geometry(std::string const& wkt1, std::string const& wkt2,
                   std::string const& ex0)
{
    test_geometry_range<G1, G2>(wkt1, wkt2, expected(ex0));
}

template <typename G1, typename G2>
void test_geometry(std::string const& wkt1, std::string const& wkt2,
    std::string const& ex0, std::string const& ex1)
{
    test_geometry_range<G1, G2>(wkt1, wkt2, expected(ex0)(ex1));
}

template <typename G1, typename G2>
void test_geometry(std::string const& wkt1, std::string const& wkt2,
    std::string const& ex0, std::string const& ex1, std::string const& ex2)
{
    test_geometry_range<G1, G2>(wkt1, wkt2, expected(ex0)(ex1)(ex2));
}

template <typename G1, typename G2, int Version>
void test_geometry(std::string const& wkt1, std::string const& wkt2,
                   expected_pusher<Version> const& expected)
{
    test_geometry_range<G1, G2>(wkt1, wkt2, expected);
}

#endif // BOOST_GEOMETRY_TEST_ALGORITHMS_OVERLAY_TEST_GET_TURNS_HPP
