#include "repairing_normalizer.h"

#include <saas/rtyserver/components/fullarchive/disk_manager.h>
#include <saas/util/queue.h>

#include <robot/library/oxygen/indexer/processor/collection/collection.h>
#include <robot/library/oxygen/indexer/processor/keyinv/keyinv.h>
#include <robot/library/oxygen/indexer/processor/arcdir/arcdir.h>
#include <robot/library/oxygen/base/protobuf/pb_utils.h>
#include <robot/library/oxygen/indexer/idspace/docid.h>


namespace {


//TODO(yrum): these bindings should be merged into Oxygen
class IOxyProcessorDescriptor {
public:
    virtual ~IOxyProcessorDescriptor() {}

    virtual bool Enabled(const NOxygen::TOxygenOptions*) {
        return false;
    }

    virtual TVector<TString> GetOutputFiles(const NOxygen::TOxygenOptions*) {
        return TVector<TString>();
    }

    virtual NOxygen::TProcessorPtr CreateForNormalizer(const NOxygen::TOxygenOptions*, TFsPath, TAtomicSharedPtr<IThreadPool>) {
        ythrow yexception() << "not implemented" << Endl;
    }
public:
    virtual bool IsOutputExist(const TFsPath dir, const NOxygen::TOxygenOptions* opts) {
        for (auto fn : GetOutputFiles(opts)) {
            if (!NFs::Exists(dir / fn))
                return false;
        }
        return true;
    }

    virtual void ClearOutput(const TFsPath dir, const NOxygen::TOxygenOptions* opts) {
        for (auto fn : GetOutputFiles(opts)) {
             if (NFs::Exists(dir / fn))
                 NFs::Remove(dir / fn);
        }
    }
};


} // namespace anonymous

using TOxyProcessorDescriptorPtr = TAtomicSharedPtr<IOxyProcessorDescriptor>;

//TODO(yrum, 20190717): As for now, TRepairingNormalizer does nothing. Consider disabling.
class TRepairingNormalizer::TImpl final {
private:
    TVector<TOxyProcessorDescriptorPtr> List;

public:
    TImpl() {
    }

    const TVector<TOxyProcessorDescriptorPtr>& GetList() const {
        return List;
    }
};

TRepairingNormalizer::TRepairingNormalizer(const TRTYServerConfig& config)
    : NRTYServer::TOxygenNormalizer(config)
    , Impl(MakeHolder<TImpl>())
{
}

TRepairingNormalizer::~TRepairingNormalizer() {
}

const char* TRepairingNormalizer::Name() const {
    return "RepairingNormalizer";
}


bool TRepairingNormalizer::AllRight(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    const NOxygen::TOxygenOptions* oxygenOptions = GetOxygenOptions(context);
    AssertCorrectConfig(oxygenOptions, "OxygenOptions are required for TRepairingNormalizer");
    const TFsPath directory = context.Dir.PathName();

    bool ok = true;
    for (const auto& descr : Impl->GetList()) {
        if (descr->Enabled(oxygenOptions))
        ok = ok && descr->IsOutputExist(directory, oxygenOptions);
    }

    return ok;
}

void TRepairingNormalizer::Fix(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    const auto archive = context.Managers.GetManager<TDiskFAManager>(FULL_ARCHIVE_COMPONENT_NAME);
    const TFsPath directory = context.Dir.PathName();
    const NOxygen::TOxygenOptions* oxygenOptions = GetOxygenOptions(context);
    AssertCorrectConfig(oxygenOptions, "OxygenOptions are required for TRepairingNormalizer");
    AssertCorrectConfig(archive, "FullArchive is required for TRepairingNormalizer");
    AssertCorrectIndex(archive->IsOpened(), "FullArchive must be opened for TRepairingNormalizer");

    INFO_LOG << "Restoring optional OXY indexes in " << directory.Basename() << Endl;
    TAtomicSharedPtr<IThreadPool> taskpool = CreateRTYQueue(1, 0, NUtil::TSmartMtpQueue::TOptions::GetNoBallocOptions().SetThreadName("OxyRepairNorm"));

    TVector<NOxygen::TProcessorPtr> processors;
    for (const auto& descr : Impl->GetList()) {
        if (descr->Enabled(oxygenOptions) && !descr->IsOutputExist(directory, oxygenOptions))
            processors.push_back(descr->CreateForNormalizer(oxygenOptions, directory, taskpool));
    }

    NOxygen::TProcessorCollection collection(processors);
    collection.Start();

    ui32 docId = 0;
    NOxygen::TDocIdMap remap;
    for (auto iterator = archive->CreateIterator(); iterator->IsValid(); iterator->Next()) {
        const ui32 realDocId = iterator->GetDocId();
        if (archive->IsRemoved(realDocId)) {
            continue;
        }

        const NRTYServer::TParsedDoc& pd = iterator->GetParsedDoc();

        NOxygen::TObjectContext objectContext(pd.GetDocument().GetIndexedDoc().GetKiwiObject());
        NOxygen::TReturnObjectContext result = collection.Process(objectContext, docId).GetValue(TDuration::Max());
        AssertCorrectIndex(result.IsProcessedOk(), "Cannot restore OXY indexes: %s", NOxygen::WritePbText(result.GetErrors()).data());
        remap.SetMapValue(docId++, realDocId);
    }

    collection.Finish(&remap);
    INFO_LOG << "Restored optional OXY indexes in " << directory.Basename() << Endl;
}
