#include "zones_description.h"
#include "read_write_makeup_manager.h"

#include <saas/rtyserver/config/config.h>

const int TZonesDescription::SerializeVersion = 1;

void TZonesDescription::Merge(NRTYMerger::IHeader* header) {
    TZonesDescription* src = dynamic_cast<TZonesDescription*>(header);
    VERIFY_WITH_LOG(src, "Incorrect situation");
    for (ui32 i = 0; i < src->GetZoneCount(); ++i) {
        Decode(src->GetZoneName(i));
    }
}

void TZonesDescription::SerializeForMerger(IOutputStream& os, ui32 docsCount) {
    os.Write(&TRTYMakeupManager::SerializeVersion, sizeof(TRTYMakeupManager::SerializeVersion));
    Serialize(os);
    os.Write(&docsCount, sizeof(docsCount));
}

void TZonesDescription::DeserializeForMerger(IInputStream& os) {
    ui32 version;
    os.Read(&version, sizeof(version));
    Deserialize(os);
    ui32 countDocs;
    os.Read(&countDocs, sizeof(countDocs));
}

bool TZonesDescription::CheckFactorsSequence() {
    if (!Factors)
        return true;
    bool startZeros = false;
    FactorsZonesCount = 0;
    for (ui32 i = 0; i < Zones.size(); ++i) {
        if (!Zones[i].GetCode()) {
            startZeros = true;
            FactorsZonesCount = i;
        } else if (startZeros && Factors->IsZoneUsed(Zones[i].GetName().data())) {
            return false;
        }

    }
    return true;
}

TZonesDescription::TZonesDescription(IInputStream& in, const TZonesDescription& baseDescription)
: Factors(baseDescription.Config.GetSearcherConfig().Factors.Get())
, Config(baseDescription.Config)
, Transaction(ITransaction::tpEqualPriorities) {
        FillDeniedZones();
        FillTitleZones();
        Deserialize(in);
}

TZonesDescription::TZonesDescription(IInputStream& in, const TRTYServerConfig& config)
: Factors(config.GetSearcherConfig().Factors.Get())
, Config(config)
, Transaction(ITransaction::tpEqualPriorities) {
    FillDeniedZones();
    FillTitleZones();
    Deserialize(in);
}

TZonesDescription::TZonesDescription(const TRTYServerConfig& config)
: Factors(config.GetSearcherConfig().Factors.Get())
, Config(config)
, Transaction(ITransaction::tpEqualPriorities) {
    FillDeniedZones();
    FillTitleZones();
    BuildByFactorsConfig();
}

ui32 TZonesDescription::GetFactorsZonesCount() const {
    return FactorsZonesCount;
}

bool TZonesDescription::GetFeatureIndex(const ui32 zoneNumber, const NZoneFactors::TZoneFactorType zoneFactor, const EFormClass matchLevel, ui32& featureIndex) const {
    featureIndex = Zones[zoneNumber].GetIndex(zoneFactor, matchLevel);
    return featureIndex != Max<ui32>();
}

const TString& TZonesDescription::GetZoneName(NZonesMakeup::TZoneNumber zoneNumber) const {
    TGuardIncompatibleAction gia(Transaction);
    return Zones[zoneNumber].GetName();
}

NZonesMakeup::TZoneMask TZonesDescription::GetZonesMask(NZonesMakeup::EZoneRole role) const {
    TGuardIncompatibleAction gia(Transaction);
    if (role == NZonesMakeup::EZoneRole::Title) {
        return GetTitleZoneMask();
    }
    return NZonesMakeup::TZoneMask();
}

NZonesMakeup::TZoneNumber TZonesDescription::GetZoneCount() const {
    TGuardIncompatibleAction gia(Transaction);
    return Zones.size();
}

NZonesMakeup::TZoneId TZonesDescription::GetZoneId(NZonesMakeup::TZoneNumber zoneNumber) const {
    CHECK_WITH_LOG(zoneNumber < Zones.size());
    return Zones[zoneNumber].GetCode();
}

NZonesMakeup::TZoneCode TZonesDescription::AddZoneUnsafe(const TString& name) {

    auto it = ZoneCodes.find(name);
    if (it == ZoneCodes.end()) {
        DEBUG_LOG << "New zone decoding: " << name << Endl;
        NZonesMakeup::TZoneCode zc = NZonesMakeup::TZoneCode(0, ZoneCodes.size());
        if ((!!Factors && Factors->IsZoneUsed(name.data())) || !ZoneIsPermittedToSnippet(name)) {
            ui32 usefulZones = 0;
            for (ui32 i = 0; i < Zones.size(); ++i) {
                if (Zones[i].GetCode())
                    usefulZones++;
            }
            zc.ZoneId = 1 << usefulZones;
        }
        it = ZoneCodes.emplace(name, zc).first;
        Zones.push_back(TZoneInfo(name, zc));
        if (!ZoneIsPermittedToSnippet(name))
            SnippetsDeniedMask |= zc.ZoneId;
        if (ZoneIsTitle(name))
            TitleZoneMask |= zc.ZoneId;

        DEBUG_LOG << "New zone decoded: " << name << ":" << it->second.ZoneId << "," << it->second.ZoneNumber << Endl;
    }
    NZonesMakeup::TZoneCode& result = it->second;
    return result;
}

NZonesMakeup::TZoneCode TZonesDescription::AddZone(const TString& name) {
    TGuardTransaction gt(Transaction);
    return AddZoneUnsafe(name);
}

NZonesMakeup::TZoneCode TZonesDescription::Decode(const TString& name) {
    TGuardIncompatibleAction gia(Transaction);
    const auto i = ZoneCodes.find(name);
    if (i == ZoneCodes.end()) {
        gia.Release();
        return AddZone(name);
    } else {
        return i->second;
    }
}

void TZonesDescription::Serialize(IOutputStream& out) {
    TGuardIncompatibleAction gia(Transaction);
    ui32 size = Zones.size();
    out.Write(&SerializeVersion, sizeof(SerializeVersion));
    out.Write(&size, sizeof(size));
    for (int i = 0; i < Zones.ysize(); i++) {
        ui32 sizeStroka = Zones[i].GetName().size();
        out.Write(&sizeStroka, sizeof(sizeStroka));
        out.Write(Zones[i].GetName().data(), sizeStroka);
    }
}

bool TZonesDescription::Deserialize(IInputStream& in) {
    TGuardTransaction gt(Transaction);
    Zones.clear();
    ZoneCodes.clear();
    ui32 size;
    char value[4096];
    int version = 1;
    in.Load(&version, sizeof(version));
    in.Load(&size, sizeof(size));
    for (ui32 i = 0; i < size; i++) {
        ui32 sizeStroka;
        CHECK_WITH_LOG(in.Load(&sizeStroka, sizeof(sizeStroka)) == sizeof(sizeStroka));
        CHECK_WITH_LOG(in.Load(value, sizeStroka) == sizeStroka);
        value[sizeStroka] = 0;
        AddZoneUnsafe(value);
    }
    return AddFactorsData();
}

void TZonesDescription::AddFactorInfo(const NRTYFactors::TFactor& factor) {
    NZonesMakeup::TZoneCode zc = Decode(factor.ZoneName);
    Zones[zc.ZoneNumber].AddInfo(factor.ZoneFactorType, factor.FormClass, factor.IndexGlobal);
}

void TZonesDescription::FillDeniedZones() {
    SnippetsDeniedMask = 0;
    DeniedZonesTemplates.clear();
    DeniedZones.clear();
    for (TVector<TString>::const_iterator i = Config.GetSearcherConfig().SnippetsDeniedZonesVector.begin(); i != Config.GetSearcherConfig().SnippetsDeniedZonesVector.end(); ++i) {
        if (i->EndsWith('*'))
            DeniedZonesTemplates.insert(TString(i->data(), i->size() - 1));
        else
            DeniedZones.insert(*i);
    }
}

void TZonesDescription::FillTitleZones() {
    TitleZoneMask = 0;
    for (const TString& zoneName : Config.GetSearcherConfig().TextMachine.TitleZonesSet) {
        TitleZones.insert(zoneName);
    }
}

bool TZonesDescription::ZoneIsPermittedToSnippet(const TString& name) const {
    if (DeniedZones.find(name) != DeniedZones.end())
        return false;
    TDeniedZones::const_iterator i(DeniedZonesTemplates.lower_bound(name));
    if (i == DeniedZonesTemplates.end())
        return true;
    return !name.StartsWith(*i);
}

bool TZonesDescription::ZoneIsTitle(const TString& name) const {
    return TitleZones.find(name) != TitleZones.end();
}

bool TZonesDescription::AddFactorsData() {
    if (!Factors)
        return true;
    for (int i = 0; i < Factors->GetZoneFactors().ysize(); i++) {
        AddFactorInfo(Factors->GetZoneFactors()[i]);
    }
    return CheckFactorsSequence();
}

void TZonesDescription::BuildByFactorsConfig() {
    VERIFY_WITH_LOG(!ZoneCodes.ysize(), "Incorrect BuildByFactorsConfig usage. must be first");
    AddFactorsData();
    FactorsZonesCount = Zones.size();
}

TZonesDescription::TZoneInfo::TZoneInfo(const TString& name, const NZonesMakeup::TZoneCode& zc)
    : Name(name)
    , ZC(zc)
{
    IndexByForm.resize(((ui32)NZoneFactors::zftNoZone + 1) * 3, -1);
}

void TZonesDescription::TZoneInfo::AddInfo(NZoneFactors::TZoneFactorType zft, EFormClass form, int index) {
    IndexByForm[(ui32)form + 3 * (ui32)zft] = index;
}

int TZonesDescription::TZoneInfo::GetIndex(NZoneFactors::TZoneFactorType zft, EFormClass form) const {
    return IndexByForm[(ui32)form + 3 * (ui32)zft];
}

const TString& TZonesDescription::TZoneInfo::GetName() const {
    return Name;
}

NZonesMakeup::TLengthZones TZonesDescription::DecodeLengths(const NZonesMakeup::TLengthZones& lengthZones, const NZonesMakeup::IZonesDescription& currentDescription, bool unsafe) {
    NZonesMakeup::TLengthZones result(lengthZones.get_allocator());
    CHECK_WITH_LOG(currentDescription.GetZoneCount() >= lengthZones.size());
    for (ui32 z = 0; z < lengthZones.size(); ++z) {
        const ui32 zNum = (unsafe ? AddZoneUnsafe(currentDescription.GetZoneName(z)).ZoneNumber : Decode(currentDescription.GetZoneName(z)).ZoneNumber);
        if (zNum >= result.size())
            result.resize(zNum + 1, 0);
        result[zNum] = lengthZones[z];
    }
    return result;
}

void TZonesDescription::DecodeSpans(NZonesMakeup::TSpansZones& spansZones, const NZonesMakeup::IZonesDescription& currentDescription, bool unsafe) {
    for (size_t i = 0; i < spansZones.size(); i += 3) {
        const ui32 z = spansZones[i];
        spansZones[i] = (unsafe ? AddZoneUnsafe(currentDescription.GetZoneName(z)).ZoneNumber : Decode(currentDescription.GetZoneName(z)).ZoneNumber);
    }
}
