#pragma once

#include <yandex/maps/wiki/validator/area_of_interest.h>
#include <yandex/maps/wiki/validator/common.h>
#include <yandex/maps/wiki/validator/message.h>

#include <maps/libs/geolib/include/point.h>
#include <maps/libs/geolib/include/polygon.h>

#include <boost/optional.hpp>
#include <functional>
#include <memory>

namespace maps::wiki::validator {

using MessageKey = std::string;
const MessageKey NO_DEDUPLICATION = "";

// If two messages have the same key (!= NO_DEDUPLICATION),
// then only one must remain, according to MessageComparator
using MessageKeyGenerator = std::function<MessageKey(const Message&)>;

// If returns true, first message has higher priority and should consume the second one
using MessageComparator = std::function<bool(const Message&, const Message&)>;

inline MessageKey defaultKeyGenerator(const Message&)
{ return NO_DEDUPLICATION; }

inline bool defaultMessageComparator(const Message&, const Message&)
{ return true; }

class CheckContext
{
public:
    ~CheckContext();

    void warning(
        std::string description,
        const geolib3::Point2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void error(
        std::string description,
        const geolib3::Point2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void critical(
        std::string description,
        const geolib3::Point2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void fatal(
        std::string description,
        const geolib3::Point2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void report(
        Severity severity,
        std::string description,
        const geolib3::Point2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void warning(
        std::string description,
        const geolib3::Polygon2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void error(
        std::string description,
        const geolib3::Polygon2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void critical(
        std::string description,
        const geolib3::Polygon2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void fatal(
        std::string description,
        const geolib3::Polygon2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void report(
        Severity severity,
        std::string description,
        const geolib3::Polygon2& geometry,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void warning(
        std::string description,
        const boost::none_t&,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void error(
        std::string description,
        const boost::none_t&,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void critical(
        std::string description,
        const boost::none_t&,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void fatal(
        std::string description,
        const boost::none_t&,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    void report(
        Severity severity,
        std::string description,
        const boost::none_t&,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator);

    template<typename TGeom>
    void report(
        Severity severity,
        std::string description,
        const boost::optional<TGeom>& geom,
        const std::vector<TId>& oids,
        MessageKeyGenerator keyGenerator = defaultKeyGenerator,
        MessageComparator messageComparator = defaultMessageComparator)
    {
        if (geom) {
            report(severity, std::move(description), *geom, oids, keyGenerator, messageComparator);
        } else {
            report(severity, std::move(description), boost::none, oids, keyGenerator, messageComparator);
        }
    }

    const AreaOfInterest& aoi() const;

    /**
     * Default: false
     * If the validation runs by the object list and the given objectId is not in the list
     * then is is possible to skip some validation for this objectId
     */
    template<class Category>
    bool canSkipValidation(TId objectId) const;

    template<class Category>
    typename Category::TObjectsView objects();

    template<class Category>
    void checkLoaded() const;

private:
    class Impl;
    explicit CheckContext(Impl* impl);
    friend class TaskContext;

private:
    std::unique_ptr<Impl> impl_;
};

} // namespace maps::wiki::validator
