#ifndef OSRM_EXTRACTOR_RESTRICTION_INDEX_HPP_
#define OSRM_EXTRACTOR_RESTRICTION_INDEX_HPP_

#include "extractor/restriction.hpp"
#include "util/typedefs.hpp"

#include <boost/unordered_map.hpp>

#include <utility>
#include <vector>

namespace osrm
{
namespace extractor
{

// allows easy check for whether a node intersection is present at a given intersection
template <typename restriction_type> class RestrictionIndex
{
  public:
    using value_type = restriction_type;

    template <typename extractor_type>
    RestrictionIndex(std::vector<restriction_type> &restrictions, extractor_type extractor);

    bool IsIndexed(NodeID first, NodeID second) const;

    auto Restrictions(NodeID first, NodeID second) const
    {
        return restriction_hash.equal_range(std::make_pair(first, second));
    };

    auto Size() const { return restriction_hash.size(); }

  private:
    boost::unordered_multimap<std::pair<NodeID, NodeID>, restriction_type *> restriction_hash;
};

template <typename restriction_type>
template <typename extractor_type>
RestrictionIndex<restriction_type>::RestrictionIndex(std::vector<restriction_type> &restrictions,
                                                     extractor_type extractor)
{
    // build a multi-map
    for (auto &restriction : restrictions)
        restriction_hash.insert(std::make_pair(extractor(restriction), &restriction));
}

template <typename restriction_type>
bool RestrictionIndex<restriction_type>::IsIndexed(const NodeID first, const NodeID second) const
{
    return restriction_hash.count(std::make_pair(first, second));
}

struct IndexNodeByFromAndVia
{
    std::pair<NodeID, NodeID> operator()(const TurnRestriction &restriction)
    {
        const auto &node = restriction.AsNodeRestriction();
        return std::make_pair(node.from, node.via);
    };
};

// check wheter a turn is restricted within a restriction_index
template <typename restriction_map_type>
std::pair<bool, typename restriction_map_type::value_type *>
isRestricted(const NodeID from,
             const NodeID via,
             const NodeID to,
             const restriction_map_type &restriction_map)
{
    const auto range = restriction_map.Restrictions(from, via);

    // check if a given node_restriction is targeting node
    const auto to_is_restricted = [to](const auto &pair) {
        const auto &restriction = *pair.second;
        if (restriction.Type() == RestrictionType::NODE_RESTRICTION)
        {
            auto const &as_node = restriction.AsNodeRestriction();
            auto const restricted = restriction.is_only ? (to != as_node.to) : (to == as_node.to);

            return restricted;
        }
        return false;
    };

    auto itr = std::find_if(range.first, range.second, to_is_restricted);

    if (itr != range.second)
        return {true, itr->second};
    else
        return {false, NULL};
}

using RestrictionMap = RestrictionIndex<TurnRestriction>;
using ConditionalRestrictionMap = RestrictionIndex<ConditionalTurnRestriction>;

} // namespace extractor
} // namespace osrm

#endif // OSRM_EXTRACTOR_RESTRICTION_INDEX_HPP_