#include "erf_writer.h"
#include "erf_disk.h"
#include "erf_mem.h"
#include <library/cpp/logger/global/global.h>
#include <util/generic/utility.h>

TNamedBlocksErf::TNamedBlocksErf(const TString& dir, const TRTYServerConfig& config,
                                 const IRTYStaticFactors* factors, const TString& name, bool rewriteAbility, const char* componentName)
    : DocsCount(0)
    , FileName(GetErfFileName(name))
    , Dir(dir)
    , Modification(false)
    , Working(false)
    , Factors(factors)
    , ReWriteAbility(rewriteAbility)
    , ComponentName(componentName)
    , Config(config)
{
    PositionsByDocId.resize(1024, Max<ui32>());
}

bool TNamedBlocksErf::Close(const TVector<ui32>* remapTable) {
    TWriteGuard g(Mutex);
    if (!Working)
        return true;
    VERIFY_WITH_LOG(Working, "Incorrect TNamedBlocksErf usage");
    VERIFY_WITH_LOG(!remapTable || DocsCount == remapTable->size(),
        "Incorrect TNamedBlocksErf usage: %u != %lu", DocsCount, remapTable->size());
    Working = false;
    if (remapTable) {
        TVector<ui32> newPositionsByDocId;
        newPositionsByDocId.resize(PositionsByDocId.size(), Max<ui32>());
        ui32 newDocsCount = 0;
        for (ui32 i = 0; i < DocsCount; ++i) {
            if ((*remapTable)[i] != REMAP_NOWHERE) {
                newPositionsByDocId[(*remapTable)[i]] = PositionsByDocId[i];
                ++newDocsCount;
            }
        }
        DocsCount = newDocsCount;
        PositionsByDocId = newPositionsByDocId;
    }
    ErfManager->Close();
    if (!!Dir && Modification) {
        Serialize();
        Modification = false;
    }
    return true;
}

bool TNamedBlocksErf::Open() {
    TWriteGuard g(Mutex);
    VERIFY_WITH_LOG(!Working, "Incorrect TNamedBlocksErf usage");
    Modification = false;
    if (Dir) {
        TRTYErfDiskManager::TCreationContext cc(TPathName{Dir}, FileName, Factors);
        if (!NFs::Exists(Dir + "/" + FileName)) {
            cc.BlockCount = 1024;
        }
        ErfManager.Reset(new TRTYErfDiskManager(cc, ComponentName));
    } else {
        ErfManager.Reset(new TRTYErfMemoryManager(1024, Factors, ComponentName));
    }
    ErfManager->Open();
    ErfWriter.Reset(new TLinkedErfWriter(ErfManager.Get()));
    PositionsByDocId.resize(1024, Max<ui32>());
    DocsCount = 0;
    if (Dir) {
        Deserialize();
    }
    Working = true;
    return true;
}

bool TNamedBlocksErf::Read(TFactorView& result, ui32 docId) const {
    TReadGuard g(Mutex);
    VERIFY_WITH_LOG(Working, "Incorrect TNamedBlocksErf::Get usage");
    VERIFY_WITH_LOG(PositionsByDocId.size() > docId, "Incorrect docid");
    VERIFY_WITH_LOG(PositionsByDocId[docId] != Max<ui32>(), "Incorrect position");

    // read with remap
    return ErfManager->Read(result, PositionsByDocId[docId]);
}

bool TNamedBlocksErf::ReadRaw(TBasicFactorStorage& erfBlock, ui32 docId) const {
    TReadGuard g(Mutex);
    VERIFY_WITH_LOG(Working, "Incorrect TNamedBlocksErf::Get usage");
    VERIFY_WITH_LOG(PositionsByDocId.size() > docId, "Incorrect docid");
    VERIFY_WITH_LOG(PositionsByDocId[docId] != Max<ui32>(), "Incorrect position");

    // load as is
    return ErfManager->ReadRaw(erfBlock, PositionsByDocId[docId]);
}

ui32 TNamedBlocksErf::Add(const TString& FValue, ui32 docId, const TBasicFactorStorage& data, bool replace) {
    TWriteGuard g(Mutex);
    VERIFY_WITH_LOG(Working, "Incorrect TNamedBlocksErf::Add usage");
    Modification = true;
    ui32 result;
    THashMap<TString, ui32>::const_iterator i = FValueToErfPos.find(FValue);
    if (i == FValueToErfPos.end()) {
        result = FValueToErfPos[FValue] = ErfWriter->Write(data);
        ErfWriter->Link(result);
    } else {
        result = i->second;
        if (replace)
            ErfManager->Write(data, result);
    };

    if (PositionsByDocId.size() <= docId)
        PositionsByDocId.resize(Max<ui32>(PositionsByDocId.size() * 2, docId + 1), Max<ui32>());
    ui32& pos = PositionsByDocId[docId];
    if (!ReWriteAbility) {
        if (replace && pos != Max<ui32>())
            return result;
        VERIFY_WITH_LOG(pos == Max<ui32>(), "Incorrect TNamedBlocksErf usage. Conflict with ReWritePolicy");
        ++DocsCount;
    } else if (pos != Max<ui32>()) {
        if (pos != result)
            ErfWriter->UnLink(pos);
    } else
        ++DocsCount;
    pos = result;
    return result;
}

void TNamedBlocksErf::Serialize() const {
    VERIFY_WITH_LOG(!!Dir, "Incorrect TNamedBlocksErf usage");
    VERIFY_WITH_LOG(!Working, "Incorrect TNamedBlocksErf usage");
    TFixedBufferFileOutput foInfo(Dir + "/" + "info." + FileName);
    NRTYService::TNamedErfBlocksInfo info;
    for (THashMap<TString, ui32>::const_iterator i = FValueToErfPos.begin(), e = FValueToErfPos.end(); i != e; ++i) {
        NRTYService::TNamedErfBlockAddress* address = info.AddAddress();
        address->SetFValue(i->first);
        address->SetAddress(i->second);
    }

    for (ui32 docid = 0; docid < DocsCount; ++docid) {
        VERIFY_WITH_LOG(PositionsByDocId[docid] != Max<ui32>(), "Incorrect remapping");

    }

    info.SetDocIdsSize(DocsCount);
    info.SetAddressByDocId((void*)&PositionsByDocId[0], DocsCount * sizeof(ui32));
    info.SerializeToArcadiaStream(&foInfo);
}

bool TNamedBlocksErf::Deserialize() {
    VERIFY_WITH_LOG(!Working, "Incorrect TNamedBlocksErf usage");
    VERIFY_WITH_LOG(!!Dir, "Incorrect TNamedBlocksErf usage");
    if (!NFs::Exists((Dir + "/" + "info." + FileName)))
        return false;
    TUnbufferedFileInput fiInfo(Dir + "/" + "info." + FileName);
    NRTYService::TNamedErfBlocksInfo info;
    VERIFY_WITH_LOG(info.ParseFromArcadiaStream(&fiInfo), "Incorrect %s file format", (Dir + "/" + "info." + FileName).data());
    for (ui32 block = 0; block < info.AddressSize(); ++block) {
        const NRTYService::TNamedErfBlockAddress& addr = info.GetAddress(block);
        FValueToErfPos[addr.GetFValue()] = addr.GetAddress();
    }
    DocsCount = info.GetDocIdsSize();
    PositionsByDocId.resize(DocsCount, Max<ui32>());
    memcpy(&PositionsByDocId[0], info.GetAddressByDocId().c_str(), DocsCount * sizeof(ui32));
    return true;
}

ui32 TNamedBlocksErf::GetDocsCount() const {
    TReadGuard g(Mutex);
    return DocsCount;
}

TString TNamedBlocksErf::GetFValue(ui32 docId) const {
    TWriteGuard g(Mutex);
    VERIFY_WITH_LOG(docId < PositionsByDocId.size(), "Incorrect GetFValue result");
    if (!FErfPosToValue.size()) {
        for (THashMap<TString, ui32>::const_iterator i = FValueToErfPos.begin(), e = FValueToErfPos.end(); i != e; ++i) {
            FErfPosToValue[i->second] = i->first;
        }
    }
    VERIFY_WITH_LOG(FErfPosToValue.find(PositionsByDocId[docId]) != FErfPosToValue.end(), "Incorrect GetFValue result");
    return FErfPosToValue[PositionsByDocId[docId]];
}

ui32 TNamedBlocksErf::Size() const {
    return GetDocsCount();
}

TString TNamedBlocksErf::GetErfFileName(const TString& blockName) {
    return "index." + blockName + ".erf";
}

ui32 TLinkedErfWriter::Write(const TBasicFactorStorage& data) {
    ui32 pos;
    if (!EmptyPositions.Dequeue(&pos)) {
        TWriteGuard g(Mutex);
        ErfManager->Resize();
        LinksCount.resize(ErfManager->Size());
        for (ui32 addPos = ErfManager->Size() / 2 + 1; addPos < ErfManager->Size(); ++addPos) {
            EmptyPositions.Enqueue(addPos);
        }
        pos = ErfManager->Size() / 2;
    }
    TReadGuard g(Mutex);
    ErfManager->Write(data, pos);
    return pos;
}
