#pragma once

#include "formatter.h"

#include <saas/searchproxy/report/abstract/reply_data.h>
#include <saas/library/report_builder/abstract.h>

#include <search/reqparam/reqparam.h>


class TReportConstructor {
    using TFullReportInfo = ISearchReplyData::TFullReportInfo;
    using TGroupingInfo = ISearchReplyData::TGroupingInfo;
    using TMainDocArchiveInfo = ISearchReplyData::TMainDocArchiveInfo;
    using TAttrsInfo = ISearchReplyData::TAttrsInfo;
    using TGroupInfo = ISearchReplyData::TGroupInfo;
    using TDocCategInfo = ISearchReplyData::TDocCategInfo;
    using TPagingInfo = ISearchReplyData::TPagingInfo;

public:
    TReportConstructor(const ISearchReplyData& replyData, IReportBuilderContext& context, bool safeReport = false)
        : ReplyData(replyData)
        , Context(context)
        , SafeReport(safeReport)
    {
        for (const auto& snip: Context.GetCgiParameters().Range("snip")) {
            if (snip.find("in_json_report=yes") != TString::npos) {
                Passages = true;
                break;
            }
        }
    }

    TBuffer BuildReport(IReportFormatter& formatter) const {
        ProcessRequest(formatter);
        formatter.OnRequestPage(Context.GetRequestedPage());

        TVector<TString> errors;
        ReplyData.FillErrors(errors);
        for (auto&& err : errors) {
            formatter.OnError(err);
        }

        TSearcherPropsRef searchProps = ReplyData.GetSearchProperties();
        formatter.OnSearchProperties(searchProps);

        TFullReportInfo reportStat;
        ReplyData.FillReportStat(reportStat);
        formatter.OnReportStat(reportStat.Stat);

        if (reportStat.Stat[0] > 0) {
            ProcessGroupings(formatter);
        }

        return formatter.GetResult();
    }

private:
    void ProcessRequest(IReportFormatter& formatter) const {
        formatter.OnRequestText(Context.GetCgiParameters().Get("text", 0));
    }

    void ProcessGroupings(IReportFormatter& formatter) const {
        const TRequestParams& rp = Context.GetRP();
        const long int requestedPage = Context.GetRequestedPage();

        if (rp.GroupingParams.empty()) {
            const TGroupingIndex gi(GM_FLAT, "");
            ProcessOneGrouping(gi, 0, requestedPage, formatter);
        } else {
            for (size_t i = 0; i < rp.GroupingParams.size(); ++i) {
                const TGroupingParams& gp = rp.GroupingParams[i];
                const TGroupingIndex gi(gp.gMode, gp.gAttr);
                formatter.OnReportGroupingStat(gi, gp.gGroups, gp.gDocs, gp.gCurCat);
            }

            for (size_t i = 0; i < rp.GroupingParams.size(); ++i) {
                const TGroupingParams& gp = rp.GroupingParams[i];

                const TGroupingIndex gi(gp.gMode, gp.gAttr);
                ProcessOneGrouping(gi, gp.gGroups, requestedPage, formatter);
            }
        }
    }

    void ProcessOneGrouping(const TGroupingIndex& gi, const int groups, const long int requestedPage,
                            IReportFormatter& formatter) const
    {
        TGroupingInfo groupingInfo;
        ReplyData.FillGroupingInfo(gi, groupingInfo);
        TPagingInfo pagingInfo;
        ReplyData.FillPagingInfo(gi, groups, requestedPage, pagingInfo);

        if (!formatter.OnGrouping(gi, groupingInfo, pagingInfo, groups)) {
            return;
        }

        for (ui64 group = pagingInfo.FirstGroup; group < pagingInfo.LastGroup; ++group) {
            TGroupInfo groupInfo;
            ReplyData.FillGroupInfo(group, gi, groupInfo);
            if (!formatter.OnGroup(groupInfo)) {
                continue;
            }

            for (ui64 doc = 0; doc < groupInfo.DocsCount; ++doc) {
                TMainDocArchiveInfo archiveInfo;
                ReplyData.FillArchiveInfo(group, doc, gi, Passages, archiveInfo);

                if (!formatter.OnDocument(archiveInfo)) {
                    continue;
                }

                TAttrsInfo attrsInfo;
                ReplyData.FillIndexAttrsInfo(group, doc, gi, attrsInfo);

                for (ui32 i = 0; i < attrsInfo.Attrs.size(); ++i) {
                    for (ui32 j = 0; j < attrsInfo.DocsCount[i]; ++j) {
                        TDocCategInfo info;
                        ReplyData.FillDocCategInfo(group, doc, gi, attrsInfo.Attrs[i], j, info);
                        ReportCateg(info, attrsInfo.Attrs[i], 0, formatter);
                    }
                }

                formatter.OnDocAttributesStart();
                ReplyData.SerializeAttributes(group, doc, gi, {&DP_ALLDOCINFOS, 1}, formatter, !SafeReport);
                formatter.OnDocAttributesEnd();
            }
        }
    }

    void ReportCateg(const TDocCategInfo& info, const TString& attr, ui32 index, IReportFormatter& formatter) const {
        if (info.Categs[index] != END_CATEG) {
            ReportCateg(info, attr, index + 1, formatter);
            formatter.OnCateg(info, attr, index);
        }
    }

private:
    const ISearchReplyData& ReplyData;
    IReportBuilderContext& Context;
    bool Passages = false;
    bool SafeReport = false;
};
