#include <yandex/maps/wiki/topo/cache.h>
#include <yandex/maps/wiki/topo/storage.h>
#include <yandex/maps/wiki/topo/editor.h>
#include <yandex/maps/wiki/topo/face_validator.h>

#include "index/plain_index.h"
#include "graph.h"
#include "cache_impl.h"
#include "editor_impl.h"

namespace maps {
namespace wiki {
namespace topo {

#define ITERATOR_INST(ID, Object)                                           \
                                                                            \
template class Iterator<ID, Object>;                                        \
                                                                            \
template <>                                                                 \
Iterator<ID, Object>::Iterator(                                             \
        std::unique_ptr<IteratorImpl<ID, Object> > impl)                    \
    : impl_(std::move(impl))                                                \
{}                                                                          \
                                                                            \
template <>                                                                 \
Iterator<ID, Object>::Iterator(const Iterator<ID, Object>& other)           \
    : impl_(std::make_unique<IteratorImpl<ID, Object> >(*(other.impl_)))   \
{}                                                                          \
                                                                            \
template <>                                                                 \
Iterator<ID, Object>::Iterator(Iterator<ID, Object>&& other)                \
    : impl_(std::move(other.impl_))                                         \
{}                                                                          \
                                                                            \
template <>                                                                 \
Iterator<ID, Object>& Iterator<ID, Object>::operator=(                      \
    const Iterator<ID, Object>& other)                                      \
{                                                                           \
    *impl_ = *other.impl_;                                                  \
    return *this;                                                           \
}                                                                           \
                                                                            \
template <>                                                                 \
Iterator<ID, Object>& Iterator<ID, Object>::operator=(                      \
    Iterator<ID, Object>&& other)                                           \
{                                                                           \
    impl_ = std::move(other.impl_);                                         \
    return *this;                                                           \
}                                                                           \
                                                                            \
template <>                                                                 \
Iterator<ID, Object>::~Iterator()                                           \
{}                                                                          \
                                                                            \
template <>                                                                 \
Object& Iterator<ID, Object>::dereference() const                           \
{                                                                           \
    return *impl_->baseIt->second;                                          \
}                                                                           \
                                                                            \
template <>                                                                 \
bool Iterator<ID, Object>::equal(const Iterator<ID, Object>& other) const   \
{                                                                           \
    return impl_->baseIt == other.impl_->baseIt;                            \
}                                                                           \
                                                                            \
template <>                                                                 \
void Iterator<ID, Object>::increment()                                      \
{                                                                           \
    ++(impl_->baseIt);                                                      \
}                                                                           \

ITERATOR_INST(NodeID, const Node)
ITERATOR_INST(EdgeID, const Edge)

#undef ITERATOR_INST

Cache::Cache(Storage& storage, double tolerance)
    : impl_(std::make_unique<CacheImpl>(
        storage,
        tolerance,
        std::make_unique<index::PlainIndex>()))
{}

Cache::~Cache()
{}

const Node& Cache::node(NodeID id) const
{
    return impl_->graph().node(id);
}

bool Cache::nodeExists(NodeID id) const
{
    return impl_->graph().nodeExists(id);
}

const Edge& Cache::edge(EdgeID id) const
{
    return impl_->graph().edge(id);
}

bool Cache::edgeExists(EdgeID id) const
{
    return impl_->graph().edgeExists(id);
}

NodeConstIteratorRange Cache::nodes() const
{
    return impl_->graph().nodes();
}

EdgeConstIteratorRange Cache::edges() const
{
    return impl_->graph().edges();
}

void Cache::loadObjects(const geolib3::BoundingBox& bbox)
{
    impl_->loadObjects(bbox);
}

void Cache::loadObjects(const geolib3::Point2& center,
    double width, double height)
{
    impl_->loadObjects(center, width, height);
}

void Cache::loadObjects(const geolib3::Polyline2& polyline,
    double withinDistance)
{
    impl_->loadObjects(polyline, withinDistance);
}

void Cache::loadByNodes(const NodeIDSet& nodeIds)
{
    impl_->loadByNodes(nodeIds);
}

void Cache::loadByEdges(const EdgeIDSet& edgeIds)
{
    impl_->loadByEdges(edgeIds);
}

void Cache::loadNode(NodeID nodeId)
{
    impl_->loadByNodes(NodeIDSet{nodeId});
}

void Cache::loadEdge(EdgeID edgeId)
{
    impl_->loadByEdges(EdgeIDSet{edgeId});
}

std::unique_ptr<Editor> Cache::editor()
{
    return std::make_unique<Editor>(std::make_unique<EditorImpl>(*impl_));
}

std::unique_ptr<FaceValidator> Cache::faceValidator()
{
    return impl_->faceValidator();
}

IncidencesByNodeMap Cache::incidencesByNodes(const NodeIDSet& nodeIds)
{
    return impl_->incidencesByNodes(nodeIds);
}

IncidencesByEdgeMap Cache::incidencesByEdges(const EdgeIDSet& edgeIds)
{
    return impl_->incidencesByEdges(edgeIds);
}

boost::optional<Circuit> Cache::circuit(const EdgeIDSet& edgeIds, bool withNodes)
{
    return impl_->circuit(edgeIds, withNodes);
}

} // namespace maps::wiki::topo
} // namespace maps::wiki
} // namespace maps
