#pragma once

#include "manager.h"
#include "layer.h"

#include <saas/protos/rtyserver.pb.h>

#include <saas/rtyserver/indexer_core/parsed_document.h>
#include <kernel/multipart_archive/statistic/archive_info.h>

#include <util/generic/maybe.h>

class TRTYFullArchiveConfig;

template <class T>
struct TReadGuardOps<TMaybe<T>> {
    static inline void Acquire(TMaybe<T>* t) noexcept {
        if (t->Defined()) {
            (**t).AcquireRead();
        }
    }

    static inline void Release(TMaybe<T>* t) noexcept {
        if (t->Defined()) {
            (**t).ReleaseRead();
        }
    }
};

template <class T>
struct TTryReadGuardOps<TMaybe<T>> : TReadGuardOps<TMaybe<T>> {
    static inline bool TryAcquire(TMaybe<T>* t) noexcept {
        return !t->Defined() || (**t).TryAcquireRead();
    }
};

template <class T>
struct TWriteGuardOps<TMaybe<T>> {
    static inline void Acquire(TMaybe<T>* t) noexcept {
        Y_VERIFY(t->Defined());
        (**t).AcquireWrite();
    }

    static inline void Release(TMaybe<T>* t) noexcept {
        Y_VERIFY(t->Defined());
        (**t).ReleaseWrite();
    }
};

template <class T>
struct TTryWriteGuardOps<TMaybe<T>> : TWriteGuardOps<T> {
    static inline bool TryAcquire(TMaybe<T>* t) noexcept {
        Y_VERIFY(t->Defined());
        return (**t).TryAcquireWrite();
    }
};

class TDiskFAManager: public IFAManager {
public:
    class TIterator {
        friend class TDiskFAManager;

    public:
        typedef TSimpleSharedPtr<TIterator> TPtr;

    public:
        inline void Next() {
            Document.Drop();
            Slave->Next();
        }

        inline bool IsValid() const {
            return Slave->IsValid();
        }

        inline ui32 GetDocId() const {
            return Slave->GetDocid();
        }

        inline TBlob GetDocBlob() const {
            return Slave->GetDocument();
        }

        const NRTYServer::TParsedDoc& GetParsedDoc() const;
        const TParsedDocument::TPtr GetDocument(NRTYServer::TMessage::TMessageType command = NRTYServer::TMessage::ADD_DOCUMENT) const;

    private:
        TIterator(const TRTYFullArchiveLayer& owner);

    private:
        const TRTYFullArchiveLayer& Owner;

        mutable NRTYServer::TParsedDoc ParsedDoc;
        mutable TParsedDocument::TPtr Document;
        NRTYArchive::TMultipartArchive::TIteratorPtr Slave;
    };

    class TSampler {
        friend class TDiskFAManager;

    public:
        typedef TSimpleSharedPtr<TSampler> TPtr;

    public:
        bool Seek(ui32 docId);

        bool IsValid() const {
            return DocId != Max<ui32>();
        }

        inline ui32 GetDocId() const {
            return DocId;
        }

        const NRTYServer::TParsedDoc& GetParsedDoc() const;
        const TParsedDocument::TPtr GetDocument(NRTYServer::TMessage::TMessageType command = NRTYServer::TMessage::ADD_DOCUMENT) const;

    private:
        TSampler(const TRTYFullArchiveLayer& owner);

    private:
        const TRTYFullArchiveLayer& Owner;
        ui32 DocId;
        mutable NRTYServer::TParsedDoc ParsedDoc;
        mutable TParsedDocument::TPtr Document;
    };

public:
    TDiskFAManager(
        const TFsPath& dir,
        ui32 docCount,
        const TRTYServerConfig& config,
        ui32 writeSpeedBytes,
        const TSet<TString>& layers,
        bool enableFastUpdates,
        bool readOnly,
        bool isFinal);
    virtual ~TDiskFAManager();

    // NRTYServer::IIndexComponentManager
    virtual bool GetDocInfo(const ui32 docId, NJson::TJsonValue& result) const override;
    virtual bool UpdateDoc(ui32 docId, const TParsedDocument::TPtr doc) override;
    virtual bool IsRemoved(ui32 docid) const override;

    virtual bool DoOpen() override;
    virtual bool DoClose() override;
    virtual void Remap(const TVector<ui32>* remapTable) override;
    virtual void Index(const TParsedDocument& document, const ui32 docId) override;
    void IndexUnsafe(const NRTYServer::TParsedDoc& document, const ui32 docId);
    void IndexUnsafe(const TBlob& document, const ui32 docId);
    void IndexUnsafe(const TParsedDocument& document, const ui32 docId);
    void IndexLayerUnsafe(const TString& layer, const TBlob& document, const ui32 docId);
    virtual TParsedDocument::TPtr GetDoc(ui32 docId, NRTYServer::TMessage::TMessageType command = NRTYServer::TMessage::ADD_DOCUMENT, bool unsafe = false) const override;
    TMaybe<NRTYServer::TParsedDoc> GetDocFast(ui32 docId) const;
    virtual THolder<NRTYServer::IDocSearchInfoIterator> GetDocSearchInfoIterator() const override;
    bool ReadParsedDoc(const TString& layer, NRTYServer::TParsedDoc& pd, ui32 docId, ui32* size = nullptr, bool unsafe = false) const;
    TBlob ReadRawDoc(const TString& layer, ui32 docId) const override;
    bool Repair();
    ui64 GetLockedMemorySize() const;

    virtual bool SearchDocument(ui32 docId, const TCgiParameters& cgi, NMetaProtocol::TDocument& doc, const TFAExtraSearchParams* extraParams = nullptr) const override final;
    virtual bool SearchDocumentCandidate(TDocIdCandidate docId, const TCgiParameters& cgi, NMetaProtocol::TDocument& doc, const TFAExtraSearchParams* extraParams = nullptr) const override final;

    inline TIterator::TPtr CreateIterator(const TString& layer = NRTYServer::NFullArchive::FullLayer) const {
        return new TIterator(*GetLayer(layer));
    }
    inline TIterator::TPtr CreateMainLayerIterator() const {
        return new TIterator(*GetLayer(MainLayer));
    }
    TVector<size_t> GetDocIds() const {
        return GetLayer(MainLayer)->GetArchive()->GetDocIds();
    }

    inline TSampler::TPtr CreateSampler(const TString& layer = NRTYServer::NFullArchive::FullLayer) const {
        return new TSampler(*GetLayer(layer));
    }

    inline TSet<TString> GetLayers() const {
        TSet<TString> result;
        for (const auto& l : Layers)
            result.insert(l.first);
        return result;
    }

    inline bool DoesLayerExist(const TString& layer) const {
        return Layers.contains(layer);
    }

    NRTYArchive::TArchiveInfo GetArchiveInfo() const;

    static bool Check(const TFsPath& dir, const TSet<TString>& layers, const TRTYFullArchiveConfig& config);
    static bool ExistsLayer(const TFsPath& dir, const TString& layer);
    static void RemoveLayer(const TFsPath& dir, const TString& layer);
    static bool FullArchiveExists(const TFsPath& dir, const TRTYFullArchiveConfig& config);
    static bool LayerIsEmpty(const TFsPath& dir, const TString& layer);

protected:
    virtual ui32 GetDocumentsCountImpl() const override;
    virtual ui32 DoRemoveDocids(const TVector<ui32>& docids) override;
    virtual ui32 DoRemoveDocidsUnsafe(const TVector<ui32>& docids) override; // Need to lock RemapMutex

    Y_FORCE_INLINE TRTYFullArchiveLayer::TPtr GetLayer(const TString& layer) const {
        TLayers::const_iterator i = Layers.find(layer);
        if (i == Layers.end())
            ythrow yexception() << "there is no layer " << layer << " in " << Directory.GetPath();
        return i->second;
    }

private:
    void AddLayer(const TRTYServerConfig& config, const NRTYArchive::TMultipartConfig& lc, const TString& directory, const TString& layerName, ui32 writeSpeedBytes, bool readOnly);

private:
    typedef TVector<ui32> TRemapTable;
    typedef TMap<TString, TRTYFullArchiveLayer::TPtr> TLayers;

private:
    void RepairByData();

private:
    const TFsPath Directory;
    const bool ReadOnly;
    const bool FastUpdatesProcessing;

    THolder<TRemapTable> RemapTable;
    ui32 DocsCount;
    TMaybe<TRWMutex> RemapMutex;
    NRTYServer::TIndexMetadata::TFullArchiveHeader Header;
    TMutex DeferredRemovesMutex;
    TVector<ui32> DeferredRemoves;
    TLayers Layers;
    TRTYFullArchiveLayer::TPtr FullLayer;
    TString MainLayer;
};
