#pragma once

#include "../topology_data.h"

#include <maps/libs/geolib/include/static_geometry_searcher.h>
#include <maps/libs/geolib/include/dynamic_geometry_searcher.h>

#include <unordered_map>
#include <memory>

namespace maps {
namespace wiki {
namespace topology_fixer {

class BBoxSearcher {
public:
    virtual ~BBoxSearcher() = default;

    /**
     * Returns objects strictly contained in @param bbox
     */
    virtual IdSet idsByBBox(const geolib3::BoundingBox& bbox) const = 0;

    virtual boost::optional<geolib3::BoundingBox> bbox(DBIdType objectId) const = 0;
    virtual boost::optional<geolib3::BoundingBox> bbox() const = 0;
};

class FaceMasterSearcher : public BBoxSearcher {
public:
    explicit FaceMasterSearcher(const TopologyData& data);

    /**
     * Returns masters strictly contained in @param bbox
     */
    virtual IdSet idsByBBox(const geolib3::BoundingBox& bbox) const;

    virtual boost::optional<geolib3::BoundingBox> bbox(MasterId masterId) const;
    virtual boost::optional<geolib3::BoundingBox> bbox() const { return data_->bbox; }

private:
    typedef geolib3::StaticGeometrySearcher<geolib3::BoundingBox, MasterId> IndexType;

    struct Data {
        std::unordered_map<MasterId, geolib3::BoundingBox> bboxes;
        IndexType index;
        boost::optional<geolib3::BoundingBox> bbox;
    };

    std::unique_ptr<Data> data_;
};

class EdgeMasterSearcher : public BBoxSearcher {
public:
    explicit EdgeMasterSearcher(const TopologyData& data);

    /**
     * Returns masters strictly contained in @param bbox
     */
    virtual IdSet idsByBBox(const geolib3::BoundingBox& bbox) const;

    virtual boost::optional<geolib3::BoundingBox> bbox(MasterId masterId) const;
    virtual boost::optional<geolib3::BoundingBox> bbox() const { return data_->bbox; }

private:
    typedef geolib3::StaticGeometrySearcher<geolib3::BoundingBox, MasterId> IndexType;

    struct Data {
        std::unordered_map<MasterId, geolib3::BoundingBox> bboxes;
        IndexType index;
        boost::optional<geolib3::BoundingBox> bbox;
    };

    std::unique_ptr<Data> data_;
};

class FaceSearcher : public BBoxSearcher {
public:
    explicit FaceSearcher(const TopologyData& data);
    FaceSearcher(const TopologyData& data, const IdSet& faceIds);

    /**
     * Returns faces being contained @param bbox
     */
    virtual IdSet idsByBBox(const geolib3::BoundingBox& bbox) const;

    IdSet idsIntersectingBBox(const geolib3::BoundingBox& bbox) const;

    virtual boost::optional<geolib3::BoundingBox> bbox(FaceId faceId) const;
    virtual boost::optional<geolib3::BoundingBox> bbox() const { return data_->bbox; }

protected:
    void init(const TopologyData& data, const std::vector<FaceId>& faceIds);

    typedef geolib3::StaticGeometrySearcher<geolib3::BoundingBox, FaceId> IndexType;

    struct Data {
        std::unordered_map<FaceId, geolib3::BoundingBox> bboxes;
        IndexType index;
        boost::optional<geolib3::BoundingBox> bbox;
    };

    std::unique_ptr<Data> data_;
};

class StaticEdgeSearcher : public BBoxSearcher {
public:
    explicit StaticEdgeSearcher(const TopologyData& data);

    /**
     * Returns edges being contained in @param bbox
     */
    virtual IdSet idsByBBox(const geolib3::BoundingBox& bbox) const;

    IdSet idsIntersectingBBox(const geolib3::BoundingBox& bbox) const;

    virtual boost::optional<geolib3::BoundingBox> bbox(EdgeId edgeId) const;
    virtual boost::optional<geolib3::BoundingBox> bbox() const { return data_->bbox; }

private:
    typedef geolib3::StaticGeometrySearcher<geolib3::BoundingBox, EdgeId> IndexType;

    struct Data {
        std::unordered_map<EdgeId, geolib3::BoundingBox> bboxes;
        IndexType index;
        boost::optional<geolib3::BoundingBox> bbox;
    };

    std::unique_ptr<Data> data_;
};

class DynamicEdgeSearcher
{
public:
    DynamicEdgeSearcher(const TopologyData& data, const std::set<EdgeId>& edgeIds);

    void insert(const geolib3::Polyline2& geom, const geolib3::BoundingBox& bbox, EdgeId edgeId);

    IdSet edgeIdsByBBox(const geolib3::BoundingBox& bbox) const;

private:
    struct Element
    {
        // Here we store a copy of the geometry.
        // Little memory overhead for the sake of code simplicity
        geolib3::Polyline2 geom;
        geolib3::BoundingBox bbox;
    };
    std::list<Element> elements_;

    using IndexType = geolib3::DynamicGeometrySearcher<geolib3::Polyline2, EdgeId>;
    std::shared_ptr<IndexType> index_;
};

} // namespace topology_fixer
} // namespace wiki
} // namespace maps
