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

#include <google/protobuf/messagext.h>
#include <kernel/multipart_archive/protos/archive.pb.h>
#include <saas/protos/rtyserver.pb.h>
#include <saas/util/app_exception.h>

using TLayerMetaSaver = TProtoFileGuard<NRTYArchive::TArchiveMeta>;

///////////////////////
// TRTYFullArchiveLayer

bool TRTYFullArchiveLayer::IsOpen() const {
    return !!Archive;
}

TRTYFullArchiveLayer::TRTYFullArchiveLayer(const TRTYServerConfig& config, const TLayerConfig& layerConf, const TString& directory, const TString& name, ui32 /*writeSpeedBytes*/, bool readOnly)
    : SerializeContext(name)
    , ParseContext(name)
    , Config(config)
    , LayerConf(layerConf)
    , Directory(directory)
    , Name(name)
    , ReadOnly(readOnly)
{
}

void TRTYFullArchiveLayer::Index(const NRTYServer::TParsedDoc& document, const ui32 docId) {
    try {
        TBufferOutput out;
        ::google::protobuf::io::TProtoSerializer::Save(&out, document);
        Index(TBlob::NoCopy(out.Buffer().data(), out.Buffer().size()), docId);
    } catch (...) {
        FAIL_LOG("Can't serialize doc: %s", CurrentExceptionMessage().data());
    }
}

bool TRTYFullArchiveLayer::ReadParsedDoc(NRTYServer::TParsedDoc& pd, ui32 docId, ui32* size) const {
    CHECK_WITH_LOG(!!Archive);
    TBlob doc = Archive->GetDocument(docId);
    if (doc.IsNull())
        return false;
    try {
        DeserializeParsedDoc(pd, doc);
    } catch (...) {
        ERROR_LOG << "Log file " << Directory << "/" << FULL_ARC_FILE_NAME_PREFIX << " is corrupted. Can't read record: " << CurrentExceptionMessage() << Endl;
        ythrow NUtil::TFileFormatError() << "Corrupted full archive for " << Directory;
        return false;
    }
    if (size)
        *size = doc.Size();
    return true;
}

TBlob TRTYFullArchiveLayer::ReadRawDoc(ui32 docId) const {
    CHECK_WITH_LOG(!!Archive);
    return Archive->GetDocument(docId);
}

namespace {
    static NRTYArchive::TArchiveMeta PrepareMeta(const NRTYArchive::TMultipartConfig& config) {
        NRTYArchive::TArchiveMeta meta;
        meta.SetPartSizeLimit(config.PartSizeLimit);
        meta.SetPopulationRate(config.PopulationRate);
        meta.SetPartSizeDeviation(config.PartSizeDeviation);
        return meta;
    }
}

void TRTYFullArchiveLayer::RepairByData(const NRTYArchive::TMultipartConfig& config) {
    INFO_LOG << "RepairByData " << Directory << " layer " << Name << "..." << Endl;
    const TFsPath archiveName = Directory / GetArchiveName(Name);
    TArchiveOwner::Repair(archiveName, config);
    AssertCorrectIndex(TArchiveOwner::Check(archiveName), "%s verification failed for %s", Y_FUNC_SIGNATURE, Directory.data());
    INFO_LOG << "RepairByData " << Directory << " layer " << Name << "...OK" << Endl;

    TLayerMetaSaver saver(archiveName.GetPath() + ".meta");
    NRTYArchive::TArchiveMeta meta = PrepareMeta(LayerConf);
    if (meta.DebugString() != saver.GetProto().DebugString()) {
        TArchiveOwner::TPtr archive = TArchiveOwner::Create(archiveName, config);
        archive->OptimizeParts(config.CreateOptimizationOptions());
    }

    (*saver).CopyFrom(meta);
}

void TRTYFullArchiveLayer::DeserializeParsedDoc(NRTYServer::TParsedDoc& pd, TBlob doc) const {
    try {
        TMemoryInput input(doc.AsCharPtr(), doc.Size());
        ::google::protobuf::io::TProtoSerializer::Load(&input, pd);
    } catch (...) {
        ythrow NUtil::TFileFormatError() << "incorrect proto usage: " << CurrentExceptionMessage().data() << "," << Directory.data();
    }
}

ui64 TRTYFullArchiveLayer::GetLockedMemorySize() const {
    const auto& readDataAccessType = LayerConf.ReadContextDataAccessType;
    if (readDataAccessType != NRTYArchive::IDataAccessor::MEMORY_LOCKED_MAP &&
        readDataAccessType != NRTYArchive::IDataAccessor::MEMORY &&
        readDataAccessType != NRTYArchive::IDataAccessor::MEMORY_FROM_FILE)
    {
        return 0;
    }
    const TArchiveOwner::TPtr localArchivePtr = Archive;
    if (!localArchivePtr) {
        return 0;
    }
    return localArchivePtr->GetSizeInBytes();
}

TParsedDocument::TPtr TRTYFullArchiveLayer::DeserializeParsedDocument(const NRTYServer::TParsedDoc& pd, NRTYServer::TMessage::TMessageType command) const {
    TParsedDocument::TPtr result(new TParsedDocument(Config));
    result->SignalRestore();
    VERIFY_WITH_LOG(result->FillFromProto(pd, command, ParseContext), "incorrect parsedDoc");
    return result;
}

void TRTYFullArchiveLayer::Open(ui32 docCount) {
    const TFsPath archiveName = Directory / GetArchiveName(Name);
    if (!TArchiveOwner::Exists(archiveName)) {
        TLayerMetaSaver saver(archiveName.GetPath() + ".meta");
        NRTYArchive::TArchiveMeta meta = PrepareMeta(LayerConf);
        (*saver).CopyFrom(meta);
    }

    Archive = TArchiveOwner::Create(Directory / GetArchiveName(Name), LayerConf, docCount, Config.IsReadOnly);
    DEBUG_LOG << "Archive opened for " << Directory << "." << Name << "/" << Archive->GetDocsCount(false) << "/" << Archive->GetDocsCount(true) << Endl;
}

void TRTYFullArchiveLayer::Close() {
    DEBUG_LOG << "Archive closing for " << Directory << "." << Name << "/" << Archive->GetDocsCount(false) << "/" << Archive->GetDocsCount(true) << Endl;
    Archive.Drop();
}

bool TRTYFullArchiveLayer::RemoveDocument(ui32 docid) {
    return !!Archive && Archive->RemoveDocument(docid);
}

void TRTYFullArchiveLayer::Remap(const TVector<ui32>& remapTable) {
    Archive->Remap(remapTable);
}

bool TRTYFullArchiveLayer::CheckLayerRight(const TString& dir, const TString& layerName, const NRTYArchive::TMultipartConfig& config) {
    const TFsPath archiveName = dir / GetArchiveName(layerName);
    const TLayerMetaSaver saver(archiveName.GetPath() + ".meta");
    NRTYArchive::TArchiveMeta meta = PrepareMeta(config);
    return TArchiveOwner::Check(archiveName) && saver.GetProto().DebugString() == meta.DebugString();
}

TFsPath TRTYFullArchiveLayer::GetArchiveName(const TString& layer) {
    return FULL_ARC_FILE_NAME_PREFIX + layer;
}

bool TRTYFullArchiveLayer::Merge(const TString& layer, const TRTYFullArchiveConfig& componentConfig, const NRTYServer::TMergeContext& context) {
    TRTYMerger::TContext mc = context.Context;

    NRTYArchive::TMultipartConfig layerConfig = componentConfig.GetLayer(layer);
    layerConfig.WriteSpeedBytes = mc.WriteOption.GetUnitsPerSecond();

    mc.MultipartMergerContext.Reset(new NRTYArchive::TMultipartMergerContext(layerConfig,
                                                                             componentConfig.GetDisablePartsOptimization()));
    mc.MultipartArcFile = GetArchiveName(layer).GetPath();

    TRTYMerger rtyMerger(context.RigidStopSignal, TRTYMerger::otMpArch);
    bool result = rtyMerger.MergeIndicies(mc);

    if (result) {
        for (auto&& dir : mc.Dests) {
            TLayerMetaSaver saver(dir + "/" + GetArchiveName(layer).GetPath() + ".meta");
            NRTYArchive::TArchiveMeta meta = PrepareMeta(layerConfig);
            (*saver).CopyFrom(meta);
        }
    }

    return result;
}

///////////////////////
// TFAKVLayer

void TFAKVLayer::Index(const TBlob& document, const ui32 docId) {
    VERIFY_WITH_LOG(!!Archive, "Indexation into not opened FullArchiveManager %s", Directory.data());
    Archive->PutDocument(document, docId);
}

void TFAKVLayer::Index(const TParsedDocument& document, const ui32 docId) {
    NRTYServer::TParsedDoc pd;
    document.MergeToProto(pd, SerializeContext);
    Index(pd, docId);
}

///////////////////////
// TFALightLayer
void TFALightLayer::Index(const TBlob& /*document*/, const ui32 /*docId*/) {
}

void TFALightLayer::IndexUnsafe(const TBlob& document, const ui32 docId) {
    VERIFY_WITH_LOG(!!Archive, "Indexation into not opened FullArchiveManager %s", Directory.data());
    Archive->PutDocument(document, docId);
}

void TFALightLayer::Index(const TParsedDocument& document, const ui32 docId) {
    const TRTYFullArchiveLightEntity* entity = document.GetComponentEntity<TRTYFullArchiveLightEntity>(this->Name);
    if (entity == nullptr) {
        return;
    }

    const auto& res = entity->GetData();
    if (res.IsNull()) {
        return;
    }
    IndexUnsafe(res, docId);
}
