#pragma once

#include "maps/wikimap/mapspro/services/editor/src/exception.h"

#include <yandex/maps/wiki/revision/diff.h>
#include <yandex/maps/wiki/revision/commit.h>

#include <utility>
#include <vector>

namespace maps {
namespace wiki {

const size_t DEFAULT_GEOM_DIFF_PARTS_LIMIT = 200;

class CommitDiffDataProvider;
class ObjectsCache;

enum class DiffScope {
    Commit,
    GroupUpdateAttributes,
    GroupUpdateRelation,
    GroupUpdateRelations,
    Object
};

DiffScope commitDiffScope(const revision::Commit& commit, TOid objectId);

class DiffLimitExceeded : public Exception
{
public:
    DiffLimitExceeded() : Exception(std::string()) {}
    virtual const char* type() const { return "Geometry updates count hit limit."; }
};

class GeomDiff
{
public:
    explicit GeomDiff();
    explicit GeomDiff(size_t maxDiffElementsNum, const geos::geom::Envelope& viewPort);

    template<typename T>
    void tryAddBefore(T&& geom)
    {
        tryAddGeom(before_, std::forward<T>(geom));
    }

    template<typename T>
    void tryAddAfter(T&& geom)
    {
        tryAddGeom(after_, std::forward<T>(geom));
    }

    const std::vector<Geom>& before() const { return before_; }
    const std::vector<Geom>& after() const { return after_; }

    bool isLimitExceeded() const { return limitExceeded_; }
    void setLimitExceeded() { limitExceeded_ = true; }

    bool empty() const { return !limitExceeded_ && before_.empty() && after_.empty(); }

    size_t numPoints() const;
    size_t simplify(double tolerance);

private:
    template<typename T>
    void tryAddGeom(std::vector<Geom>& addTo, T&& geom)
    {
        if (geom.isNull() ||
            (viewPort_ && !viewPort_->intersects(geom->getEnvelopeInternal()))) {
            return;
        }
        if (addTo.size() >= maxDiffElementsNum_) {
            throw DiffLimitExceeded();
        }
        addTo.emplace_back(std::forward<T>(geom));
    }

    size_t maxDiffElementsNum_;
    std::vector<Geom> before_;
    std::vector<Geom> after_;
    bool limitExceeded_;
    boost::optional<geos::geom::Envelope> viewPort_;
};

struct RelData
{
    TOid slaveId;
    TOid masterId;
    bool removed;
};

GeomDiff objectGeomDiff(
            const std::unique_ptr<ObjectsCache>& prevCache,
            ObjectsCache& curCache,
            const boost::optional<revision::CommitDiff>& commitDiff,
            TOid objectId,
            const TOIds& allCommitedObjectIds,
            DiffScope scope,
            const geos::geom::Envelope& viewPort,
            size_t maxDiffElements);

GeomDiff objectGeomDiff(
            const std::unique_ptr<ObjectsCache>& prevCache,
            ObjectsCache& curCache,
            const boost::optional<revision::CommitDiff>& commitDiff,
            TOid objectId,
            const TOIds& allCommitedObjectIds,
            DiffScope scope);

geos::geom::Envelope changesEnvelope(const GeomDiff& geomDiff);
geos::geom::Envelope changesEnvelope(const GeoObject* fromObj, const GeoObject* toObj);

} // namespace wiki
} // namespace maps
