#include "relev_utils.h"
#include <kernel/relevfml/relev_fml.h>
#include <kernel/matrixnet/mn_dynamic.h>
#include <util/string/cast.h>
#include <util/folder/path.h>
#include <util/string/vector.h>

namespace NRTYDeploy {
    void inline SetMnName(NDMInterface::TRelevConfig::TMatrixNet& result) {
        if (!result.GetFile().GetPath())
            return;
        result.SetName(TFsPath(result.GetFile().GetPath()).Basename() + " v." + ToString(result.GetFile().GetVersion()));
    }

    void FillMatrixNetMetadata(NDMInterface::TRelevConfig::TMatrixNet& result, IVersionedStorage& storage, const TString& path, i64 version) {
        if (version == -1) {
            if (!storage.GetVersion(path, version))
                ythrow yexception() << "there is no " << path;
            FillMatrixNetMetadata(result, storage, path, version);
            return;
        }
        TFsPath normPath(path);
        normPath.Fix();
        TFsPath metadataPath = normPath.Parent() / "matrixnet_metadata" / (normPath.GetName() + ToString(version));
        try {
            storage.GetValue(metadataPath, result);
        } catch (...) {
            TString data;
            if (!storage.GetValue(normPath, data, version))
                ythrow yexception() << "there is no " << path << " version " << version;
            TStringInput si(data);
            NMatrixnet::TMnSseDynamic mn;
            mn.Load(&si);
            TSet<ui32> factors;
            mn.UsedFactors(factors);
            for (const auto& f : factors)
                result.AddUsedFactors()->SetId(f);
            result.MutableFile()->SetPath(normPath.GetPath());
            result.MutableFile()->SetVersion(version);
            storage.SetValue(metadataPath, result, false);
        }
        SetMnName(result);
    }

    void DecodePolynom(NDMInterface::TRelevConfig::TPolynom& result, const TString& polynom) {
        SRelevanceFormula poly;
        Decode(&poly, polynom, Max<int>());
        TVector<TVector<ui32> > factors;
        TVector<float> weights;
        poly.GetFormula(&factors, &weights);
        for (ui32 i = 0; i < weights.size(); ++i) {
            NDMInterface::TRelevConfig::TPolynom::TMonom& monom = *result.AddMonoms();
            monom.SetWeight(weights[i]);
            for (const auto& f : factors[i])
                monom.AddFactor()->SetId(f);
        }
        TSet<ui32> usedFactors;
        poly.UsedFactors(usedFactors);
        for (const auto& f : usedFactors)
            result.AddUsedFactors()->SetId(f);
        result.SetEncoded(polynom);
    }

    TString EncodePolynom(const NDMInterface::TRelevConfig::TPolynom& polynom) {
        SRelevanceFormula poly;
        for (const auto& monom : polynom.GetMonoms()) {
            TVector<ui32> factors;
            factors.reserve(monom.FactorSize());
            for (const auto& f : monom.GetFactor())
                factors.push_back(f.GetId());
            poly.AddSlag(factors, monom.GetWeight());
        }
        return Encode(poly);
    }

    TString EncodePolynom(const TString& polynom, const TMap<TString, ui32>& namesToIndex) {
        TVector<TString> monoms(1);
        for (const char* c = polynom.data(); c && *c; ++c) {
            switch (*c) {
                case ' ':
                    continue;
                case '+':
                case '-':
                    monoms.push_back(TString());
                default:
                    monoms.back().append(*c);
            }
        }
        SRelevanceFormula poly;
        for (const auto& monom : monoms) {
            if (!monom)
                continue;
            TVector<TString> split = SplitString(monom, "*");
            float weight = FromString<float>(split.front());
            TVector<ui32> factors;
            for (TVector<TString>::const_iterator f = split.begin() + 1; f != split.end(); ++f) {
                if (f->StartsWith("factor[") && f->EndsWith(']')) {
                    factors.push_back(FromString<ui32>(f->substr(7, f->length() - 8)));
                } else {
                    auto i = namesToIndex.find(*f);
                    if (i == namesToIndex.end())
                        ythrow yexception() << "unknown factor " << *f;
                    factors.push_back(i->second);
                }
            }
            poly.AddSlag(factors, weight);
        }
        return Encode(poly);
    }

    TString DecodePolynom(const TString& encodedPolynom, const TMap<ui32, TString>& indexesToNames) {
        SRelevanceFormula poly;
        Decode(&poly, encodedPolynom, Max<int>());
        TVector<TVector<ui32> > factors;
        TVector<float> weights;
        poly.GetFormula(&factors, &weights);
        TString result;
        for (ui32 i = 0; i < weights.size(); ++i) {
            if (weights[i] >= 0 && !!result)
                result.append('+');
            result.append(ToString(weights[i]));
            for (const auto index : factors[i]) {
                auto iter = indexesToNames.find(index);
                result.append('*').append((iter == indexesToNames.end()) ? ("factor[" + ToString(index) + "]") : iter->second);
            }
        }
        return result;
    }

    void ListMatrixNetMetadata(TVector<NDMInterface::TRelevConfig::TMatrixNet>& result, IVersionedStorage& storage, const TString& path) {
        TFsPath metadataPath = TFsPath(path) / "matrixnet_metadata";
        TVector<TString> names;
        storage.GetNodes(metadataPath, names);
        result.reserve(names.size());
        for (const auto& name : names) {
            try {
                NDMInterface::TRelevConfig::TMatrixNet mn;
                storage.GetValue(metadataPath / name, mn);
                SetMnName(mn);
                result.push_back(mn);
            } catch (...){}
        }
    }
}
