// Boost.Geometry

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

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

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

#ifndef BOOST_GEOMETRY_CORE_VISIT_HPP
#define BOOST_GEOMETRY_CORE_VISIT_HPP

#include <utility>

#include <b/range/value_type.hpp>

#include <b/geometry/core/static_assert.hpp>
#include <b/geometry/util/type_traits_std.hpp>


namespace boost { namespace geometry { namespace traits
{

// TODO: Alternatives:
// - free function
//    template <typename Visitor, typename ...Variants>
//    auto visit(Visitor &&, Variants && ...) {}
//
// - additional Enable tparam
//    template <bool Enable, typename ...DynamicGeometries>
//    struct visit {};

template <typename ...DynamicGeometries>
struct visit
{
    BOOST_GEOMETRY_STATIC_ASSERT_FALSE(
        "Not implemented for these DynamicGeometries types.",
        DynamicGeometries...);
};

// By default call 1-parameter visit for each geometry
template <typename DynamicGeometry1, typename DynamicGeometry2>
struct visit<DynamicGeometry1, DynamicGeometry2>
{
    template <typename Function, typename Variant1, typename Variant2>
    static void apply(Function && function, Variant1 && variant1, Variant2 && variant2)
    {
        visit<util::remove_cref_t<Variant1>>::apply([&](auto && g1)
        {
            using ref1_t = decltype(g1);
            visit<util::remove_cref_t<Variant2>>::apply([&](auto && g2)
            {
                function(std::forward<ref1_t>(g1),
                         std::forward<decltype(g2)>(g2));
            }, std::forward<Variant2>(variant2));
        }, std::forward<Variant1>(variant1));
    }
};

// By default treat GeometryCollection as a range of DynamicGeometries
template <typename GeometryCollection>
struct iter_visit
{
    template <typename Function, typename Iterator>
    static void apply(Function && function, Iterator iterator)
    {
        using value_t = typename boost::range_value<GeometryCollection>::type;
        using reference_t = typename std::iterator_traits<Iterator>::reference;
        visit<value_t>::apply(std::forward<Function>(function),
                              std::forward<reference_t>(*iterator));
    }
};

}}} // namespace boost::geometry::traits

#endif // BOOST_GEOMETRY_CORE_VISIT_HPP
