#include "preload_objects.h"

#include "../cache_impl.h"
#include "../graph.h"

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

#include <maps/libs/geolib/include/bounding_box.h>

namespace maps {
namespace wiki {
namespace topo {

TopologyUpdateData::TopologyUpdateData(const Editor::TopologyData& data)
{
    nodesData.reserve(data.nodesData.size());
    for (const auto& nodeData : data.nodesData) {
        nodesData.push_back({nodeData.id, &nodeData.pos});
    }

    edgesData.reserve(data.edgesData.size());
    for (const auto& edgeData : data.edgesData) {
        OptionalEdgeID edgeId;
        if (edgeData.id.exists()) {
            edgeId = edgeData.id.id();
        }
        edgesData.push_back({edgeId, &edgeData.geom});
    }
}

TopologyUpdateData::TopologyUpdateData(const geolib3::PolylinesVector& geoms)
{
    edgesData.reserve(geoms.size());
    for (const auto& geom : geoms) {
        edgesData.push_back({boost::none, &geom});
    }
}

TopologyUpdateData::TopologyUpdateData(
        std::vector<NodeUpdateData> nodesData,
        std::vector<EdgeUpdateData> edgesData)
    : nodesData(std::move(nodesData))
    , edgesData(std::move(edgesData))
{}

void
preloadObjects(
    CacheImpl& cache,
    const TopologyUpdateData& updateData,
    const TopologyRestrictions& restrictions)
{
    if (updateData.nodesData.empty() && updateData.edgesData.empty()) {
        return;
    }

    auto nodeBBox = [] (const NodeUpdateData& nodeData) {
        return geolib3::BoundingBox(*nodeData.newPos, geolib3::EPS, geolib3::EPS);
    };

    geolib3::BoundingBox totalBBox = !updateData.nodesData.empty()
        ? nodeBBox(updateData.nodesData.front())
        : updateData.edgesData.front().newGeom->boundingBox();

    NodeIDSet updatedNodeIds;
    for (const auto& nodeData : updateData.nodesData) {
        totalBBox = geolib3::expand(totalBBox, nodeBBox(nodeData));
        updatedNodeIds.insert(nodeData.nodeId);
    }
    EdgeIDSet updatedEdgeIds;
    for (const auto& edgeData : updateData.edgesData) {
        totalBBox = geolib3::expand(totalBBox, edgeData.newGeom->boundingBox());
        if (edgeData.edgeId) {
            updatedEdgeIds.insert(*edgeData.edgeId);
        }
    }

    NodeIDSet incidentNodeIds;

    cache.loadByEdges(updatedEdgeIds);

    auto addNodeToLoad = [&incidentNodeIds, updatedNodeIds] (NodeID id) {
        if (!updatedNodeIds.count(id)) {
            incidentNodeIds.insert(id);
        }
    };

    for (auto edgeId : updatedEdgeIds) {
        const Edge& edge = cache.graph().edge(edgeId);
        addNodeToLoad(edge.startNode());
        addNodeToLoad(edge.endNode());
    }

    cache.loadByNodes(updatedNodeIds);

    for (auto nodeId : updatedNodeIds) {
        for (auto edgePtr : cache.graph().node(nodeId).incidentEdges()) {
            addNodeToLoad(edgePtr->startNode());
            addNodeToLoad(edgePtr->endNode());
            totalBBox = geolib3::expand(totalBBox, edgePtr->geom().boundingBox());
        }
    }

    cache.loadByNodes(incidentNodeIds);

    totalBBox = geolib3::resizeByValue(
        totalBBox,
        Editor::maxInteractionDistance(restrictions) + geolib3::EPS);

    cache.loadObjects(totalBBox);
}

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