#pragma once
#include "module.h"
#include <yandex/maps/wiki/validator/check.h>
#include <yandex/maps/wiki/validator/categories.h>

#include <algorithm>
#include <map>
#include <utility>

namespace maps {
namespace wiki {
namespace validator {
namespace checks {

using categories::RD_JC;

template <typename Condition>
bool equal(const Condition& left, const Condition& right)
{
    return left.fromRoadElement() == right.fromRoadElement() &&
           left.viaJunction() == right.viaJunction() &&
           left.toRoadElements() == right.toRoadElements();
}

template<typename CondCategory>
class ConditionAccumulator
{
public:
    typedef typename CondCategory::TObject CondObject;
    ConditionAccumulator(CheckContext* context) : context_(context) { }

    void checkDuplicates(const std::string& messageId)
    {
        context_->objects<CondCategory>().visit(
            [&](const typename CondCategory::TObject* condObj) {
                if (!context_->objects<RD_JC>().loaded(condObj->viaJunction())) {
                    // Out of AOI
                    return;
                }

                const auto duplicateCondId = duplicateId(condObj);
                if (duplicateCondId) {
                    context_->error(
                        messageId,
                        context_->objects<RD_JC>()
                                 .byId(condObj->viaJunction())->geom(),
                        { condObj->id(), duplicateCondId });
                } else {
                    add(condObj);
                }
            });
    }

private:
    void add(const CondObject* condition)
    {
        junctionConditions_.insert(std::make_pair(condition->viaJunction(),
                                                   condition->id()));
    }

    TId duplicateId(const CondObject* condition) const
    {
        auto candidatesRange =
                junctionConditions_.equal_range(condition->viaJunction());

        auto duplicate = std::find_if(
                candidatesRange.first,
                candidatesRange.second,
                [&](decltype(*candidatesRange.first) candidatePair) {
                    return equal(*condition, *context_->objects<CondCategory>().byId(
                            candidatePair.second));
                });

        if (duplicate != candidatesRange.second) {
            return duplicate->second;
        }
        return TId();
    }


    CheckContext* context_;
    std::multimap<TId, TId> junctionConditions_;
};

} // namespace checks
} // namespace validator
} // namespace wiki
} // namespace maps
