#pragma once

#include "common.h"
#include "context.h"
#include "objects/object.h"
#include "collection.h"
#include "branch_helpers.h"
#include "object_update_data.h"

#include <yandex/maps/wiki/revision/commit.h>
#include <maps/libs/geolib/include/bounding_box.h>

#include <map>
#include <functional>
#include <list>

#include "exception.h"

namespace maps {
namespace wiki {

class GeoObject;
class RelationObject;
class RevisionsFacade;
class RuntimeDataCalculator;
class RelationsManager;
class BBoxProvider;

enum class TableAttributesLoadPolicy { Skip, Load };
enum class ServiceAttributesLoadPolicy { Skip, Load };
enum class DanglingRelationsPolicy { Ignore, Check };

struct CachePolicy
{
    const TableAttributesLoadPolicy tableAttributesLoadPolicy;
    const ServiceAttributesLoadPolicy serviceAttributesLoadPolicy;
    const DanglingRelationsPolicy danglingRelationsPolicy;
};

const CachePolicy EDITOR_CACHE_POLICY =
{
    TableAttributesLoadPolicy::Load,
    ServiceAttributesLoadPolicy::Load,
    DanglingRelationsPolicy::Check
};

class ObjectsCache
{
public:
    ObjectsCache(
        const BranchContext& branchContext,
        boost::optional<TCommitId> headCommit,
        const CachePolicy& policy = EDITOR_CACHE_POLICY);
    ~ObjectsCache();

    TCommitId headCommitId() const;

    const BranchContext& branchContext() const { return *branchContext_; }

    Transaction& workCore() { return branchContext_->txnCore(); }
    Transaction& workView() { return branchContext_->txnView(); }

    RevisionsFacade& revisionsFacade() const;
    RelationsManager& relationsManager();
    BBoxProvider& bboxProvider();

    const CachePolicy& policy() const { return policy_; }

    TRevisionId newObjectId();

    boost::optional<ObjectPtr> get(TOid id);
    ObjectPtr getExisting(TOid id);

    GeoObjectCollection get(const TOIds& ids);

    template<class Container>
    GeoObjectCollection get(const Container& ids)
    {
        return get(TOIds(ids.begin(), ids.end()));
    }

    void load(const TOIds& ids) { get(ids); }

    template <class InputIterator>
    GeoObjectCollection get(InputIterator begin, InputIterator end)
    {
        return get(TOIds(begin, end));
    }

    ObjectPtr add(ObjectPtr obj);
    GeoObjectCollection add(const GeoObjectCollection& objects);

    GeoObjectCollection find(const ObjectPredicate& predicate) const;

    /**
     * Save modified objects.
     */
    void save(
        TUid user,
        const std::string& commitActionString,
        const StringMap& actionNotes = StringMap())
    {
        saveWithContext({}, {}, user, commitActionString, actionNotes);
    }

    void saveWithContext(
        const ObjectsDataMap& objectsUpdateData,
        const ObjectsEditContextsPtrMap& editContexts,
        TUid user,
        const std::string& commitActionString,
        const StringMap& actionNotes = StringMap());
    void saveWithContextSkipPermissionsCheck(
        const ObjectsEditContextsPtrMap& editContexts,
        TUid user,
        const std::string& commitActionString,
        const StringMap& actionNotes = StringMap());

    /**
     * Commit current write transaction and replace it by read transaction.
     */
    Token commit();

    bool hasSavedCommit() const { return savedCommit_.get(); }
    const revision::Commit& savedCommit() const;
    void setSavedCommit(revision::Commit commit);

private:
    ObjectPtr cacheObject(ObjectPtr obj);

    std::map<TOid, ObjectPtr> cachedObjects_;

    std::unique_ptr<revision::Commit> savedCommit_;

    std::unique_ptr<BranchContext> branchContext_;

    std::unique_ptr<RuntimeDataCalculator> runtimeDataCalculator_;
    std::unique_ptr<RelationsManager> relationsManager_;
    std::unique_ptr<RevisionsFacade> revisionsFacade_;
    std::unique_ptr<BBoxProvider> bboxProvider_;

    CachePolicy policy_;
};

} // namespace wiki
} // namespace maps
