#ifndef YANDEX_MAPS_WIKI_TOPO_PLAIN_INDEX_INL
#error "Direct inclusion of plain_index_inl.h is not allowed, " \
    "please include plain_index.h instead"
#endif

#include <maps/libs/geolib/include/intersection.h>
#include <maps/libs/geolib/include/distance.h>

#include <algorithm>

namespace maps {
namespace wiki {
namespace topo {
namespace index {

namespace {

typedef std::pair<const NodeID, geolib3::Point2> NodePair;
typedef std::pair<const EdgeID, geolib3::Polyline2> EdgePair;

struct PointDistCompare {
    PointDistCompare(const geolib3::Point2& point)
        : point_(point)
    {}

    bool operator()(const NodePair& n1, const NodePair& n2) const
    {
        return geolib3::distance(n1.second, point_) <
            geolib3::distance(n2.second, point_);
    }
    bool operator()(const EdgePair& e1, const EdgePair& e2) const
    {
        return geolib3::distance(e1.second, point_) <
            geolib3::distance(e2.second, point_);
    }

private:
    const geolib3::Point2& point_;
};

} // namespace

template <class ObjectsMap, class IDSet>
boost::optional<typename ObjectsMap::key_type>
PlainIndex::objectAtPoint(
    const ObjectsMap& objects,
    const geolib3::Point2& point,
    double maxDistance,
    const IDSet& ignoredObjectIds) const
{
    if (ignoredObjectIds.empty()) {
        typename ObjectsMap::const_iterator i = std::min_element(
            objects.begin(), objects.end(), PointDistCompare(point));
        if (i == objects.end() ||
            geolib3::distance(i->second, point) >= maxDistance)
        {
            return boost::none;
        }
        return i->first;
    } else {
        auto nearestObjectIds = nearestObjects<
                ObjectsMap,
                IDSet,
                geolib3::Point2,
                std::vector<typename ObjectsMap::key_type>
            >(objects, point, maxDistance, ignoredObjectIds);
        if (!nearestObjectIds.empty()) {
            return nearestObjectIds.front();
        }
        return boost::none;
    }
}

template <class ObjectsMap, class IDSet, class SearchGeometry, class ResultVector>
ResultVector
PlainIndex::nearestObjects(
    const ObjectsMap& objects,
    const SearchGeometry& geom,
    double distance,
    const IDSet& ignoredObjectIds) const
{
    typedef std::vector<std::pair<typename ObjectsMap::key_type, double> >
        DistIDVector;

    DistIDVector distances;
    for (const auto& object : objects) {
        const double curDist = geolib3::distance(object.second, geom);
        if (curDist < distance) {
            distances.push_back(std::make_pair(object.first, curDist));
        }
    }

    std::sort(distances.begin(), distances.end(),
        [] (
            typename DistIDVector::const_reference d1,
            typename DistIDVector::const_reference d2)
        {
            return d1.second < d2.second;
        }
    );

    ResultVector result;
    result.reserve(distances.size());
    for (size_t i = 0; i < distances.size(); ++i) {
        if (ignoredObjectIds.find(distances[i].first) == ignoredObjectIds.end()) {
            result.push_back(distances[i].first);
        }
    }

    return result;
}

} // namespace maps::wiki::topo::index
} // namespace maps::wiki::topo
} // namespace maps::wiki
} // namespace maps
