#pragma once

#include "common.h"
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/sequenced_lifetime_guard.h>
#include <maps/wikimap/mapspro/services/mrc/libs/object/include/loader.h>
#include <maps/wikimap/mapspro/services/mrc/libs/privacy/include/geo_id_provider.h>
#include <maps/wikimap/mapspro/services/mrc/libs/privacy/include/region_privacy.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/concurrent/include/lru_cache.h>
#include <maps/libs/geolib/include/multipolygon.h>
#include <maps/libs/geolib/include/prepared_polygon.h>
#include <maps/libs/geolib/include/static_geometry_searcher.h>

#include <library/cpp/containers/sorted_vector/sorted_vector.h>

#include <mutex>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
#include <vector>


namespace maps::mrc::privacy {

class PreparedGeom {

public:
    template <class Geom>
    PreparedGeom(const Geom& geom): preapred_(geom) {}

    bool contains(const geolib3::BoundingBox& geoBox)
    {
        std::lock_guard<std::mutex> guard(lock_);
        return geolib3::spatialRelation(preapred_, geoBox, geolib3::SpatialRelation::Contains);
    }

    bool intersects(const geolib3::BoundingBox& geoBox)
    {
        std::lock_guard<std::mutex> guard(lock_);
        return geolib3::spatialRelation(preapred_, geoBox, geolib3::SpatialRelation::Intersects);
    }

private:
    std::mutex lock_;
    geolib3::PreparedPolygon2 preapred_;
};

using PreparedGeomPtr = std::shared_ptr<PreparedGeom>;

class CachingRegionPrivacy : common::SequencedLifetimeGuard<CachingRegionPrivacy>,
                             public RegionPrivacy {
public:
    CachingRegionPrivacy(
            std::map<db::TId, db::FeaturePrivacy> geoIdToPrivacyMap,
            object::Loader& objectLoader,
            const std::string& geoIdPath,
            LockMemory);

    CachingRegionPrivacy(
            std::map<db::TId, db::FeaturePrivacy> geoIdToPrivacyMap,
            object::Loader& objectLoader,
            GeoIdProviderPtr geoIdProvider);


    /// Privacy at given point @param geoPoint is evaluated as max
    /// among geoids privacies that contain that point.
    /// If there is no geoid containing this point then
    /// Restricted is used as default value.
    db::FeaturePrivacy evalFeaturePrivacy(const geolib3::Point2& geoPoint) override;

    /// Evaluates min value among all the points inside the @param geoBox
    db::FeaturePrivacy evalMinFeaturePrivacy(const geolib3::BoundingBox& geoBox) override;
    /// Evaluates max value among all the points inside the @param geoBox
    db::FeaturePrivacy evalMaxFeaturePrivacy(const geolib3::BoundingBox& geoBox) override;

private:
    PreparedGeom& geomByGeoId(db::TId geoId);
    std::optional<db::FeaturePrivacy> evalGeoIdFeaturePrivacy(db::TId geoId) const;
    bool withinForbiddenAreas(const geolib3::BoundingBox& geoBox) const;
    bool intersectsForbiddenAreas(const geolib3::BoundingBox& geoBox) const;
    std::pair<db::FeaturePrivacy, db::FeaturePrivacy>
    evalMinMaxFeaturePrivacy(const geolib3::BoundingBox& geoBox);

    std::map<db::TId, db::FeaturePrivacy> geoIdToPrivacy_;

    GeoIdProviderPtr geoIdProvider_;

    std::unordered_map<db::TId, PreparedGeom> geomCache_;
    std::shared_mutex geomCacheMutex_;

    object::MrcRegions forbiddenAreas_;
    geolib3::StaticGeometrySearcher<geolib3::Polygon2, PreparedGeomPtr>
        forbiddenAreasSearcher_;
};

} // namespace maps::mrc::privacy
