#include "config_tab_worker.h"
#include "relev_utils.h"
#include <library/cpp/logger/global/global.h>
#include <saas/rtyserver/components/cs/functions/cs_function_abstract.h>
#include <saas/rtyserver/components/qs/functions/qs_function_abstract.h>
#include <library/cpp/json/json_reader.h>
#include <util/folder/path.h>

namespace NRTYDeploy {
    using namespace NDMInterface;
    class TConfigTabWorkerRelev : public TConfigTabWorker {
    public:
        void FillTabImpl(TConfigTab& tab, IVersionedStorage& storage, const TProjectConfig::TComponent* component, const NDMInterface::TProjectConfig& projectConfig) const override {
            tab.SetRequired(false);
            tab.SetType(TConfigTab::RTYSERVER_RELEV);
            tab.SetTabName("Relev");
            tab.SetFilePath("/configs/" + component->GetComponent().GetServiceName() + "/relev.conf");
            tab.SetEnabled(false);
            for (const auto& file : component->GetFiles().GetFile()) {
                if (TFsPath(file.GetPath()).GetName().StartsWith("relev.conf")) {
                    tab.SetFilePath(file.GetPath());
                    TString data;
                    if (!storage.GetValue(file.GetPath(), data, file.GetVersion()))
                        ythrow yexception() << "cannot get data from " << file.GetPath() << ", version " << file.GetVersion();
                    TStringInput si(data);
                    NJson::TJsonValue relevJson;
                    if (!NJson::ReadJsonTree(&si, &relevJson))
                        continue;
                    FillSimpleFactorSet(*tab.MutableRelevConfig()->AddFactorSets(), relevJson["static_factors"], TRelevConfig::TFactorSet::STATIC, "Static");
                    FillSimpleFactorSet(*tab.MutableRelevConfig()->AddFactorSets(), relevJson["dynamic_factors"], TRelevConfig::TFactorSet::DYNAMIC, "Dynamic");
                    FillSimpleFactorSet(*tab.MutableRelevConfig()->AddFactorSets(), relevJson["user_factors"], TRelevConfig::TFactorSet::USER, "User");
                    FillZoneFactorSet(*tab.MutableRelevConfig()->AddFactorSets(), relevJson["zone_factors"]);
                    TSet<TString> functions;
                    Singleton<ICSFunction::TFactory>()->GetKeys(functions);
                    for (const TString& f : functions)
                        FillSimpleFactorSet(*tab.MutableRelevConfig()->AddFactorSets(), relevJson["CS_" + f], TRelevConfig::TFactorSet::STATIC, "CS_" + f);
                    functions.clear();
                    Singleton<IQSFunction::TFactory>()->GetKeys(functions);
                    for (const TString& f : functions)
                        FillSimpleFactorSet(*tab.MutableRelevConfig()->AddFactorSets(), relevJson["QS_" + f], TRelevConfig::TFactorSet::STATIC, "QS_" + f);
                    for (const auto& f : relevJson["formulas"].GetMap())
                        FillFormula(*tab.MutableRelevConfig()->AddFormulas(), storage, f.first, f.second, component, projectConfig);
                    tab.SetEnabled(true);
                    break;
                }
            }
        }

        void SetChangedConfigImpl(const TConfigTab& tab, IVersionedStorage& storage) override {
            if (!tab.HasRelevConfig())
                ythrow yexception() << "there is no relev config";
            NJson::TJsonValue result;
            SetChangedConfigFactors(result, tab.GetRelevConfig());
            SetChangedConfigFormula(result, tab.GetRelevConfig());
            if (!storage.SetValue(tab.GetFilePath(), result.GetStringRobust()))
                ythrow yexception() << "cannot write " << tab.GetFilePath();
        }

        static TFactory::TRegistrator<TConfigTabWorkerRelev> Registrator;

    private:
        static void SetChangedConfigFactorsCommon(NJson::TJsonValue& result, const TRelevConfig::TFactorSet& factors) {
            for (const auto& f : factors.GetFactors())
                result.InsertValue(f.GetName(), f.GetIndex());
        }

        static void SetChangedConfigFactorsStatic(NJson::TJsonValue& result, const TRelevConfig::TFactorSet& factors) {
            for (const auto& f : factors.GetFactors()) {
                NJson::TJsonValue& fJson = result.InsertValue(f.GetName(), NJson::JSON_MAP);
                fJson.InsertValue("index", f.GetIndex());
                fJson.InsertValue("width", f.GetWidth());
                if (f.GetHasDefaultValue())
                    fJson.InsertValue("default", f.GetDefaultValue());
            }
        }

        static void SetChangedConfigFactorsZone(NJson::TJsonValue& result, const TRelevConfig::TFactorSet& factors) {
            for (const auto& f : factors.GetFactors()) {
                const auto& alg = f.GetZoneFactorAlgorithm();
                const auto& fc = f.GetZoneFactorFormClass();
                const TString& algStr = alg.GetDescriptor()->FindEnumTypeByName("TValue")->FindValueByNumber(alg.GetValue())->name();
                const TString& fcStr = fc.GetDescriptor()->FindEnumTypeByName("TValue")->FindValueByNumber(fc.GetValue())->name();
                result.InsertValue("_" + algStr + "_" + fcStr + "_" + f.GetName(), f.GetIndex());
            }
        }

        static void SetChangedConfigFactors(NJson::TJsonValue& root, const TRelevConfig& relevConfig) {
            for (const auto& factorSet : relevConfig.GetFactorSets()) {
                if (!factorSet.FactorsSize())
                    continue;
                switch (factorSet.GetType()) {
                case TRelevConfig::TFactorSet::STATIC:
                    if (factorSet.GetName() == "Static")
                        SetChangedConfigFactorsStatic(root["static_factors"], factorSet);
                    else
                        SetChangedConfigFactorsStatic(root[factorSet.GetName()], factorSet);
                    break;
                case TRelevConfig::TFactorSet::ZONE:
                    SetChangedConfigFactorsZone(root["zone_factors"], factorSet);
                    break;
                case TRelevConfig::TFactorSet::DYNAMIC:
                    SetChangedConfigFactorsCommon(root["dynamic_factors"], factorSet);
                    break;
                case TRelevConfig::TFactorSet::USER:
                    SetChangedConfigFactorsCommon(root["user_factors"], factorSet);
                    break;
                default:
                    ythrow yexception() << "unknown factor type " << factorSet.GetDescriptor()->FindEnumTypeByName("TType")->FindValueByNumber(factorSet.GetType())->name();
                }
            }
        }

        static void SetChangedConfigFormula(NJson::TJsonValue& root, const TRelevConfig& relevConfig) {
            bool wasDefault = 0;
            for (const auto& formula : relevConfig.GetFormulas()) {
                if (!formula.HasMatrixNet() && !formula.HasPolynom())
                    ythrow yexception() << "formula " << formula.GetId() << " is empty";
                wasDefault |= formula.GetId() == "default";
                if (formula.HasMatrixNet())
                    root["formulas"][formula.GetId()].InsertValue("matrixnet", "${BIN_DIRECTORY}/configs/" + TFsPath(formula.GetMatrixNet().GetFile().GetPath()).GetName());
                if (formula.HasPolynom())
                    root["formulas"][formula.GetId()].InsertValue("polynom", EncodePolynom(formula.GetPolynom()));
            }
            if (!wasDefault)
                ythrow yexception() << "there is not default formula";
        }

        static void FillFormula(TRelevConfig::TFormula& result, IVersionedStorage& storage, const TString& name, const NJson::TJsonValue& root,
            const TProjectConfig::TComponent* component, const NDMInterface::TProjectConfig& projectConfig)
        {
            result.SetId(name);
            if (root.Has("polynom"))
                DecodePolynom(*result.MutablePolynom(), root["polynom"].GetStringRobust());
            if (root.Has("matrixnet")) {
                TString mnName = TFsPath(root["matrixnet"].GetStringRobust()).GetName();
                const TProjectConfig::TFiles::TFile* mnFile = nullptr;
                for (const auto& f : component->GetFiles().GetFile()) {
                    if (TFsPath(f.GetPath()).GetName() == mnName) {
                        mnFile = &f;
                        break;
                    }
                }
                if (!mnFile) {
                    for (const auto& f : projectConfig.GetCommonFiles().GetFile()) {
                        if (TFsPath(f.GetPath()).GetName() == mnName) {
                            mnFile = &f;
                            break;
                        }
                    }
                }
                if (!mnFile)
                    ythrow yexception() << "unknowm matrixnet: " << mnName;
                FillMatrixNetMetadata(*result.MutableMatrixNet(), storage, mnFile->GetPath(), mnFile->GetVersion());
            }
        }

        static void FillZoneFactorSet(TRelevConfig::TFactorSet& result, const NJson::TJsonValue& root) {
            result.SetName("Zone");
            result.SetType(TRelevConfig::TFactorSet::ZONE);
            const auto algDescr = TRelevConfig::TFactor::TZoneFactorAlgorithm::descriptor();
            const auto fcDescr = TRelevConfig::TFactor::TZoneFactorFormClass::descriptor();
            const auto algValues = algDescr->FindEnumTypeByName("TValue");
            const auto fcValues = fcDescr->FindEnumTypeByName("TValue");
            for (int i = 0; i < algValues->value_count(); ++i)
                *result.AddAlgorithmAllowedValues() = algValues->value(i)->name();
            for (int i = 0; i < fcValues->value_count(); ++i)
                *result.AddFormClassAllowedValues() = fcValues->value(i)->name();

            for (const auto& f : root.GetMap()) {
                TRelevConfig::TFactor& factor = *result.AddFactors();
                TVector<TString> s = SplitString(f.first, "_", 3);
                if (s.size() < 3)
                    ythrow yexception() << "invalid zone factor name " << f.first;
                const auto alg = algDescr->FindEnumValueByName(s[0]);
                if (!alg)
                    ythrow yexception() << "unknown zone factor algorithm " << s[0];
                factor.MutableZoneFactorAlgorithm()->SetValue((TRelevConfig::TFactor::TZoneFactorAlgorithm::TValue)alg->number());
                for (int i = 0; i < algValues->value_count(); ++i)
                    *factor.MutableZoneFactorAlgorithm()->AddAllowedValues() = algValues->value(i)->name();

                const auto fc = fcDescr->FindEnumValueByName(s[1]);
                if (!fc)
                    ythrow yexception() << "unknown zone factor form class " << s[1];
                factor.MutableZoneFactorFormClass()->SetValue((TRelevConfig::TFactor::TZoneFactorFormClass::TValue)fc->number());
                for (int i = 0; i < fcValues->value_count(); ++i)
                    *factor.MutableZoneFactorFormClass()->AddAllowedValues() = fcValues->value(i)->name();

                factor.SetName(s[2]);
                factor.SetIndex(f.second.GetUIntegerRobust());
            }
        }

        static void FillSimpleFactorSet(TRelevConfig::TFactorSet& result,
            const NJson::TJsonValue& root, TRelevConfig::TFactorSet::TType type,
            const TString& name)
        {
            result.SetName(name);
            result.SetType(type);
            for (const auto& f : root.GetMap()) {
                TRelevConfig::TFactor& factor = *result.AddFactors();
                factor.SetName(f.first);
                if (f.second.IsUInteger())
                    factor.SetIndex(f.second.GetUIntegerRobust());
                else {
                    factor.SetIndex(f.second["index"].GetUIntegerRobust());
                    if (f.second.Has("width"))
                        factor.SetWidth(f.second["width"].GetUIntegerRobust());
                    if (f.second.Has("default_value"))
                        factor.SetDefaultValue(f.second["default_value"].GetDoubleRobust());
                    factor.SetHasDefaultValue(true);
                }
            }
        }
    };

    TConfigTabWorkerRelev::TFactory::TRegistrator<TConfigTabWorkerRelev> TConfigTabWorkerRelev::Registrator(TConfigTab::RTYSERVER_RELEV);
}
