#include "parsed_entity.h"
#include "config.h"
#include "globals.h"

namespace {
    template <class T>
    using TRepeatedStruct = google::protobuf::RepeatedPtrField<T>;

    template <class T>
    inline TString GetRepeatedFieldName(const T& element) {
        return element.GetName();
    }

    template <>
    inline TString GetRepeatedFieldName(const NRTYServer::TMessage::TTimestamp& element) {
        return ToString(element.GetStream());
    }

    template <class T>
    inline bool IsRepeatedFieldDeleted(const T& element) {
        return element.GetValue() == "__delete__";
    }

    template <>
    inline bool IsRepeatedFieldDeleted(const NRTYServer::TMessage::TTimestamp& /*element*/) {
        return false;
    }

    template <>
    inline bool IsRepeatedFieldDeleted(const NRTYServer::TMessage::TCSInfo& /*element*/) {
        return false;
    }

    template <>
    inline bool IsRepeatedFieldDeleted(const NRTYServer::TMessage::TQSInfo& /*element*/) {
        return false;
    }

    template <class T>
    void CollapseRepeatedField(TRepeatedStruct<T>* field, const TRepeatedStruct<T>& patch) {
        if (patch.empty()) {
            return;
        }

        TMap<TString, int> indices;
        for (int i = 0; i < field->size(); ++i) {
            indices[GetRepeatedFieldName(field->Get(i))] = i;
        }

        TRepeatedStruct<T> merged;
        for (auto&& i : indices) {
            const auto& element = field->Get(i.second);
            if (IsRepeatedFieldDeleted(element)) {
                continue;
            }

            merged.Add()->MergeFrom(element);
        }
        merged.Swap(field);
    }

    void MergeDocumentPatch(NRTYServer::TMessage::TDocument& document, const NRTYServer::TMessage::TDocument& patch) {
        document.MergeFrom(patch);
        CollapseRepeatedField(document.MutableDocumentProperties(), patch.GetDocumentProperties());
        CollapseRepeatedField(document.MutableSearchAttributes(), patch.GetSearchAttributes());
        CollapseRepeatedField(document.MutableGroupAttributes(), patch.GetGroupAttributes());
        CollapseRepeatedField(document.MutableCSInfo(), patch.GetCSInfo());
        CollapseRepeatedField(document.MutableQSInfo(), patch.GetQSInfo());
        CollapseRepeatedField(document.MutableFSProperties(), patch.GetFSProperties());
        CollapseRepeatedField(document.MutableTimestamps(), patch.GetTimestamps());
        CollapseRepeatedField(document.MutableAdditionalKeys(), patch.GetAdditionalKeys());
    }
}

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

void TRTYFullArchiveParsedEntity::MergeToProto(NRTYServer::TParsedDoc& pd, const NRTYServer::TDocSerializeContext& context) const {
    TBaseGeneratorParsedEntity::MergeToProto(pd, context);
    pd.MutableDocument()->CopyFrom(Document);
}

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

void TRTYFullArchiveParsedEntity::DoApplyPatch(const TParsedDocument& doc) {
    auto patch = doc.GetComponentEntity<TRTYFullArchiveParsedEntity>(FULL_ARCHIVE_COMPONENT_NAME);
    if (patch) {
        MergeDocumentPatch(Document, patch->GetDocument());
    }
}

void TRTYFullArchiveParsedEntity::SetDocument(const NRTYServer::TMessage::TDocument& document) {
    Document = document;
}

const NRTYServer::TMessage::TDocument& TRTYFullArchiveParsedEntity::GetDocument() const {
    return Document;
}

void TRTYFullArchiveComponentParser::Parse(TParsingContext& context) const {
    TBaseGeneratorEntityParser::ParseBody(context);
    if (context.Command == NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT) {
        TBaseGeneratorEntityParser::ParseUpdate(context);
        if (context.DocParseContext.GetLayer() != NRTYServer::NFullArchive::BaseLayer) {
            RestoreIsNeeded(context.Result);
        }
    }
    auto result = context.Result.GetComponentEntity<TRTYFullArchiveParsedEntity>(FULL_ARCHIVE_COMPONENT_NAME);
    if (result) {
        result->SetDocument(context.Document);
    }
}
