#include "group_attrs_normalizer.h"

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

#include <kernel/groupattrs/docsattrswriter.h>
#include <kernel/groupattrs/mutdocattrs.h>
#include <kernel/groupattrs/docsattrs.h>

namespace {
    const TString GrAttrFilename = "indexaa";
}

TGroupAttrsNormalizer::TGroupAttrsNormalizer(const TRTYServerConfig& config)
    : NRTYServer::INormalizer(config)
{}

const char* TGroupAttrsNormalizer::Name() const {
    return "GroupAttrs";
}

bool TGroupAttrsNormalizer::AllRight(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& /*indexFrq*/) const {
    if (!context.Index || !context.Index->GetGroupsAttrs() || context.Index->GetGroupsAttrs()->DocCount() == 0) {
        return true;
    }

    const NGroupingAttrs::TConfig& oldConfig = context.Index->GetGroupsAttrs()->Config();
    DEBUG_LOG << "Old config: " << oldConfig.ToString() << Endl;
    const NGroupingAttrs::TConfig& newConfig = *context.Config.GetCommonIndexers().GroupingConfig;
    DEBUG_LOG << "New config: " << newConfig.ToString() << Endl;

    TVector<bool> found(newConfig.AttrCount(), false);
    for (ui32 attrnum = 0; attrnum < oldConfig.AttrCount(); ++attrnum) {
        const bool unique = oldConfig.IsAttrUnique(attrnum);
        const TString& name = oldConfig.AttrName(attrnum);
        const NGroupingAttrs::TConfig::Type type = oldConfig.AttrType(attrnum);

        ui32 newAttrnum = newConfig.AttrNum(name.data());
        if (newAttrnum == NGroupingAttrs::TConfig::NotFound) {
            if (unique) {
                ERROR_LOG << "Unique attribute " << name << " is not found in the new config" << Endl;
                return false;
            }
            continue;
        }
        found[newAttrnum] = true;

        if (unique != newConfig.IsAttrUnique(newAttrnum)) {
            ERROR_LOG << "Unique attribute " << name << " is not unique in the new config" << Endl;
            return false;
        }

        const NGroupingAttrs::TConfig::Type newType = newConfig.AttrType(newAttrnum);
        if (unique && type != newType) {
            ERROR_LOG << "Unique attribute " << name << " has different types in the new config: "
                      << static_cast<ui32>(newType) << " != " << static_cast<ui32>(type) << Endl;
            return false;
        }
    }
    for (ui32 newAttrnum = 0; newAttrnum < found.size(); ++newAttrnum) {
        if (!found[newAttrnum] && newConfig.IsAttrUnique(newAttrnum)) {
            ERROR_LOG << "New unique attribute " << newConfig.AttrName(newAttrnum) << " is not found in the old config" << Endl;
            return false;
        }
    }
    return true;
}

void TGroupAttrsNormalizer::Fix(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& indexFrq) const {
    NGroupingAttrs::TConfig mergedConfig;
    MergeConfigs(context, mergedConfig);
    MergeData(context, indexFrq, mergedConfig);
    SwapFiles(context);
}

void TGroupAttrsNormalizer::MergeConfigs(const NRTYServer::TNormalizerContext& context, NGroupingAttrs::TConfig& mergedConfig) const {
    const NGroupingAttrs::TConfig& oldConfig = context.Index->GetGroupsAttrs()->Config();
    const NGroupingAttrs::TConfig& newConfig = *context.Config.GetCommonIndexers().GroupingConfig;
    TVector<bool> finded(newConfig.AttrCount(), false);
    for (ui32 attrnum = 0; attrnum < oldConfig.AttrCount(); ++attrnum) {
        ui32 newAttrnum = newConfig.AttrNum(oldConfig.AttrName(attrnum));
        if (newAttrnum == NGroupingAttrs::TConfig::NotFound) {
            mergedConfig.AddAttr(oldConfig.AttrName(attrnum), oldConfig.AttrType(attrnum), false, false);
            continue;
        }
        finded[newAttrnum] = true;
        bool unique = newConfig.IsAttrUnique(newAttrnum);
        mergedConfig.AddAttr(oldConfig.AttrName(attrnum), unique ? newConfig.AttrType(newAttrnum) : oldConfig.AttrType(attrnum), false, unique);
    }
    for (ui32 newAttrnum = 0; newAttrnum < finded.size(); ++newAttrnum) {
        if (!finded[newAttrnum] && newConfig.IsAttrUnique(newAttrnum))
            mergedConfig.AddAttr(newConfig.AttrName(newAttrnum), newConfig.AttrType(newAttrnum), false, true);
    }
}

namespace {
    TFsPath GetNewFile(const NRTYServer::TNormalizerContext& context) {
        return TFsPath(context.Dir.PathName()) / "new_indexaa";
    }
}

void TGroupAttrsNormalizer::MergeData(const NRTYServer::TNormalizerContext& context, const THolder<TFileMap>& indexFrq, NGroupingAttrs::TConfig& mergedConfig) const {
    ui32 docCount = indexFrq->Length() / sizeof(i16);
    const i16* deleted = (const i16*)indexFrq->Ptr();
    TFixedBufferFileOutput out(GetNewFile(context));
    NGroupingAttrs::TVersion version = context.Config.GetCommonIndexers().GroupingConfig->GetVersion();
    NGroupingAttrs::TDocsAttrsWriter writer(mergedConfig, version, NGroupingAttrs::TConfig::Search, context.Dir.PathName(), out, "");
    const NGroupingAttrs::TDocsAttrs* oldAttrs = context.Index->GetGroupsAttrs();
    TVector<ui32> oldAttrNums(mergedConfig.AttrCount());
    for (ui32 attrnum = 0; attrnum < mergedConfig.AttrCount(); ++attrnum)
        oldAttrNums[attrnum] = oldAttrs->Config().AttrNum(mergedConfig.AttrName(attrnum));
    for (ui32 docid = 0; docid < docCount; ++docid) {
        NGroupingAttrs::TMutableDocAttrs da(mergedConfig);
        if (deleted[docid] != -1) {
            for (ui32 attrnum = 0; attrnum < mergedConfig.AttrCount(); ++attrnum) {
                TCategSeries values;
                if (oldAttrNums[attrnum] != NGroupingAttrs::TConfig::NotFound)
                    oldAttrs->DocCategs(docid, oldAttrNums[attrnum], values);
                values.SortAndUnique();
                const TCateg* begin = values.Begin();
                while (begin != values.End() && *begin < 0)
                    ++begin;
                TCategSeries filtered;
                for (; begin != values.End(); ++begin) {
                    if (*begin > mergedConfig.AttrMaxValue(attrnum))
                        ythrow yexception() << "can not convert attriubute "
                        << mergedConfig.AttrName(attrnum) << " to type " << (int)mergedConfig.AttrType(attrnum) << " because it has value "
                        << *begin << " for docid " << docid << " in " << context.Dir.PathName();
                    filtered.AddCateg(*begin);
                }
                if (mergedConfig.IsAttrUnique(attrnum)) {
                    if (filtered.size() > 1)
                        ythrow yexception() << "can not convert attriubute "
                        << mergedConfig.AttrName(attrnum) << " to unique because it has "
                        << filtered.size() << " values for docid " << docid << " in " << context.Dir.PathName();
                    if (filtered.size() == 0)
                        filtered.AddCateg(0);
                }
                for (const TCateg* v = filtered.Begin(); v != filtered.End(); ++v)
                    da.SetAttr(attrnum, *v);
            }
        }
        if (da.IsEmpty())
            writer.WriteEmpty();
        else
            writer.Write(da);
    }
    writer.Close();
}

void TGroupAttrsNormalizer::SwapFiles(const NRTYServer::TNormalizerContext& context) const {
    NRTYServer::IIndexOwner::TGuardIndexModification g(context.Index);
    GetNewFile(context).RenameTo(TFsPath(context.Dir.PathName()) / "indexaa");
}
