#include <yandex/maps/wiki/groupedit/session.h>

#include "commit_writer.h"
#include "geometry_filter.h"
#include "object_diff.h"
#include "object_loader.h"
#include "utils/geom.h"

#include <yandex/maps/wiki/groupedit/object.h>

#include <yandex/maps/wiki/common/exception_trap.hpp>
#include <yandex/maps/wiki/revision/branch_manager.h>
#include <yandex/maps/wiki/revision/commit_manager.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/threadutils/threadpool.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/polygon.h>
#include <maps/libs/geolib/include/bounding_box.h>

namespace maps {
namespace wiki {

namespace rf = revision::filters;

namespace groupedit {

namespace {

const size_t COMMIT_SIZE = 5000;

} // namespace

namespace {

void processBatch(
        const std::shared_ptr<std::vector<Object>>& batchPtr,
        const ObjectVisitor& visitor,
        CommitWriter& writer)
{
    REQUIRE(batchPtr, "Invalid batch pointer");
    for (Object& object : *batchPtr) {
        visitor(object);
        writer.add(ObjectDiff(std::move(object)));
    }
}

void processBatchReadOnly(
        const std::shared_ptr<std::vector<Object>>& batchPtr,
        const ConstObjectVisitor& visitor)
{
    REQUIRE(batchPtr, "Invalid batch pointer");
    for (const Object& object : *batchPtr) {
        visitor(object);
    }
}

} // namespace

//======================= RevGeomFilter =========================

RevGeomFilter::RevGeomFilter(rf::ProxyFilterExpr revFilter)
    : filterExpr_(std::move(revFilter))
{
    init();
}

RevGeomFilter::RevGeomFilter(GeomPredicate geomPredicate, std::string wkb)
    : filterExpr_(rf::True())
    , geomFilter_(geomPredicate, std::move(wkb))
{
    init();
}

RevGeomFilter::RevGeomFilter(
        rf::ProxyFilterExpr revFilter,
        GeomPredicate geomPredicate,
        std::string wkb)
    : filterExpr_(std::move(revFilter))
    , geomFilter_(geomPredicate, std::move(wkb))
{
    init();
}

void RevGeomFilter::init()
{
    filterExpr_ &= rf::ObjRevAttr::isNotDeleted();
    filterExpr_ &= rf::ObjRevAttr::isNotRelation();

    const auto& bbox = geomFilter_.boundingBox();
    if (bbox) {
        filterExpr_ &= rf::Geom::intersects(
            bbox->minX(), bbox->minY(), bbox->maxX(), bbox->maxY());
    }
}

GeomPredicate RevGeomFilter::geomPredicate() const
{ return geomFilter_.predicate(); }

const std::string& RevGeomFilter::wkb() const
{ return geomFilter_.wkb(); }

//======================= Session =========================

Session::Session(Transaction& txn, TBranchId branchId)
    : txn_(txn)
    , branch_(revision::BranchManager(txn).load(branchId))
    , readonly_(false)
    , headCommitId_(0)
{ }

Session::Session(Transaction& txn, TBranchId branchId, TCommitId headCommitId)
    : txn_(txn)
    , branch_(revision::BranchManager(txn).load(branchId))
    , readonly_(true)
    , headCommitId_(headCommitId)
{ }

Query Session::query(
        const revision::filters::ProxyFilterExpr& filterExpr) const
{ return query(RevGeomFilter(filterExpr)); }

Query Session::query(
        const revision::filters::ProxyFilterExpr& filterExpr,
        GeomPredicate geomPredicate,
        const std::string& wkb) const
{
    return query({filterExpr, geomPredicate, wkb});
}

Query Session::query(const revision::ConstRange<TObjectId>& oids) const
{
    if (oids.empty()) {
        return Query();
    }
    return query(RevGeomFilter(rf::ObjRevAttr::objectId().in(oids)));
}

Query Session::query(RevGeomFilter revGeomFilter) const
{
    std::unique_ptr<revision::RevisionsGateway> gateway(
            new revision::RevisionsGateway(txn_, branch_));

    revision::DBID commitId = readonly_ ? headCommitId_ : gateway->headCommitId();
    auto snapshot = gateway->snapshot(commitId);
    auto revisionIds = snapshot.revisionIdsByFilter(
        revGeomFilter.filterExpr_);
    return Query(
            readonly_,
            std::move(gateway),
            commitId,
            std::move(revisionIds),
            std::move(revGeomFilter.geomFilter_));
}

//======================= Query =========================

Query::Query(
        bool readonly,
        std::unique_ptr<revision::RevisionsGateway> gateway,
        revision::DBID commitId,
        std::vector<revision::RevisionID> revisionIds,
        const GeometryFilter& geomFilter)
    : readonly_(readonly)
    , gateway_(std::move(gateway))
    , commitId_(commitId)
    , revisionIds_(std::move(revisionIds))
    , geomFilter_(geomFilter)
{ }

std::vector<TCommitId> Query::update(
        std::string commitAction, TUserId author, const ObjectVisitor& visitor) const
{
    if (revisionIds_.empty()) {
        return {};
    }

    REQUIRE(!readonly_, "update() called on readonly query");
    ASSERT(gateway_);

    auto snapshot = gateway_->snapshot(commitId_);
    CommitWriter writer;
    ThreadPool processorThread(1);
    common::ExceptionTrap trap;

    loadObjects(
        snapshot, revisionIds_, geomFilter_,
        [&](std::vector<Object> batch)
        {
            auto batchPtr =
                std::make_shared<std::vector<Object>>(std::move(batch));
            processorThread.push(
                [&, batchPtr]
                {
                    trap.try_(processBatch, batchPtr, visitor, writer);
                });
        });

    processorThread.shutdown();
    trap.release(); // may throw

    return writer.write(*gateway_, author, std::move(commitAction), COMMIT_SIZE);
}

void Query::visit(const ConstObjectVisitor& visitor) const
{
    if (revisionIds_.empty()) {
        return;
    }

    ASSERT(gateway_);

    auto snapshot = gateway_->snapshot(commitId_);
    ThreadPool processorThread(1);
    common::ExceptionTrap trap;

    loadObjects(
        snapshot, revisionIds_, geomFilter_,
        [&](std::vector<Object> batch)
        {
            auto batchPtr =
                std::make_shared<std::vector<Object>>(std::move(batch));
            processorThread.push(
                [&, batchPtr]
                {
                    trap.try_(processBatchReadOnly, batchPtr, visitor);
                });
        });

    processorThread.shutdown();
    trap.release(); // may throw
}

} // namespace groupedit
} // namespace wiki
} // namespace maps
