#include "legacy_range.h"

#include <balancer/kernel/http/parser/common_headers.h>
#include <balancer/kernel/http/parser/http.h>

#include <util/generic/ptr.h>
#include <util/generic/xrange.h>
#include <util/stream/str.h>

#include <array>

namespace NReport {
    namespace NLegacyRange {
        namespace {
            struct TDimInfoBase : TAtomicRefCount<TDimInfoBase> {
                const TString Attr;
                const TVector<TString> Cases;
                const size_t Size;
                const EDim Id;
                const unsigned Mask;

            public:
                TDimInfoBase(TString attr, EDim id, TVector<TString> cases)
                    : Attr(std::move(attr))
                    , Cases(std::move(cases))
                    , Size(Cases.size())
                    , Id(id)
                    , Mask(TDimFilter().Set(id).Mask)
                {}

                virtual size_t GetIndex(const TDimData& data) const = 0;

                virtual ~TDimInfoBase() {}
            };


            struct TStatusInfo : public TDimInfoBase {
                TStatusInfo()
                    : TDimInfoBase("sc", EDim::Status,
                        //   0,     1,     2,     3,     4,     5,    6
                        {"err", "1xx", "2xx", "3xx", "4xx", "5xx", "404"})
                {}

                size_t GetIndex(const TDimData& data) const override {
                    const auto st = data.Status < 600 ? data.Status : 0;
                    switch (st) {
                    case 404:
                        return 6;
                    default:
                        return st / 100;
                    }
                }
            };


            struct TRobotInfo : public TDimInfoBase {
                TRobotInfo()
                    : TDimInfoBase("sr", EDim::Robot,
                        // 0,   1,   2
                        {"u", "n", "y"})
                {}

                size_t GetIndex(const TDimData& data) const override {
                    return data.Robot ? 1 + *data.Robot : 0;
                }
            };


            struct TInternalInfo : public TDimInfoBase {
                TInternalInfo()
                    : TDimInfoBase("ir", EDim::Internal,
                        // 0,   1,   2
                        {"u", "n", "y"})
                {}

                size_t GetIndex(const TDimData& data) const override {
                    return data.Internal ? 1 + *data.Internal : 0;
                }
            };


            struct TSslInfo : public TDimInfoBase {
                TSslInfo()
                    : TDimInfoBase("sl", EDim::Ssl,
                        //  0,    1,    2,    3
                        {"cu", "cU", "Cu", "CU"})
                {}

                size_t GetIndex(const TDimData& data) const override {
                    return data.Ssl.UserConnIsSsl + 2 * data.Ssl.ThisConnIsSsl;
                }
            };

            namespace {
                const std::array<TIntrusiveConstPtr<TDimInfoBase>, static_cast<size_t>(EDim::Total)> AllDims{{
                    MakeIntrusiveConst<TStatusInfo>(),
                    MakeIntrusiveConst<TRobotInfo>(),
                    MakeIntrusiveConst<TInternalInfo>(),
                    MakeIntrusiveConst<TSslInfo>(),
                }};

                const std::array<TIntrusiveConstPtr<TDimInfoBase>, static_cast<size_t>(EDim::Total)> UnistatDims{{
                    MakeIntrusiveConst<TStatusInfo>(),
                    MakeIntrusiveConst<TInternalInfo>(),
                    MakeIntrusiveConst<TRobotInfo>(),
                    MakeIntrusiveConst<TSslInfo>(),
                }};
            }

            struct TDimInfos : TNonCopyable {
                const unsigned TotalCombinations = TDimFilter().Set(EDim::Total).Mask;
                const unsigned Mask = TotalCombinations - 1;

                TVector<size_t> MatrixSizes;
                TVector<TVector<TString>> Prefixes;

            public:
                TDimInfos() {
                    MatrixSizes.resize(TotalCombinations, 1);
                    Prefixes.resize(TotalCombinations);

                    for (const auto mask : xrange(0u, TotalCombinations)) {
                        // Calculating the matrixSize for the current combination
                        auto& matrixSize = MatrixSizes[mask];
                        for (const auto& dim : AllDims) {
                            if (mask & dim->Mask) {
                                matrixSize *= dim->Size;
                            }
                        }

                        // Filling all the prefixes for the current combination
                        auto& maskPrefixes = Prefixes[mask];
                        maskPrefixes.resize(matrixSize);
                        for (auto idx : xrange<size_t>(0u, matrixSize)) {
                            TStringOutput sout(maskPrefixes[idx]);
                            sout << "<x";
                            for (const auto& dim : AllDims) {
                                if (mask & dim->Mask) {
                                    sout << " " << dim->Attr << "=\"" << dim->Cases[idx % dim->Size] << "\"";
                                    idx /= dim->Size;
                                }
                            }
                            sout << ">";
                        }
                    }
                }

                static const auto& Instance() {
                    return Default<TDimInfos>();
                }
            };
        }

        TString GetDimParameter(TDimFilter filter, size_t index, EDim parameter) {
            for (const auto& dim : UnistatDims) {
                if (filter.Mask & dim->Mask) {
                    if (dim->Mask == TDimFilter{}.Set(parameter).Mask) {
                        return dim->Cases[index % dim->Size];
                    }
                    index /= dim->Size;
                }
            }
            Y_VERIFY(false);
        }

        size_t GetUnistatMatrixSize(TDimFilter filter) {
            size_t size = 1;
            for (const auto& dim : AllDims) {
                if (filter.Mask & dim->Mask) {
                    size *= dim->Size;
                }
            }
            return size;
        }

        TString GetUnistatSuffix(TDimFilter filter, size_t index) {
            TStringBuilder suffix;
            for (const auto& dim : UnistatDims) {
                if (filter.Mask & dim->Mask) {
                    suffix << "-" << dim->Cases[index % dim->Size];
                    index /= dim->Size;
                }
            }
            return suffix;
        }

        size_t GetUnistatIndex(TDimFilter filter, TDimData data, bool disableRobotness) {
            if (disableRobotness) {
                data.Internal = Nothing();
                data.Robot = Nothing();
            }

            size_t res = 0;
            size_t mult = 1;
            for (const auto& dim : UnistatDims) {
                if (filter.Mask & dim->Mask) {
                    res += dim->GetIndex(data) * mult;
                    mult *= dim->Size;
                }
            }
            return res;
        }

        size_t GetMatrixSize(TDimFilter filter) {
            return TDimInfos::Instance().MatrixSizes[filter.Mask];
        }

        size_t GetIndex(TDimFilter filter, const TDimData& data) {
            size_t res = 0;
            size_t mult = 1;
            for (const auto& dim : AllDims) {
                if (filter.Mask & dim->Mask) {
                    res += dim->GetIndex(data) * mult;
                    mult *= dim->Size;
                }
            }
            return res;
        }

        TStringBuf GetPrefix(TDimFilter filter, size_t index) {
            return TDimInfos::Instance().Prefixes[filter.Mask & TDimInfos::Instance().Mask][index];
        }

        TStringBuf GetSuffix() {
            return "</x>";
        }

        TDimData GetDimData(const TConnDescr& descr, unsigned respStatus) {
            TDimData res;
            res.Status = respStatus;
            res.Ssl.UserConnIsSsl = descr.Properties->UserConnIsSsl;
            res.Ssl.ThisConnIsSsl = descr.Ssl().ThisConnIsSsl;

            if (descr.Request) {
                if (auto v = descr.Request->Headers().GetFirstValue("X-Yandex-Internal-Request")) {
                    res.Internal = Match(TTrueFsm::Instance(), v);
                }
                if (auto v = descr.Request->Headers().GetFirstValue("X-Yandex-Suspected-Robot")) {
                    res.Robot = Match(TTrueFsm::Instance(), v);
                }
            }

            return res;
        }

        void Initialize() {
            TDimInfos::Instance();
        }
    }
}
