#pragma once
#include <saas/rtyserver/config/const.h>
#include <saas/rtyserver/common/stream_data.h>
#include <saas/rtyserver/model/component.h>
#include <saas/rtyserver/pruning_config/pruning_config.h>
#include <saas/util/messages_collector.h>

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

#include <library/cpp/json/json_value.h>

#include <util/generic/maybe.h>

class TFactorStorage;
class TParsedDocument;
struct TRTYServerConfig;

class TParsedDocument : public TNonCopyable, public TPruningConfig::IDocument {
public:
    class TParsedEntity: public NRTYServer::IParsedEntity {
    public:
        typedef TAtomicSharedPtr<TParsedEntity> TPtr;
    public:
        TParsedEntity(TConstructParams& params);
        virtual ~TParsedEntity() {}

        bool GetGroupAttrValue(const TString& /*name*/, i64& /*result*/) const override {
            return false;
        }

        bool FillFactorStorage(TFactorView& /*factorStorage*/) const override {
            return false;
        }

        void MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const override;
        bool FillFromProto(const NRTYServer::TParsedDoc& /*pd*/, const NRTYServer::TDocParseContext& /*context*/) override {
            return true;
        }
        void SetDocSearchInfo(const TDocSearchInfo& /*docInfo*/) override {}
        void SetDocId(ui32 /*docid*/) override {}

        virtual bool IsEmpty() const { return false; }

        void ApplyPatch(const TParsedDocument& doc) override;

    protected:
        virtual void DoApplyPatch(const TParsedDocument& /*doc*/) {}
        const NRTYServer::IIndexComponent& Component;
        TParsedDocument& Owner;
    };

    class TProtectedFieldsSetter {
    protected:
        static void AddExtraTimestamp(TParsedDocument& document, NRTYServer::TStreamId stream, NRTYServer::TTimestampValue value) {
            document.UpdateExtraTimestamp(stream, value);
        }
        static void RestoreIsNeeded(TParsedDocument& document) {
            document.NeedRestore = true;
        }
        static void SetQueryDel(TParsedDocument& document, const char *value) {
            document.QueryDel = value;
        }
        static void SetRealtime(TParsedDocument& document, bool value) {
            document.Realtime = value;
        }
        static void SetVersion(TParsedDocument& document, ui64 value) {
            document.Version = value;
        }
        static void SetStreamId(TParsedDocument& document, NRTYServer::TStreamId stream) {
            document.StreamId = stream;
        }
        static void SetTimestamp(TParsedDocument& document, NRTYServer::TTimestampValue timestamp) {
            document.Timestamp = timestamp;
        }
        static void SetVersionTimestamp(TParsedDocument& document, ui32 timestamp) {
            document.VersionTimestamp = timestamp;
        }
        static void SetUpdateType(TParsedDocument& document, NRTYServer::TMessage::TUpdateType type) {
            document.UpdateType = type;
        }
        static void SetPosition(TParsedDocument& document, const NRTYServer::TPosition& position) {
            Y_ASSERT(position.IsInitialized());
            document.Position = position;
        }
        static void AddExtraPosition(TParsedDocument& document, const NRTYServer::TPosition& position) {
            Y_ASSERT(position.IsInitialized());
            document.ExtraPositions.push_back(position);
        }
        static void Clear(TParsedDocument& document);
    };

    using TComponentsEntitiesStorage = THashMap<TString, NRTYServer::IParsedEntity::TPtr>;
    using TTimestamps = TMap<NRTYServer::TStreamId, NRTYServer::TTimestampValue>;
    using TPtr = TAtomicSharedPtr<TParsedDocument>;

public:
    TParsedDocument(const TRTYServerConfig& config);

    template<class T>
    const T* GetComponentEntity(const TString& componentName) const {
        TComponentsEntitiesStorage::const_iterator i = ComponentsEntitiesStorage.find(componentName);
        if (i == ComponentsEntitiesStorage.end())
            return nullptr;
        return dynamic_cast<T*>(i->second.Get());
    }

    template<class T>
    T* GetComponentEntity(const TString& componentName) {
        TComponentsEntitiesStorage::iterator i = ComponentsEntitiesStorage.find(componentName);
        if (i == ComponentsEntitiesStorage.end())
            return nullptr;
        return dynamic_cast<T*>(i->second.Get());
    }

    void CreateParsedEntity(const NRTYServer::IIndexComponent* component);
    void ApplyPatch(const TParsedDocument& doc);
    void MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const;
    bool FillFromProto(const NRTYServer::TParsedDoc& pd, NRTYServer::TMessage::TMessageType command, const NRTYServer::TDocParseContext& context);

    bool IsEmpty() const;

    bool IsNeedRestore() const {
        return NeedRestore;
    }

    const TString& GetQueryDel() const {
        return QueryDel;
    }

    void SetDocSearchInfo(const TDocSearchInfo& docInfo);
    void SetTempDiskInfo(ui32 docid);
    void SetTempDiskInfo(const TString& indexName);

    const TDocSearchInfo& GetDocSearchInfo() const {
        return DocInfo;
    }

    bool GetRealtime() const {
        return Realtime;
    }

    TMessagesCollector& GetErrorsCollector() const {
        return ErrorsCollector;
    }

    NRTYServer::TMessage::TUpdateType GetUpdateType() const {
        return UpdateType;
    }

    ui64 GetVersion() const {
        return Version;
    }

    ui32 GetEntitiesHash() const {
        if (!EntitiesHash.Defined()) {
            TWriteGuard wg(HashMutex);
            if (!EntitiesHash.Defined()) {
                EntitiesHash = CalcEntitiesFullHash();
            }
        }
        TReadGuard rg(HashMutex);
        return *EntitiesHash;
    }

    NRTYServer::TStreamId GetStreamId() const {
        return StreamId;
    }

    ui64 GetTimestamp() const final {
        return SafeIntegerCast<ui64>(Timestamp);
    }

    ui32 GetVersionTimestamp() const /* non-virtual */ {
        return VersionTimestamp;
    }

    TMaybe<NRTYServer::TPosition> GetPosition() const {
        return Position;
    }

    const TTimestamps& GetExtraTimestamps() const {
        return ExtraTimestamps;
    }

    const TVector<NRTYServer::TPosition>& GetExtraPositions() const {
        return ExtraPositions;
    }

    const TRTYServerConfig& GetConfig() const {
        return Config;
    }

    NRTYServer::TShard GetShard() const;

    virtual bool GetGroupAttrValue(const TString& name, i64& result) const override;
    virtual void FillFactorStorage(TFactorView& factorStorage) const override;
    virtual const TString& GetUrl() const override;
    virtual ui64 GetKeyPrefix() const override;

    void SignalFailed() const {
        Failed = true;
    }

    bool IsFailed() const {
        return Failed;
    }

    void SignalDeprecated() const {
        Deprecated = true;
    }

    bool IsDeprecated() const {
        return Deprecated;
    }

    void SignalIsUpdate() const {
        IsUpdateFlag = true;
    }

    bool GetIsUpdate() const {
        return IsUpdateFlag;
    }

    bool IsRestore() const {
        return Restore;
    }

    void SignalRestore() {
        Restore = true;
    }
private:
    ui32 CalcEntitiesFullHash() const;
    void MergeTimestamp(const NRTYServer::TStreamId stream, const NRTYServer::TTimestampValue value);
    void MergeExtraTimestamps(const TTimestamps& other);
    void UpdateExtraTimestamp(const NRTYServer::TStreamId stream, const NRTYServer::TTimestampValue value);

private:
    const TRTYServerConfig& Config;

    mutable TComponentsEntitiesStorage ComponentsEntitiesStorage;
    mutable TMessagesCollector ErrorsCollector;
    mutable TMaybe<ui32> EntitiesHash;
    TRWMutex HashMutex;

    TDocSearchInfo DocInfo;
    TString QueryDel;

    TTimestamps ExtraTimestamps;
    TMaybe<NRTYServer::TPosition> Position;
    TVector<NRTYServer::TPosition> ExtraPositions;
    ui64 Version = 0;
    NRTYServer::TTimestampValue Timestamp = 0;
    ui32 VersionTimestamp = 0;
    NRTYServer::TStreamId StreamId = 0;
    NRTYServer::TMessage::TUpdateType UpdateType = NRTYServer::TMessage::UNDEFINED;

    mutable bool Failed = false;
    mutable bool Deprecated = false;
    mutable bool IsUpdateFlag = false;

    bool NeedRestore = false;
    bool Realtime = false;
    bool Restore = false;
};
