#include "processor.h"

#include "hits/hits_builder.h"
#include "saas/protos/cgi_parameters.pb.h"
#include "sums/sums_builder.h"
#include "facets/facet_builder.h"
#include "facets/gafacet_builder.h"
#include "extended_facets/extended_facets.h"
#include "filters/docids_filter.h"
#include "filters/regexp_docid_filter.h"
#include "util/generic/fwd.h"

#include <iterator>
#include <library/cpp/codecs/codecs.h>
#include <library/cpp/string_utils/base64/base64.h>
#include <saas/library/cgi/compressed_data.h>
#include <saas/protos/compressed_data.pb.h>
#include <saas/protos/cgi_parameters.pb.h>
#include <saas/rtyserver/search/fastarchive.h>
#include <search/reqparam/reqparam.h>
#include <kernel/qtree/richrequest/richnode.h>
#include <util/string/cast.h>
#include <util/string/split.h>

namespace {
    const char LocalDelimiter[] = ",";

    void AddDocs(TDocidsFilter::TDocIds& docids, const IRemapperUrlDocId& remapper, ui64 kps, const TVector<TString>& vUrls) {
        for (TVector<TString>::const_iterator i = vUrls.begin(), e = vUrls.end(); i != e; ++i) {
            ui32 docId = 0;
            if (remapper.RemapUrl2DocId(TDocSearchInfo(*i, kps), docId))
                docids.insert(docId);
        }
    }

    TVector<TString> ParseUrlsFromPlainString(const TString& serializedUrls) {
        return StringSplitter(serializedUrls).SplitBySet(", ").SkipEmpty();
    }

    TVector<TString> ParseUrlsFromBase64ProtoString(const TString& serializedUrls) {
        TString protoCompressedDataContainer = Base64StrictDecode(serializedUrls);

        NRTYServer::TCompressedDataContainer compressedDataContainer;
        if (!compressedDataContainer.ParseFromString(protoCompressedDataContainer)) {
             ythrow yexception() << "Cannot deserialize TCompressedDataContainer";
        }

        TString protoDataString = ExtractDecompressedData(compressedDataContainer);

        NRTYServer::TUrlsParameter urlsParameter;
        if (!urlsParameter.ParseFromString(protoDataString)) {
            ythrow yexception() << "Cannot deserialize TUrlsParameter";
        }

        const google::protobuf::RepeatedPtrField<TProtoStringType>& urls = urlsParameter.GetUrls();  

        TVector<TString> vUrls(
                std::make_move_iterator(urls.begin()), 
                std::make_move_iterator(urls.end())
        );
        return vUrls;
    }

    bool ProcessUrls(
        const TStringBuf& param,
        const std::function<TVector<TString> (const TString&)> parseFunction,
        TDocidsFilter::TDocIds& docids,
        const TRequestParams& rp,
        const IRemapperUrlDocId& remapper,
        const TCgiParameters& cgiParams
    ) {
        bool result = false;
        
        for (size_t i = 0; i < cgiParams.NumOfValues(param); ++i) {
            const TString& paramValue = cgiParams.Get(param, i);
            if (!!paramValue) {
                result = true;

                TVector<TString> vUrls = parseFunction(paramValue);

                const TVector<ui64>& kps = rp.GetRequestTree()->Root->GetKeyPrefix();
                if (kps.size()) {
                    for (TVector<ui64>::const_iterator ikps = kps.begin(), ekps = kps.end(); ikps != ekps; ++ikps)
                        AddDocs(docids, remapper, *ikps, vUrls);
                }
                else {
                    AddDocs(docids, remapper, 0, vUrls);
                }
            }
        }

        return result;
    }

    bool ProcessUrls(
            const TStringBuf& plainStringParam,
            const TStringBuf& protoParam,
            TDocidsFilter::TDocIds& docids,
            const TRequestParams& rp,
            const IRemapperUrlDocId& remapper,
            const TCgiParameters& cgiParams
    ) {
        bool result = ProcessUrls(plainStringParam, ParseUrlsFromPlainString, docids, rp, remapper, cgiParams);
        result |= ProcessUrls(protoParam, ParseUrlsFromBase64ProtoString, docids, rp, remapper, cgiParams);
        return result;
    }
}

TRTYDocProcessor::TRTYDocProcessor(const TRequestParams& rp, const IRemapperUrlDocId& remapper, const IArchiveData& AD, const IFastArchive* FA, const TCgiParameters& cgiParams) {
    TVector<TAtomicSharedPtr<IRTYDocProcessor>> processors;

    const TString& facets = cgiParams.Get("facets");
    if (!!facets) {
        TFacetBuilder::FacetMode mode = TFacetBuilder::DetermineMode(facets);
        TVector<TString> allFacetNames;
        TVector<TString> ingoredFacetsNames;
        TString propertyPrefix;
        bool enabled = true;
        if (mode == TFacetBuilder::SINGLE) {
            StringSplitter(facets).SplitByString(LocalDelimiter).SkipEmpty().Collect(&allFacetNames);
        } else {
            StringSplitter(cgiParams.Get("nofacet")).SplitByString(LocalDelimiter).SkipEmpty().Collect(&ingoredFacetsNames);
            StringSplitter(cgiParams.Get("forcefacet")).SplitByString(LocalDelimiter).SkipEmpty().Collect(&allFacetNames);
            propertyPrefix = cgiParams.Get("facetprefix");
            enabled = !!propertyPrefix;
        }
        for (const TString& name : allFacetNames) {
            const TString& strippedName = TFacetBuilder::StripFacetOptions(name);
            if (FA && FA->HasProperty(strippedName))
                FastFacetsNames.push_back(name);
            else
                FacetsNames.push_back(name);
        }
        if (enabled)
            processors.push_back(new TFacetBuilder(AD, FA, mode, FacetsNames, FastFacetsNames, ingoredFacetsNames, propertyPrefix));
    }
    const TString& gafacets = cgiParams.Get("gafacets");
    if (!!gafacets) {
        StringSplitter(gafacets).SplitByString(LocalDelimiter).SkipEmpty().Collect(&GroupAttrFacetsNames);
        processors.push_back(new TGroupAttrFacetBuilder(GroupAttrFacetsNames, remapper.GetDocumentsCount()));
    }
    for (size_t i = 0; i < cgiParams.NumOfValues("efacets"); ++i) {
        const TString& efacet = cgiParams.Get("efacets", i);
        if (!!efacet) {
            ExtendedFacetExpressions.push_back(efacet);
        }
    }
    if (!!ExtendedFacetExpressions) {
        processors.push_back(new TExtendedFacetBuilder(ExtendedFacetExpressions));
    }

    const TString& sums = cgiParams.Get("sums");
    if (!!sums) {
        StringSplitter(sums).SplitByString(LocalDelimiter).SkipEmpty().Collect(&SumsNames);
        processors.push_back(new TSumsBuilder(SumsNames));
    }
    if (IsTrue(cgiParams.Get("rty_hits_detail"))) {
        processors.push_back(new THitsBuilderDetail());
    } else if (IsTrue(cgiParams.Get("rty_hits_count"))) {
        processors.push_back(new THitsBuilder());
    }
    const TString& borders = cgiParams.Get("borders");
    if (!!borders) {
        StringSplitter(borders).SplitByString(LocalDelimiter).SkipEmpty().Collect(&BordersNames);
        processors.push_back(new TBordersBuilder(BordersNames));
    }

    TDocidsFilter::TDocIds acceptedDocids;
    TDocidsFilter::TDocIds bannedDocids;
    bool accepted = ProcessUrls("au", "auproto", acceptedDocids, rp, remapper, cgiParams);
    bool banned = ProcessUrls("bu", "buproto", bannedDocids, rp, remapper, cgiParams);
    if (accepted) {
        if (banned)
            for (const auto ban : bannedDocids)
                acceptedDocids.erase(ban);
        processors.push_back(new TAccessedDocidsFilter(acceptedDocids));
    } else if (banned)
        processors.push_back(new TBanedDocidsFilter(bannedDocids));

    TAtomicSharedPtr<TRegExpDocidsFilter> regexpFilter = new TRegExpDocidsFilter(AD);
    bool ignoreCase = IsTrue(cgiParams.Get("regexp_ignore_case"));

    for (const auto& val: cgiParams.Range("regexp")) {
        TVector<TStringBuf> args = StringSplitter(val).Split('~').SkipEmpty();
        if (args.size() != 2 || args[0].empty() || args[1].empty()){
            continue;
        }
        bool inverseMatching = (args[0][args[0].size() - 1] == '!');
        const TStringBuf& regexp = args[1];
        const TStringBuf& prop = args[0].substr(0, args[0].size() - 1);

        regexpFilter->AddFilter(ToString(regexp), ToString(prop), inverseMatching, ignoreCase);
    }
    if (!regexpFilter->Empty()){
        processors.push_back(regexpFilter);
    }

    const bool facetsAfterFilterBorder = IsTrue(cgiParams.Get("facetsfb")); // false by defaull
    Add(processors, !facetsAfterFilterBorder);
}
