#pragma once

#include "config.h"
#include "common.h"
#include "context.h"
#include "result_interpreter.h"
#include "filter_helpers.h"

#include <yandex/maps/wiki/revision/common.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/objectrevision.h>

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

#include <cstddef>
#include <list>
#include <vector>

namespace maps::wiki::revision {

template<typename Container>
struct BatchFilterGenerator
{
    using type = std::function<filters::ProxyFilterExpr (const maps::common::Batch<Container> &)>;
};

enum class CheckResultSize
{
    Yes,
    No
};

class ReaderImpl
{
public:
    ReaderImpl(
        pqxx::transaction_base& txn,
        BranchType branchType,
        DBID branchId,
        DescriptionLoadingMode descriptionLoadingMode
    );

    //Loading methods are guaranteed to sort the data
    //by revisionId in ascending order

    //filter-aware version of loading revisions
    Revisions loadRevisions(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit,
        const filters::FilterExpr& expr
    ) const;

    //pipelined version of the loading revisions
    Revisions loadRevisions(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit,
        const DBIDSet& objectIds
    ) const;

    /**
     * Some client classes (Snapshot, HistoricalSnapshot, Reader) need
     * support for both filtering and batched filtering
     * by a custom set of columns.
     *
     * NOTE: batching will be performed without additional batchData modification.
     *
     * NOTE: When basicFilter is nullptr,
     *       and checkResultSize is Yes
     *       an extra check will be applied:
     *       Each revision specified in batchData should be loaded.
     */
    template<typename Container>
    Revisions loadRevisions(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit,
        const filters::FilterExpr* basicFilter,
        CheckResultSize checkResultSize,
        typename BatchFilterGenerator<Container>::type batchFilterGenerator,
        const Container& batchData
    ) const;

    //filter-aware version of loading revisionIds
    RevisionIds loadRevisionIds(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit,
        const filters::FilterExpr& expr
    ) const;

    //pipelined version of loading revisionIds
    RevisionIds loadRevisionIds(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit,
        const DBIDSet& objectIds
    ) const;

    //pipelined version of loading revisionIds
    RevisionIds loadRevisionIds(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit,
        const DBIDSet& objectIds,
        const filters::FilterExpr& expr
    ) const;

    void setDescriptionLoadingMode(DescriptionLoadingMode descriptionLoadingMode)
    {
        descriptionLoadingMode_ = descriptionLoadingMode;
    }

    pqxx::transaction_base& work()
    {
        return txn_;
    }

    BranchType branchType() const
    {
        return branchType_;
    }

    DBID branchId() const
    {
        return branchId_;
    }

    //returns textual query which contains
    //WITH, SELECT, FROM, WHERE, LIMIT clauses and
    //UNION ALL's requests for required partitions
    //
    //WARN: will throw in case of empty configs
    //
    //WARN: made public for testing purposes only.
    //  Do not use this method in client code!
    std::string makeQuery(
        const db::StructuredQuery& query,
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId,
        const OptionalSize& limit
    ) const;

private:
    //with, select, from clause generators
    //are placed in anonymous namespace in the cpp file

    //returns textual WHERE clause expressing the filtering expression,
    //including filters for revision.commit schema
    std::string makeWhereClause(
        const std::string& filterStatement,
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId
    ) const;

    //returns textual query for a single partition, which contains
    //SELECT, FROM, WHERE clauses
    std::string makePartitionQuery(
        const db::StructuredSubquery& subquery,
        LoadLimitations limitations,
        const OptionalSnapshotId& snapshotId
    ) const;

    //returns textual clause expressing if commit
    //is of branchType_ type and belogns to branchId_ branch
    std::string makeCommitStateFilterClause(
        LoadLimitations loadLimitations
    ) const;

    //returns textual clause expressing
    //if commit should be taken into consideration
    //and excluding past object revisions (if needed)
    std::string makeHistoricalFilterClause(
        LoadLimitations loadLimitations,
        const OptionalSnapshotId& snapshotId
    ) const;

    //Historical revisions couldn't be filtered when requesting data from stable branch
    //when user-generated filters were applied
    //(see comment in ReaderImpl::makeHistoricalFilterClause implementation)
    //Following two methods will perform additional database request
    //to filter non-head revisions correctly
    //
    //Branch checking, non-emptiness of revisionIds, and their ascending order
    //are responsibilities of the calling side
    RevisionIds removeNonHeadRevisions(
        RevisionIds&& revisionIds,
        const OptionalSnapshotId& snapshotId
    ) const;
    Revisions removeNonHeadRevisions(
        Revisions&& revisions,
        const OptionalSnapshotId& snapshotId
    ) const;

    bool needExcludeHistoricals(LoadLimitations loadLimitations) const
    {
        return (
            (branchType_ != BranchType::Trunk) &&
            (loadLimitations == LoadLimitations::Snapshot)
        );
    }

    void fetchHeavyContent(Revisions& revisions) const;

    BranchType branchType_;
    DBID branchId_;
    pqxx::transaction_base& txn_;

    DescriptionLoadingMode descriptionLoadingMode_;
};

} // namespace maps::wiki::revision

