#include "l2_normalizer.h"
#include "l2_core.h"

#include <saas/rtyserver/indexer_core/index_metadata_processor.h>
#include <saas/rtyserver/components/fullarchive/disk_manager.h>
#include <google/protobuf/messagext.h>

#include <util/folder/path.h>
#include <util/system/compiler.h>

namespace NRTYServer {
    class TL2Normalizer::TImpl final {
    public:
        IL2ComponentCore::TPtr Core;

    public:
        TImpl(IL2ComponentCore::TPtr core)
            : Core(core)
        {
            Y_VERIFY(core);
        }
    };

    class TL2InputIterator final: public TL2IteratorBase {
    private:
        TDiskFAManager::TIterator::TPtr Iter;

    public:
        TL2InputIterator(const TDiskFAManager& fa, const TString& componentName)
            : Iter(fa.CreateIterator(componentName))
        {
            Fetch();
        }

        Y_FORCE_INLINE void Fetch() {
            if (Y_UNLIKELY(!Iter->IsValid())) {
                DocId = Max<ui32>();
                return;
            }

            Current = Iter->GetDocBlob();
            DocId = Iter->GetDocId();
        }

        void Next() override {
            if (Iter->IsValid()) {
                Iter->Next();
            }
            Fetch();
        }
    };

    namespace {
        static TString FormatDisplayName(const TString& componentName) {
            TStringStream ss;
            ss << "TL2Normalizer(" << componentName << ")";
            return ss.Str();
        }
    }

    TL2Normalizer::TL2Normalizer(const TString& name, IL2ComponentCore::TPtr core, const TRTYServerConfig& config, const TL2DocStorageParams& arcParams)
        : NRTYServer::INormalizer(config)
        , Impl(MakeHolder<TImpl>(core))
        , DisplayName(FormatDisplayName(name))
        , ComponentName(name)
        , StorageParams(arcParams)
    {
        CHECK_WITH_LOG(!StorageParams.GetLayer().empty() && !StorageParams.GetPropertyName().empty());
    }

    TL2Normalizer::~TL2Normalizer() {
    }

    const char* TL2Normalizer::Name() const {
        return DisplayName.c_str();
    }

    TMaybe<ui32> TL2Normalizer::GetNormalizerVersion(const TIndexMetadata& meta) const {
        return GetNormalizerVersion(meta, ComponentName);
    }

    TMaybe<ui32> TL2Normalizer::GetNormalizerVersion(const TIndexMetadata& meta, const TString& componentName) {
        for (const auto& header : meta.GetL2IndexHeaders()) {
            if (header.GetEntityName() == componentName) {
                return header.GetNormalizerVersion();
            }
        }
        return Nothing();
    }

    void TL2Normalizer::SetNormalizerVersion(TIndexMetadata& meta, const TString& componentName, ui32 version) {
        size_t cnt = meta.L2IndexHeadersSize();
        for (size_t i = 0; i < cnt; ++i) {
            auto* header = meta.MutableL2IndexHeaders(i);
            if (header->GetEntityName() == componentName) {
                header->SetNormalizerVersion(version);
                return;
            }
        }
        {
            auto* header = meta.AddL2IndexHeaders();
            header->SetEntityName(componentName);
            header->SetNormalizerVersion(version);
        }
    }

    TL2IteratorBase::TPtr TL2Normalizer::CreateInputIterator(const TNormalizerContext& context) const {
        // This function does not use indexfrq, so the result iterator may include documents that are
        // marked as removed (deleted by fast update). This is the desired behavior.
        const TDiskFAManager* fullArc = context.Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
        VERIFY_WITH_LOG(fullArc && fullArc->IsOpened(), "Fullarc must be opened");
        return MakeIntrusive<TL2InputIterator>(*fullArc, ComponentName);
    }

    THolder<IDocSearchInfoIterator> TL2Normalizer::CreateDocSearchInfoIterator(const TNormalizerContext& context) const {
        const IFAManager* fullArc = context.Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
        VERIFY_WITH_LOG(fullArc && fullArc->IsOpened(), "Fullarc must be opened");
        return fullArc->GetDocSearchInfoIterator();
    }

    ui32 TL2Normalizer::GetMaxDocId(const TNormalizerContext& context) const {
        const IFAManager* fullArc = context.Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
        VERIFY_WITH_LOG(fullArc && fullArc->IsOpened(), "Fullarc must be opened");
        return fullArc->GetDocumentsCount() - 1; // returns Max<ui32>() if the archive is empty
    }

    void TL2Normalizer::RemoveLayer(const TNormalizerContext& context) const {
        INFO_LOG << "Removing l2 layer " << ComponentName << " layer in " << context.Dir.BaseName() << Endl;
        AssertCorrectIndex(!context.Config.IsReadOnly, "Cannot RemoveLayer() in read-only mode");
        TDiskFAManager::RemoveLayer(context.Dir.PathName(), ComponentName);
    }

    bool TL2Normalizer::AllRight(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
        const TFsPath directory = context.Dir.PathName();
        const TIndexMetadataProcessor meta(directory);
        TMaybe<ui32> ver = GetNormalizerVersion(*meta, /*entityName=*/ComponentName);
        if (!ver) {
            return true; // it is allright - the index was build without the component, or .meta is deleted by admin
        }
        return ver == Impl->Core->GetNormalizerVersion();
    }

    void TL2Normalizer::Fix(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& indexFrq) const {
        Y_ENSURE(AllRight(context, indexFrq), Y_FUNC_SIGNATURE << ": not implemented");
    }
}
