#pragma once

#include <saas/rtyserver/components/suggest/config/config.h>
#include <saas/rtyserver/components/suggest/html_parser/dict_filter.h>

#include <saas/rtyserver/indexer_core/parsed_document.h>
#include <saas/rtyserver/indexer_core/document_parser.h>
#include <saas/protos/rtyservice.pb.h>
#include <saas/rtyserver/components/generator/parsed_entity.h>
#include <google/protobuf/messagext.h>

#include <util/charset/utf8.h>
#include <util/generic/cast.h>

struct TSentInfo;

class TSuggestRecord {
private:
    TString Suggest;
    ui64 Kps;
    TVector<ui32> Factors;
    ui32 DocId;

public:
    static const ui32 NoDocId;

public:
    TSuggestRecord()
        : Kps(0)
        , DocId(NoDocId)
    {
    }

    TSuggestRecord(const TString& suggest, ui64 kps, const TVector<ui32>& factors, ui32 docId)
        : Suggest(suggest)
        , Kps(kps)
        , Factors(factors)
        , DocId(docId)
    {
    }

    void SuggestLowerCase() {
        Suggest = ToLowerUTF8(Suggest);
    }

    ui32& MutableDocId() {
        return DocId;
    }

    void ResetDocId() {
        DocId = NoDocId;
    }

    ui32 GetDocId() const {
        return DocId;
    }

    ui32 GetWeight() const {
        CHECK_WITH_LOG(Factors.size());
        return (ui64)Factors[0];
    }

    ui32 GetCount() const {
        CHECK_WITH_LOG(Factors.size() > 1);
        return (ui64)Factors[1];
    }

    ui32 GetWordsCount() const {
        CHECK_WITH_LOG(Factors.size() > 2);
        return (ui64)Factors[2];
    }

    TFactorStorage GetFactors() const {
        TFactorStorage result(Factors.size());
        for (ui32 i = 0; i < Factors.size(); ++i)
            result[i] = BitCast<float>(Factors[i]);
        return result;
    }

    void LoadFactors(const TBasicFactorStorage& erfBlock) {
        CHECK_WITH_LOG(Factors.size() == 0);
        for (ui32 i = 0; i < erfBlock.Size(); ++i) {
            Factors.push_back(BitCast<ui32>(erfBlock[i]));
        }
    }

    void AppendFactor(ui32 factor) {
        Factors.push_back(factor);
    }

    void ClearFactors() {
        Factors.clear();
    }

    bool CompareWithKps(const TString& text, ui64 kps) const {
        return (GetKps() < kps) || (GetKps() == kps && ToLowerUTF8(GetSuggest()) < ToLowerUTF8(text));
    }

    bool PossibleForMerge(const TSuggestRecord* r) const {
        if (!r)
            return false;
        return
            (ToLowerUTF8(r->GetSuggest()) == ToLowerUTF8(GetSuggest())) &&
            (r->GetKps() == GetKps()) &&
            (r->Factors.size() == Factors.size())
            ;
    }

    void Merge(const TSuggestRecord& record) {
        VERIFY_WITH_LOG(PossibleForMerge(&record), "Incorrect merge case: %s/%s", Suggest.data(), record.GetSuggest().data());

        for (ui32 i = 0; i < Factors.size(); ++i)
            Factors[i] += record.Factors[i];
    }

    NRTYService::TSuggestRecord SerializeToProto() const {
        NRTYService::TSuggestRecord sr;
        if (DocId != NoDocId)
            sr.SetDocId(DocId);
        sr.SetKps(Kps);
        sr.SetSuggest(Suggest);
        for (ui32 i = 0; i < Factors.size(); ++i) {
            sr.AddFactors(Factors[i]);
        }
        return sr;
    }

    void Save(IOutputStream* out) const {
        NRTYService::TSuggestRecord sr = SerializeToProto();
        ::google::protobuf::io::TProtoSerializer::Save(out, sr);
    }

    void Load(IInputStream* in) {
        NRTYService::TSuggestRecord sr;
        ::google::protobuf::io::TProtoSerializer::Load(in, sr);
        Suggest = sr.GetSuggest();
        Kps = sr.GetKps();
        if (sr.HasDocId())
            DocId = sr.GetDocId();
        else
            DocId = NoDocId;
        Factors.clear();
        for (ui32 i = 0; i < sr.FactorsSize(); ++i) {
            Factors.push_back(sr.GetFactors(i));
        }
    }

    ui32 SaveSuggest(IOutputStream& out) const {
        NRTYService::TSuggestRecord sr;
        sr.SetSuggest(Suggest);
        sr.SetKps(Kps);
        ::google::protobuf::io::TProtoSerializer::Save(&out, sr);

        TStringStream ss;
        ::google::protobuf::io::TProtoSerializer::Save(&ss, sr);
        return ss.Size();
    }

    bool operator<(const TSuggestRecord& item) const {
        return CompareWithKps(item.Suggest, item.Kps);
    }

    const TString& GetSuggest() const {
        return Suggest;
    }

    ui64 GetKps() const {
        return Kps;
    }

    struct TLessByFactors {
        inline bool operator()(const TSuggestRecord& a, const TSuggestRecord& b) const {
            return a.GetWeight() > b.GetWeight();
        }
    };

    struct TLessByText {
        inline bool operator()(const TSuggestRecord& a, const TSuggestRecord& b) const {
            return a < b;
        }
    };
};

class TInfoBySentence {
private:
    TVector<TSuggestRecord> Sentences;
public:

    TInfoBySentence() {

    }

    ui32 CountSents() const {
        return Sentences.size();
    }

    const TSuggestRecord& SentInfo(ui32 index) const {
        return Sentences[index];
    }

    void AddInfo(const TSuggestRecord& info) {
        return Sentences.push_back(info);
    }

};

class TSuggestParsedEntity: public TBaseGeneratorParsedEntity {
private:
    TInfoBySentence InfoBySentence;
    TString Body;
public:
    TSuggestParsedEntity(TConstructParams& params);

    const TInfoBySentence& GetInfoBySentence() const {
        return InfoBySentence;
    }

    TInfoBySentence& GetInfoBySentence() {
        return InfoBySentence;
    }

    void SetBody(const TString& body) {
        Body = body;
    }

    virtual bool IsEmpty() const override {
        return InfoBySentence.CountSents() == 0;
    }

    void MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const override {
        TBaseGeneratorParsedEntity::MergeToProto(pd, context);
        if (!pd.MutableDocument()->HasBody())
            pd.MutableDocument()->SetBody(Body);
    }

protected:
    void DoApplyPatch(const TParsedDocument& doc) override {
        VERIFY_WITH_LOG(doc.GetConfig().IndexGenerator != Component.GetName() || doc.GetConfig().IsSecondaryMetaServiceComponent, "Incorrect action for suggest");
    }

};

class TSuggestComponentParser : public TBaseGeneratorEntityParser {
private:
    TSuggestZonesInfo ZonesInfo;
    NSearchMapParser::TShardsInterval AllowedInterval;
    const TSuggestComponentConfig* ConfigSuggest;
    TDictFilter DictFilter;

public:
    explicit TSuggestComponentParser(const TRTYServerConfig& config);
    virtual void Parse(TParsingContext& context) const;

private:
    TString GetBody(TParsingContext& context) const;
    NSearchMapParser::TShardIndex GetShard(const TSentInfo& entry) const;
};
