#include "revisions_loader.h"

#include <yandex/maps/wiki/common/batch.h>
#include <yandex/maps/wiki/revision/branch_manager.h>
#include <yandex/maps/wiki/revision/snapshot.h>
#include <yandex/maps/wiki/revision/historical_snapshot.h>
#include <yandex/maps/wiki/revision/reader.h>

#include <initializer_list>

namespace maps {
namespace wiki {
namespace contours {
namespace revision_meta {

namespace {
const size_t REVISION_LOAD_BATCH_SIZE = 1000;
}

RevisionsLoader::RevisionsLoader(
        Transaction& txn,
        TBranchId branchId,
        const Categories& categories)
    : gateway_(txn, revision::BranchManager(txn).load(branchId))
    , categories_(categories)
{
}

TRevisionList
RevisionsLoader::loadRelations(TCommitId minCommitId, TCommitId maxCommitId) const
{
    auto filter = categories_.relationsFilter();

    auto snapshot = gateway_.historicalSnapshot(minCommitId, maxCommitId);
    return snapshot.relationsByFilter(filter);
}

namespace {

revision::filters::ProxyFilterExpr
relationsByTypeFilter(
    const Categories& categories,
    const TCategoryId& categoryId,
    RelationType relationType,
    const TObjectIdSet& objectIds)
{
    return (relationType == RelationType::Master
            ? categories.masterRelationsFilter(categoryId)
            : categories.slaveRelationsFilter(categoryId))
        && (relationType == RelationType::Master
            ? revision::filters::ObjRevAttr::slaveObjectId().in(objectIds)
            : revision::filters::ObjRevAttr::masterObjectId().in(objectIds));
}

revision::filters::ProxyFilterExpr
notDeletedRelationsByTypeFilter(
    const Categories& categories,
    RelationType relationType,
    const TObjectIdSet& objectIds)
{
    return categories.relationsFilter()
        && revision::filters::ObjRevAttr::isNotDeleted()
        && (relationType == RelationType::Master
            ? revision::filters::ObjRevAttr::slaveObjectId().in(objectIds)
            : revision::filters::ObjRevAttr::masterObjectId().in(objectIds));
}

} // namespace

TRevisionList
RevisionsLoader::loadRelations(
    RelationType relationType,
    const TCategoryId& categoryId,
    const TObjectIdSet& objectIds,
    TCommitId maxCommitId) const
{
    TRevisionList result;

    auto snapshot = gateway_.snapshot(maxCommitId);

    common::applyBatchOp<TObjectIdSet>(
        objectIds,
        REVISION_LOAD_BATCH_SIZE,
        [&](const TObjectIdSet& batchIds) {
            auto filter =
                relationsByTypeFilter(categories_, categoryId, relationType, batchIds);

            result.splice(result.end(), snapshot.relationsByFilter(filter));
        }
    );
    return result;
}


TRevisionList
RevisionsLoader::loadNotDeletedRelations(
    RelationType relationType,
    const TObjectIdSet& objectIds,
    TCommitId maxCommitId) const
{
    TRevisionList result;

    auto snapshot = gateway_.snapshot(maxCommitId);

    common::applyBatchOp<TObjectIdSet>(
        objectIds,
        REVISION_LOAD_BATCH_SIZE,
        [&](const TObjectIdSet& batchIds) {
            auto filter =
                notDeletedRelationsByTypeFilter(categories_, relationType, batchIds);

            result.splice(result.end(), snapshot.relationsByFilter(filter));
        }
    );
    return result;
}


TRevisionIdVector
RevisionsLoader::loadObjectIds(TCommitId minCommitId, TCommitId maxCommitId) const
{
    auto snapshot = gateway_.historicalSnapshot(minCommitId, maxCommitId);
    TRevisionIdVector revisionIds;
    for (CategoryType hintCatType :
        {CategoryType::Attr, CategoryType::Complex, CategoryType::Geom})
    {
        auto revIds = snapshot.revisionIdsByFilter(categories_.objectsFilter(hintCatType));
        revisionIds.insert(revisionIds.end(), revIds.begin(), revIds.end());
    }

    return revisionIds;
}

namespace {

revision::filters::ProxyFilterExpr
objectsFilter(const Categories& categories, const TObjectIdSet& objectIds)
{
    return
        (categories.categoriesFilter(CategoryType::Attr) ||
        categories.categoriesFilter(CategoryType::Complex) ||
        categories.categoriesFilter(CategoryType::Geom)) &&
        revision::filters::ObjRevAttr::objectId().in(objectIds) &&
        revision::filters::ObjRevAttr::isNotRelation();
}

} // namespace

TRevisionIdVector
RevisionsLoader::loadObjectIds(
    const TObjectIdSet& objectIds,
    TCommitId maxCommitId) const
{
    return gateway_.snapshot(maxCommitId).revisionIdsByFilter(
        objectsFilter(categories_, objectIds));
}

TRevisionList
RevisionsLoader::loadObjects(const TRevisionIdVector& revisionIds) const
{
    return gateway_.reader().loadRevisions(
        revisionIds, revision::filters::ObjRevAttr::isNotRelation());
}

TRevisionIdVector
RevisionsLoader::loadObjectIds(
    CategoryType hintCategoryType,
    const TObjectIdSet& objectIds,
    TCommitId maxCommitId) const
{
    auto filter = categories_.objectsFilter(hintCategoryType);

    return gateway_.snapshot(maxCommitId).revisionIdsByFilter(objectIds, filter);
}

TRevisionList RevisionsLoader::
loadObjects(
    CategoryType hintCategoryType,
    const TRevisionIdVector& revisionIds) const
{
    return gateway_.reader().loadRevisions(
        revisionIds, categories_.objectsFilter(hintCategoryType));
}

} // namespace revision_meta
} // namespace contours
} // namespace wiki
} // namespace maps
