#pragma once

#include "collection.h"
#include "common.h"
#include "context.h"

#include "relation_infos.h"

#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/branch.h>

#include <maps/libs/geolib/include/bounding_box.h>

#include <boost/noncopyable.hpp>

#include <map>
#include <pqxx/pqxx>

namespace maps {
namespace wiki {

namespace revision {

class Branch;

} // namespace revision

class Context;
class GeoObjectFactory;

struct RelativesIdsDiff
{
    TOIds added;
    TOIds deleted;
};

typedef std::map<TOid, RelativesIdsDiff> RelativesIdsMap;
typedef std::map<std::string, RelativesIdsDiff> RoleToRelativesIdsDiff;

class RevisionsFacade : private boost::noncopyable
{
public:
    enum class CheckPermissionsPolicy
    {
        Check,
        Skip
    };
    typedef std::list<revision::ObjectRevision> RevisionsList;
    typedef std::map<revision::DBID, revision::ObjectRevision> RevisionsMap;

    RevisionsFacade(std::unique_ptr<GeoObjectFactory> factory,
        Transaction& work,
        const revision::Branch& branch,
        boost::optional<TCommitId> headCommit);

    ~RevisionsFacade();

    Transaction& work();
    TCommitId headCommit() const { return snapshot_.maxCommitId(); }

    boost::optional<ObjectPtr> load(TOid oid);

    GeoObjectCollection load(const TOIds& oids);

    template <class TOidsConstIterator>
    GeoObjectCollection load(
        const TOidsConstIterator& startIt,
        const TOidsConstIterator& endIt);

    revision::Commit loadHeadCommit();
    revision::CommitDiff headCommitDiff();

    static TOIds getIdsByBBox(
        const revision::Snapshot& snapshot,
        const geolib3::BoundingBox& bbox,
        const StringSet& categoryIds,
        revision::filters::GeomFilterExpr::Operation op);

    GeoObjectCollection loadRelations(RelationType type, const TOIds& ids);
    GeoObjectCollection loadRelations(RelationType type, const TOIds& ids, const StringSet& roleIds);

    /// Limit is cumulative (for all roles together).
    boost::optional<GeoObjectCollection> loadRelations(RelationType type, TOid id, const StringSet& roleIds, size_t limit);

    std::vector<ObjectPtr> findRelation(TOid masterId, TOid slaveId,
        const std::string& roleId,
        const std::string& masterCategoryId,
        const std::string& slaveCategoryId);

    size_t relativesForRoleCount(
        RelationType type,
        TOid id,
        const std::string& roleId,
        const std::string& relativeCategoryId = {});

    RelativesIdsDiff headCommitRelativesDiff(RelationType type, TOid id);
    RelativesIdsMap headCommitRelativesDiff(RelationType type, const TOIds& ids);
    RoleToRelativesIdsDiff headCommitRelativesDiff(RelationType type, TOid id, const StringSet& roleIds);
    RoleToRelativesIdsDiff headCommitRelativesDiff(RelationType type);

    static RoleToRelativesIdsDiff relationsRevisionsToRoleRelDiff(
        RelationType type,
        const RevisionsList& curRelations,
        const RevisionsList& prevRelations);

    // Generate new sequential object id
    TRevisionId newObjectId();

    // Save objects collection
    revision::Commit save(
        const GeoObjectCollection& collection,
        TUid user,
        const StringMap& commitAttributes,
        const ObjectsDataMap& objectsUpdateData,
        CheckPermissionsPolicy checkPermissionsPolicy);

    revision::DBID branchId() const { return gateway_.branchId(); }

    revision::Snapshot& snapshot() { return snapshot_; }
    revision::RevisionsGateway& gateway() { return gateway_; }
    const revision::RevisionsGateway& gateway() const { return gateway_; }
    const revision::Snapshot& snapshot() const { return snapshot_; }

private:
    ObjectPtr loadRevision(const revision::ObjectRevision& rev);

    revision::filters::ProxyFilterExpr relationsFilter(
        RelationType type, const TOIds& ids, const StringSet& roleIds) const;

    RelativesIdsDiff relationsRevisionsToDiff(RelationType type,
        const RevisionsList& curRelations,
        const RevisionsList& prevRelations);

    template <typename RevisionsContainer>
    GeoObjectCollection collectResult(const RevisionsContainer& revisions);

    std::unique_ptr<GeoObjectFactory> factory_;
    revision::RevisionsGateway gateway_;
    revision::Snapshot snapshot_;
};

template <class TOidsConstIterator>
GeoObjectCollection
RevisionsFacade::load(
    const TOidsConstIterator& startIt, const TOidsConstIterator& endIt)
{
    return load(TOIds{startIt, endIt});
}

} // namespace wiki
} // namespace maps
