#include "parsed_entity.h"
#include "component.h"

#include <saas/rtyserver/indexer_core/index_component_storage.h>
#include <saas/library/behaviour/behaviour.h>

#include <yweb/robot/kiwi/tuplelib/lib/object.h>
#include <robot/library/oxygen/indexer/processor/protos/config.pb.h>
#include <robot/library/oxygen/indexer/tuple_value/tuple_value.h>

TOXYParsedEntity::TOXYParsedEntity(TConstructParams& params)
    : TBaseGeneratorParsedEntity(params)
{
}

void TOXYParsedEntity::MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const {
    // This serializes nothing, but filters out the unused tuple from MutableIndexedDoc (yes, the "immutable" FULLARC record is changed)
    // TTuplesListsAndFilters "used tuples" list is applied here

    TBaseGeneratorParsedEntity::MergeToProto(pd, context);
    NRTYServer::TMessage::TDocument& document = *pd.MutableDocument();
    *document.MutableIndexedDoc() = *IndexedDoc;
    DoFilterTuples(*document.MutableIndexedDoc(), context);
}

bool TOXYParsedEntity::FillFromProto(const NRTYServer::TParsedDoc& pd, const NRTYServer::TDocParseContext& context) {
    return TBaseGeneratorParsedEntity::FillFromProto(pd, context);
}

void TOXYParsedEntity::DoFilterTuples(NRealTime::TIndexedDoc& doc, const NRTYServer::TDocSerializeContext& context) {
    if (!doc.HasKiwiObject())
        return;
    const TOXYIndexComponent* comp = dynamic_cast<const TOXYIndexComponent*>(TIndexComponentsStorage::Instance().GetComponent(OXY_COMPONENT_NAME));
    if (!comp)
        return;
    const TTuplesListsAndFilters::TTupleNameSet* filterContext = comp->GetFilterTuples(context.GetLayer());
    if (!filterContext)
        return;
    NKiwi::TKiwiObject source(doc.GetKiwiObject());
    NKiwi::TKiwiObject dest;
    for (auto i = source.GetIterator(); i.IsValid(); ++i)
        if (filterContext->contains(i.Current()->ParseInfo()->GetLabel()))
            dest.Add(i.Current(), i.Current()->SizeOfAligned());
    doc.SetKiwiObject(TString(dest.Data(), dest.Size()));
}

void TOXYParsedEntity::DoApplyPatch(const TParsedDocument& doc) {
    const TOXYParsedEntity* patch = doc.GetComponentEntity<TOXYParsedEntity>(OXY_COMPONENT_NAME);
    if (!patch) {
        ythrow yexception() << "cannot find Oxygen parsed entity";
    }

    const TString& patchUrl = patch->GetIndexedDoc()->GetUrl();
    if (!GetIndexedDoc()->GetUrl() && patchUrl) {
        MutableIndexedDoc()->SetUrl(patchUrl);
    }
    const TString& thisUrl = GetIndexedDoc()->GetUrl();
    if (patchUrl && patchUrl != thisUrl) {
        ythrow yexception() << "cannot patch Oxygen parsed entity: urls differ " << patchUrl.Quote() << " != " << thisUrl.Quote();
    }

    NOxygen::TObjectContext oc2Patch(MutableIndexedDoc()->GetKiwiObject());
    NOxygen::TObjectContext oc(patch->GetIndexedDoc()->GetKiwiObject());
    NOxygen::TTupleNameSet tupleNames = oc.GetTupleLabels();

    const auto& component = dynamic_cast<const TOXYIndexComponent&>(Component);
    const TTuplesListsAndFilters::TTupleNameSet& filterContext = component.GetFilterTuples();

    THashSet<TString> tuplesForInsert;
    for (auto&& i : tupleNames) {
        auto oc2PatchAttr = oc2Patch[i];
        auto ocAttr = oc[i];
        if ((!oc2PatchAttr || oc2PatchAttr->GetTag() < ocAttr->GetTag()) && filterContext.contains(i)) {
            if (!oc2PatchAttr) {
                DEBUG_LOG << "Tuple for update: " << i << ": NEW : " << ocAttr->GetTag() << Endl;
            } else {
                DEBUG_LOG << "Tuple for update: " << i << ":" << oc2PatchAttr->GetTag() << "/" << ocAttr->GetTag() << Endl;
            }
            tuplesForInsert.insert(i);
        }
    }

    if (tuplesForInsert.size()) {
        oc2Patch.Erase(tuplesForInsert);

        oc.Select(tuplesForInsert);
        oc2Patch.Join(oc);

        TString kiwiObject;
        for (const auto& attr : oc2Patch) {
            NOxygen::TTupleAttrValuePtr tupleAttr = NOxygen::ConvertToTupleValue(attr);
            tupleAttr->AppendAsTuple(kiwiObject);
        }
        MutableIndexedDoc()->SetKiwiObject(kiwiObject);
    }
}

// TIndexComponentParser
TOXYComponentParser::TOXYComponentParser(const NRTYServer::IIndexComponent& component, TVector<IOxyUpdater::TPtr> updaters)
    : OxygenComponent(dynamic_cast<const TOXYIndexComponent&>(component))
    , Updaters(updaters)
{
}

bool TOXYComponentParser::IsFastUpdate(TParsingContext& context) const {
    if (context.Command != NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT)
        return false;
    NOxygen::TObjectContext objectContext(context.Document.GetIndexedDoc().GetKiwiObject());
    NOxygen::TTuplesUsageInfo tuplesForUse;

    const TTuplesListsAndFilters::TTupleNameSet& filterContext = OxygenComponent.GetFilterTuples();
    ui32 countUseless = 0;
    for (auto&& i : objectContext.GetTupleLabels()) {
        if (!filterContext.contains(i)) {
            ++countUseless;
        }
    }

    NOxygen::TTupleNameSet handledTuples;
    for (auto&& i : Updaters) {
        const auto result = i->GetTuplesInfo().VerifyObject(objectContext);
        if (!result.Correct) {
            continue;
        }
        const EValidationStatus validationStatus = i->Validate(objectContext, handledTuples);
        switch(validationStatus) {
            case VS_SLOW:
                // Explicitely forbidden by validate
                return false;
            case VS_WRONG_DOCUMENT:
                ythrow yexception() << "Wrong document detected";
            case VS_OK:
                continue;
        }
    }
    return objectContext.GetTupleLabels().size() - countUseless == handledTuples.size();
}

void TOXYComponentParser::Parse(TParsingContext& context) const {
    if (!GetBehaviour(context.Command).IsContentMessage || context.Command == NRTYServer::TMessage::DELETE_DOCUMENT)
        return;
    if (!context.Result.IsRestore()) {
        if (!context.Document.HasIndexedDoc())
            throw yexception() << "Document does not have IndexedDoc";
        if (!context.Document.GetIndexedDoc().HasKiwiObject())
            throw yexception() << "Document does not have KiwiObject";
    }

    TBaseGeneratorEntityParser::Parse(context);

    if (context.Command == NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT && context.DocParseContext.GetLayer() == NRTYServer::NFullArchive::FullLayer) {
        if (!IsFastUpdate(context)) {
            RestoreIsNeeded(context.Result);
        } else {
            DEBUG_LOG << "Fast update activated" << Endl;
        }
    }

    THolder<NRealTime::TIndexedDoc> indexedDoc(new NRealTime::TIndexedDoc(context.Document.GetIndexedDoc()));
    bool isUpdate = context.Command == NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT || context.Result.GetIsUpdate();
    if (!isUpdate && !context.Result.IsRestore()) {
        OxygenComponent.CheckTuples(indexedDoc->GetKiwiObject());
    }

    auto entity = context.Result.GetComponentEntity<TOXYParsedEntity>(OXY_COMPONENT_NAME);
    CHECK_WITH_LOG(entity);
    entity->SetIndexedDoc(indexedDoc.Release(), false);
}
