#include "topology_data_helpers.h"
#include "topology_data_proxy.h"

#include <set>
#include <algorithm>

namespace maps {
namespace wiki {
namespace topology_fixer {

namespace {

template <class TopologyDataT>
std::unique_ptr<TopologyData>
copyByEdgesImpl(TopologyDataT& data, FaceLocker& locker, const IdSet& edgeIds)
{
    std::list<NodeData> nodes;
    std::list<EdgeData> edges;
    std::list<FaceEdgeData> faceEdgeRels;
    std::list<EdgeMasterData> masterEdgeRels;
    std::list<FaceMasterData> masterFaceRels;

    IdSet allMasterIds;
    IdSet allEdgeIds;
    IdSet allFaceIds;
    IdSet edgeNodeIds;
    for (auto edgeId : edgeIds) {
        const Edge& edge = data.edge(edgeId);
        const auto& edgeFaces = edge.faceIds();
        allFaceIds.insert(edgeFaces.begin(), edgeFaces.end());
        for (auto masterId : edge.masterIds()) {
            masterEdgeRels.push_back({masterId, edgeId});
        }
        edgeNodeIds.insert(edge.fnodeId());
        edgeNodeIds.insert(edge.tnodeId());
    }

    auto locks = locker.lockFaces(allFaceIds);

    for (auto faceId : allFaceIds) {
        const Face& face = data.face(faceId);
        const auto& faceMasterIds = face.masterIds();
        allMasterIds.insert(faceMasterIds.begin(), faceMasterIds.end());
        for (auto masterId : faceMasterIds) {
            auto relationType = data.master(masterId).relationType(faceId);
            masterFaceRels.push_back({masterId, faceId, relationType});
        }
        const auto& faceEdgeIds = face.edgeIds();
        allEdgeIds.insert(faceEdgeIds.begin(), faceEdgeIds.end());
        for (auto edgeId : faceEdgeIds) {
            faceEdgeRels.push_back({faceId, edgeId});
        }
    }
    for (auto nodeId : edgeNodeIds) {
        const auto& nodeEdges = data.node(nodeId).edgeIds();
        allEdgeIds.insert(nodeEdges.begin(), nodeEdges.end());
    }
    IdSet allNodeIds;
    for (auto edgeId : allEdgeIds) {
        const auto& edge = data.edge(edgeId);
        allNodeIds.insert(edge.fnodeId());
        allNodeIds.insert(edge.tnodeId());
    }
    for (auto nodeId : allNodeIds) {
        nodes.push_back({nodeId, data.node(nodeId).point()});
    }
    for (auto edgeId : allEdgeIds) {
        const Edge& edge = data.edge(edgeId);
        edges.push_back({
            edgeId,
            edge.linestring(),
            edge.fnodeId(), edge.tnodeId(),
            edge.fZlev(), edge.tZlev()});
    }
    std::unique_ptr<TopologyData> result(new TopologyData(
        data.ftGroup(),
        nodes, edges, faceEdgeRels, masterEdgeRels, masterFaceRels,
        data.idGenerator(), data.srid()));
    for (auto masterId : allMasterIds) {
        const Master& master = data.master(masterId);
        if (master.ftAttributes()) {
            result->setFtAttributes(masterId, *master.ftAttributes());
        }
    }
    return result;
}

} // namespace

/**
 * Deep copy topology starting from given edges:
 *   select all dependent faces -> masters -> copy hierarchically downwards
 *   select all dependent nodes -> edges -> copy edges and nodes
 */

std::unique_ptr<TopologyData>
copyByEdges(const TopologyData& data, FaceLocker& locker, const IdSet& edgeIds)
{
    return copyByEdgesImpl<const TopologyData>(data, locker, edgeIds);
}

std::unique_ptr<TopologyData>
copyByEdges(utils::TopologyDataProxy& data, FaceLocker& locker, const IdSet& edgeIds)
{
    return copyByEdgesImpl<utils::TopologyDataProxy>(data, locker, edgeIds);
}

namespace {

ObjectIds
getDataObjectIds(const TopologyData& data)
{
    auto nodeIds = data.nodeIds();
    auto edgeIds = data.edgeIds();
    auto faceIds = data.faceIds();
    ObjectIds result = {
        IdSet(nodeIds.begin(), nodeIds.end()),
        IdSet(edgeIds.begin(), edgeIds.end()),
        IdSet(faceIds.begin(), faceIds.end()),
        {}, {}, {}, {}
    };
    for (const auto& master : data.masters()) {
        result.masterIds.insert(master.first);
        for (const auto& faceRel : master.second.faceRels()) {
            result.faceMasterRelations.push_back(
                {master.first, faceRel.first, faceRel.second});
        }
        for (auto edgeId : master.second.edgeIds()) {
            result.edgeMasterRelations.push_back({master.first, edgeId});
        }
    }
    for (const auto& face : data.faces()) {
        for (auto edgeId : face.second.edgeIds()) {
            result.faceEdgeRelations.push_back({face.first, edgeId});
        }
    }
    return result;
}

template <class Data>
struct DataCompare {};

template <>
struct DataCompare<FaceMasterData> {
    bool operator () (const FaceMasterData& fm1, const FaceMasterData& fm2) const
    {
        return fm1.masterId < fm2.masterId ||
            (fm1.masterId == fm2.masterId && fm1.faceId < fm2.faceId);
    }
};

template <>
struct DataCompare<EdgeMasterData> {
    bool operator () (const EdgeMasterData& em1, const EdgeMasterData& em2) const
    {
        return em1.masterId < em2.masterId ||
            (em1.masterId == em2.masterId && em1.edgeId < em2.edgeId);
    }
};

template <>
struct DataCompare<FaceEdgeData> {
    bool operator () (const FaceEdgeData& fe1, const FaceEdgeData& fe2) const
    {
        return fe1.faceId < fe2.faceId ||
            (fe1.faceId == fe2.faceId && fe1.edgeId < fe2.edgeId);
    }
};

template <class Data>
std::pair<std::vector<Data>, std::vector<Data>>
relationsDiff(const std::vector<Data>& data, const std::vector<Data>& other)
{
    std::set<Data, DataCompare<Data>> dataRels(data.begin(), data.end());
    std::set<Data, DataCompare<Data>> otherDataRels(other.begin(), other.end());
    std::pair<std::vector<Data>, std::vector<Data>> result;
    std::set_difference(dataRels.begin(), dataRels.end(),
        otherDataRels.begin(), otherDataRels.end(),
        std::back_inserter(result.first), DataCompare<Data>());
    std::set_difference(otherDataRels.begin(), otherDataRels.end(),
        dataRels.begin(), dataRels.end(),
        std::back_inserter(result.second), DataCompare<Data>());
    return result;
}

} // namespace

TopologyDataDiff
computeDiff(const TopologyData& data, const TopologyData& other)
{
    ObjectIds dataObjectIds = getDataObjectIds(data);
    ObjectIds otherDataObjectIds = getDataObjectIds(other);
    auto faceMasterRelsDiff = relationsDiff(
        dataObjectIds.faceMasterRelations, otherDataObjectIds.faceMasterRelations);
    auto edgeMasterRelsDiff = relationsDiff(
        dataObjectIds.edgeMasterRelations, otherDataObjectIds.edgeMasterRelations);
    auto faceEdgeRelsDiff = relationsDiff(
        dataObjectIds.faceEdgeRelations, otherDataObjectIds.faceEdgeRelations);
    return {
        { idsDiff(dataObjectIds.nodeIds, otherDataObjectIds.nodeIds),
          idsDiff(dataObjectIds.edgeIds, otherDataObjectIds.edgeIds),
          idsDiff(dataObjectIds.faceIds, otherDataObjectIds.faceIds),
          idsDiff(dataObjectIds.masterIds, otherDataObjectIds.masterIds),
          faceEdgeRelsDiff.first,
          edgeMasterRelsDiff.first,
          faceMasterRelsDiff.first
        },
        { idsDiff(otherDataObjectIds.nodeIds, dataObjectIds.nodeIds),
          idsDiff(otherDataObjectIds.edgeIds, dataObjectIds.edgeIds),
          idsDiff(otherDataObjectIds.faceIds, dataObjectIds.faceIds),
          idsDiff(otherDataObjectIds.masterIds, dataObjectIds.masterIds),
          faceEdgeRelsDiff.second,
          edgeMasterRelsDiff.second,
          faceMasterRelsDiff.second
        }
    };
}

namespace {

template <class TopologyDataT>
void
applyDiffImpl(TopologyDataT& data, const TopologyData& other,
    const TopologyDataDiff& diff)
{
    for (auto newNodeId : diff.added.nodeIds) {
        data.addExistingNode(newNodeId, other.node(newNodeId).point());
    }
    for (auto newEdgeId : diff.added.edgeIds) {
        const Edge& edge = other.edge(newEdgeId);
        data.addExistingEdge(newEdgeId,
            edge.linestring(), edge.fnodeId(), edge.tnodeId(),
            edge.fZlev(), edge.tZlev());
    }
    for (auto newFaceId : diff.added.faceIds) {
        data.addExistingFace(newFaceId);
    }
    for (auto newMasterId : diff.added.masterIds) {
        const auto& masterAttrs = other.master(newMasterId).ftAttributes();
        masterAttrs
            ? data.addExistingMaster(newMasterId, *masterAttrs)
            : data.addExistingMaster(newMasterId);
    }
    for (const auto& faceEdgeRel : diff.added.faceEdgeRelations) {
        data.addFaceEdge(faceEdgeRel.faceId, faceEdgeRel.edgeId);
    }
    for (const auto& edgeMasterRel : diff.added.edgeMasterRelations) {
        data.addMasterEdgeRel(edgeMasterRel.masterId, edgeMasterRel.edgeId);
    }
    for (const auto& faceMasterRel : diff.added.faceMasterRelations) {
        data.addMasterFaceRel(
            faceMasterRel.masterId, faceMasterRel.faceId, faceMasterRel.relationType);
    }
    for (const auto& faceEdgeRel : diff.deleted.faceEdgeRelations) {
        data.removeFaceEdgeRel(faceEdgeRel.faceId, faceEdgeRel.edgeId);
    }
    for (const auto& edgeMasterRel : diff.deleted.edgeMasterRelations) {
        data.removeMasterEdgeRel(edgeMasterRel.masterId, edgeMasterRel.edgeId);
    }
    for (const auto& faceMasterRel : diff.deleted.faceMasterRelations) {
        data.removeMasterFaceRel(
            faceMasterRel.masterId, faceMasterRel.faceId);
    }
    for (auto deletedMasterId : diff.deleted.masterIds) {
        data.removeMaster(deletedMasterId, DeletionMode::Restrict);
    }
    for (auto deletedFaceId : diff.deleted.faceIds) {
        data.removeFace(deletedFaceId, DeletionMode::Restrict);
    }
    for (auto deletedEdgeId : diff.deleted.edgeIds) {
        data.removeEdge(deletedEdgeId, DeletionMode::Restrict);
    }
    for (auto deletedNodeId : diff.deleted.nodeIds) {
        data.removeNode(deletedNodeId);
    }
}

} // namespace

void
applyDiff(TopologyData& data, const TopologyData& other,
    const TopologyDataDiff& diff)
{
    applyDiffImpl<TopologyData>(data, other, diff);
}

void
applyDiff(utils::TopologyDataProxy& data, const TopologyData& other,
    const TopologyDataDiff& diff)
{
    applyDiffImpl<utils::TopologyDataProxy>(data, other, diff);
}

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