#include "grouping.h"

#include <library/cpp/logger/global/global.h>

namespace NPrivate {

    TGroupReporter::TGrouping::TGrouping()
        : OnlyParams(false)
    {}

    TGroupReporter::TGrouping::TGrouping(const TString& str) {
        size_t begin = str.find('(');
        Filter = str.substr(0, begin);
        if (OnlyParams = Filter.back() == '@')
            Filter.pop_back();
        if (Filter[0] == '$' && Filter.back() == '$')
            Name = Filter.substr(1, Filter.size() - 2) + "s";
        else
            Name = Filter + "s";
        if (begin != TString::npos) {
            size_t end = str.find(')', begin);
            if (end == TString::npos)
                ythrow yexception() << "invalid grouping " << str;
            for (const auto& p : SplitString(str.substr(begin + 1, end - begin - 1), ",")) {
                TGroupingCommonParam::TPtr param(TGroupingCommonParam::Create(p));
                if (param) {
                    CommonParams[param->GetName()] = param;
                } else {
                    DEBUG_LOG << "Unknown grouping param: " << p << Endl;
                }
            }
        }
        begin = str.find('[');
        if (begin != TString::npos) {
            size_t end = str.find(']', begin);
            if (end == TString::npos)
                ythrow yexception() << "invalid grouping " << str;
            TVector<TString> params = SplitString(str.substr(begin + 1, end - begin - 1), ",");
            Sort.insert(Sort.end(), params.begin(), params.end());
        }
    }

    bool TGroupReporter::Report(NJson::TJsonValue& result, const NJson::TJsonValue& richedData) const {
        return Report(result, richedData, Groupings.begin());
    }

    bool TGroupReporter::Report(NJson::TJsonValue& result, const NJson::TJsonValue& richedData, TGroupings::const_iterator group) const {
        bool ret = false;
        for (const auto& val : richedData.GetMap()) {
            NJson::TJsonValue data;
            NJson::TJsonValue nextResult;
            const NJson::TJsonValue* inserted(nullptr);
            TGroupings::const_iterator nextGroup = group + 1;
            if (nextGroup + 1 != Groupings.end()) {
                if (Report(nextResult, val.second, nextGroup)) {
                    inserted = &nextResult;
                    if (!nextGroup->OnlyParams)
                        data.InsertValue(nextGroup->Name, nextResult);
                }
            } else {
                inserted = &val.second;
                if (!nextGroup->OnlyParams)
                    NPrivate::TSort::SortJson(data.InsertValue(nextGroup->Name, val.second), nextGroup->Sort);
            }

            if (inserted) {
                for (const auto& param : group->CommonParams) {
                    NJson::TJsonValue paramValue(NJson::JSON_NULL);
                    if (inserted->IsArray()) {
                        for (const auto& insertedVal : inserted->GetArray())
                            if (param.second->UpdateParamValueFromElement(paramValue, insertedVal))
                                break;
                    } else if (inserted->IsMap()) {
                        for (const auto& insertedVal : inserted->GetMap())
                            if (param.second->UpdateParamValueFromElement(paramValue, insertedVal.second))
                                break;
                    }
                    param.second->InsertParamValueToGroup(data, paramValue);
                }
                if (FlatReport) {
                    data.InsertValue("id", val.first);
                    result.AppendValue(data);
                }
                else
                    result.InsertValue(val.first, data);
            }
            ret |= !!inserted;
        }
        NPrivate::TSort::SortJson(result, group->Sort);
        return ret;
    }

    void TGroupReporter::UpdateFieldsFilter(TSet<TString>& filter) const {
        for (size_t i = 0; i < Groupings.size() - 1; ++i)
            filter.insert(Groupings[i].Filter);
        for (const auto& param : Groupings.back().CommonParams)
            for (const auto& parPath : param.second->GetParams())
                filter.insert(parPath);
    }

    TVector<TString> TGroupReporter::GetGroupings() const {
        TVector<TString> result;
        for (size_t i = 0; i < Groupings.size() - 1; ++i)
            result.push_back(Groupings[i].Filter);
        return result;
    }

    TGroupReporter::TGroupReporter(const TCgiParameters& cgi)
        : FlatReport(!cgi.Has("flat_report") || FromString<bool>(cgi.Get("flat_report")))
    {
        TVector<TString> groupings = SplitString(cgi.Get("groupings"), ";");
        groupings.push_back("slot");
        for (size_t i = 0; i < groupings.size(); ++i) {
            Groupings.push_back(TGrouping(groupings[i]));
            if (i > 0) {
                for (const auto& p : Groupings[i - 1].CommonParams) {
                    TGroupingCommonParam::TPtr& inserted = Groupings.back().CommonParams[p.first];
                    if (!inserted)
                        inserted = p.second;
                }
                Groupings.back().OnlyParams |= Groupings[i - 1].OnlyParams;
            }
        }
        Groupings.back().OnlyParams |= cgi.Has("report_slots") && !FromString<bool>(cgi.Get("report_slots"));
        for (const auto& val: cgi.Range("sort")) {
            const TVector<TString> sortV = SplitString(val, ",");
            Groupings.back().Sort.insert(Groupings.back().Sort.end(), sortV.begin(), sortV.end());
        }
    }

}
