#include <yandex/maps/wiki/revision/reader.h>
#include "reader_impl.h"
#include "helpers.h"
#include "filter_generators.h"

#include <maps/libs/common/include/exception.h>

#include <algorithm>
#include <utility>

namespace maps::wiki::revision {

using namespace helpers;

namespace {

Revisions loadRevisionsHelper(
    const ReaderImpl& readerImpl,
    RevisionIds&& ids,
    const filters::FilterExpr* expr = nullptr
)
{
    RevisionIds revisionIds(std::move(ids));
    //performing classification by (commitId, objectId)
    std::sort(
        revisionIds.begin(),
        revisionIds.end(),
        [](const RevisionID& lhs, const RevisionID& rhs)
        {
            if (lhs.commitId() != rhs.commitId()) {
                return (lhs.commitId() < rhs.commitId());
            }
            return (lhs.objectId() < rhs.objectId());
        }
    );

    return readerImpl.loadRevisions(
        LoadLimitations::None,
        NO_SNAPSHOT_ID,
        NO_LIMIT,
        expr,
        CheckResultSize::Yes,
        filters::makeFilterForRevisionIdBatch<maps::common::Batch<RevisionIds>>,
        revisionIds
    );
}

} //anonymous namespace

Reader::Reader(std::shared_ptr<ReaderImpl> impl)
    : impl_(std::move(impl))
{
    ASSERT(impl_);
}

Reader::~Reader() = default;

pqxx::transaction_base&
Reader::work() const
{
    return impl_->work();
}

DBID
Reader::branchId() const
{
    return impl_->branchId();
}

ObjectRevision
Reader::loadRevision(const RevisionID& revisionId) const
{
    checkRevisionId(revisionId);

    auto revision = tryLoadRevision(revisionId);
    REQUIRE(
        revision,
        "Can not load object data, for revisionId " << revisionId
    );
    return *revision;
}

std::optional<ObjectRevision>
Reader::tryLoadRevision(const RevisionID& revisionId) const
{
    checkRevisionId(revisionId);

    auto revisions = impl_->loadRevisions(
        LoadLimitations::None,
        NO_SNAPSHOT_ID,
        NO_LIMIT,
        (
            filters::ObjRevAttr::objectId() == revisionId.objectId() &&
            filters::ObjRevAttr::commitId() == revisionId.commitId()
        )
    );
    if (revisions.empty()) {
        return std::nullopt;
    }
    ASSERT(revisions.size() == 1);
    return revisions.front();
}

Revisions
Reader::loadRevisions(const ConstRange<RevisionID>& ids) const
{
    if (ids.empty()) {
        return {};
    }
    RevisionIds revisionIds = revisionIdsToVector(ids);
    return loadRevisionsHelper(*impl_, std::move(revisionIds));
}

Revisions
Reader::loadRevisions(
    const ConstRange<RevisionID>& ids,
    const filters::FilterExpr& expr
) const
{
    if (ids.empty()) {
        return {};
    }
    RevisionIds revisionIds = revisionIdsToVector(ids);
    return loadRevisionsHelper(*impl_, std::move(revisionIds), &expr);
}

Revisions
Reader::loadRevisions(const filters::FilterExpr& expr) const
{
    return impl_->loadRevisions(
        LoadLimitations::None,
        NO_SNAPSHOT_ID,
        NO_LIMIT,
        expr
    );
}

RevisionIds
Reader::loadRevisionIds(const filters::FilterExpr& expr) const
{
    return impl_->loadRevisionIds(
        LoadLimitations::None,
        NO_SNAPSHOT_ID,
        NO_LIMIT,
        expr
    );
}

RevisionIds
Reader::commitRevisionIds(DBID commitId) const
{
    checkCommitId(commitId);

    auto revisionIds = impl_->loadRevisionIds(
        LoadLimitations::None,
        NO_SNAPSHOT_ID,
        NO_LIMIT,
        filters::ObjRevAttr::commitId() == commitId
    );
    return revisionIds;
}

Revisions
Reader::commitRevisions(DBID commitId) const
{
    checkCommitId(commitId);

    auto revisions = impl_->loadRevisions(
        LoadLimitations::None,
        std::nullopt,
        NO_LIMIT,
        filters::ObjRevAttr::commitId() == commitId
    );
    return revisions;
}

CommitDiff
Reader::commitDiff(DBID commitId) const
{
    checkCommitId(commitId);
    return maps::wiki::revision::commitDiff(impl_->work(), commitId);
}

void
Reader::setDescriptionLoadingMode(DescriptionLoadingMode mode)
{
    impl_->setDescriptionLoadingMode(mode);
}

} // namespace maps::wiki::revision
