#include "calc.h"

#include <library/cpp/config/config.h>
#include <library/cpp/digest/murmur/murmur.h>

#include <util/string/builder.h>
#include <util/string/join.h>
#include <util/string/subst.h>

namespace NModCgiHasher {
    using NSrvKernel::TRegexp;
    using NSrvKernel::TChunkPtr;
    using NSrvKernel::TChunkList;

    namespace {
        TString WrapRegexp(TString regexp) {
            return TString::Join("[?&](?:", regexp, ")=([^&]+)");
        }

        auto RegexpOpts(ICgiHasher::TOpts opts) {
            TRegexp::TOpts res;
            res.SetCaseInsensitive(opts.CaseInsensitive);
            res.SetPosixSyntax(false);
            return res;
        }

        auto GenSingleRegexp(TVector<TString> params, ICgiHasher::TOpts opts) {
            return MakeHolder<TRegexp>(
                WrapRegexp(JoinSeq("|", params)),
                RegexpOpts(opts)
            );
        }

        auto GenMultipleRegexps(TVector<TString> params, ICgiHasher::TOpts opts) {
            TDeque<TRegexp> res;
            for (auto param : params) {
                res.emplace_back(
                    WrapRegexp(param),
                    RegexpOpts(opts)
                );
            }
            return res;
        }

        TMaybe<ui64> CalcHash(TConstArrayRef<TStringBuf> buffers) {
            if (buffers.empty()) {
                return Nothing();
            }

            TMurmurHash2A<ui64> hash;
            for (auto buffer : buffers) {
                hash.Update(buffer.Data(), buffer.Size());
            }

            return hash.Value();
        }

        class TConcatenatedCgiHasher : public ICgiHasher {
        public:
            TConcatenatedCgiHasher(TVector<TString> params, TOpts opts)
                : Regexp_(GenSingleRegexp(params, opts))
            {}

            TMaybe<ui64> Calc(TStringBuf cgi) const noexcept override {
                TVector<TStringBuf> matches;
                Regexp_->ExtractAll(cgi, &matches, false);
                return CalcHash(matches);
            }

        private:
            THolder<TRegexp> Regexp_;
        };


        class TPriorityCgiHasher : public ICgiHasher {
        public:
            TPriorityCgiHasher(TVector<TString> params, ICgiHasher::TOpts opts)
                : Regexps_(GenMultipleRegexps(params, opts))
            {}

            TMaybe<ui64> Calc(TStringBuf cgi) const noexcept override {
                for (const auto& regexp : Regexps_) {
                    TVector<TStringBuf> match;
                    regexp.Extract(cgi, &match, true);
                    TConstArrayRef<TStringBuf> view = match;
                    if (!view.empty()) {
                        view = view.subspan(1);
                    }
                    if (auto res = CalcHash(view)) {
                        return res;
                    }
                }
                return Nothing();
            }

        private:
            TDeque<TRegexp> Regexps_;
        };
    }


    THolder<ICgiHasher> MakeHasher(TVector<TString> params, ICgiHasher::TOpts opts) {
        switch (opts.Mode) {
        case ICgiHasher::EMode::Concatenated:
            return MakeHolder<TConcatenatedCgiHasher>(params, opts);
        case ICgiHasher::EMode::Priority:
            return MakeHolder<TPriorityCgiHasher>(params, opts);
        }
    }
}
