#include "calc.h"
#include "module.h"

#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/log/errorlog.h>
#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/module/module.h>
#include <balancer/kernel/regexp/regexp_pire.h>

#include <library/cpp/config/sax.h>

#include <util/generic/ptr.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <util/string/builder.h>
#include <util/string/join.h>


using namespace NConfig;
using namespace NSrvKernel;


MODULE_BASE(cgi_hasher, TModuleWithSubModule) {
public:
    TModule(const TModuleParams& mp)
        : TModuleBase(mp)
    {
        Config->ForEach(this);

        if (!Submodule_) {
            ythrow TConfigParseError() << "no submodule configured";
        }

        if (Parameters_.empty()) {
            ythrow TConfigParseError() << "no parameters configured";
        }

        Hasher_ = NModCgiHasher::MakeHasher(std::move(Parameters_), HasherOpts_);
    }

private:
    START_PARSE {
        if (key == "parameters") {
            auto* subconf = value->AsSubConfig();
            if (!subconf) {
                ythrow TConfigParseError() << "\"parameters\" must be a list of cgi param names";
            }

            ParseMap(subconf, [this](const auto& key, auto* value) {
                Y_ENSURE_EX(!value->AsString().empty(),
                        NConfig::TConfigParseError{} << "empty parameter at " << key);

                Parameters_.push_back(value->AsString());
            });

            if (Parameters_.empty()) {
                ythrow TConfigParseError() << "parameters must be non-empty";
            }

            return;
        }

        ON_KEY("case_insensitive", HasherOpts_.CaseInsensitive) {
            return;
        }

        ON_KEY("mode", HasherOpts_.Mode) {
            return;
        }

        ON_KEY("combine_hashes", CombineHashes_) {
            return;
        }

        ON_KEY("randomize_empty_match", RandomizeEmptyMatch_) {
            return;
        }

        Submodule_.Reset(Loader->MustLoad(key, Copy(value->AsSubConfig())).Release());
        return;
    } END_PARSE

    TMaybe<ui64> CalcHash(const TConnDescr& descr) const noexcept {
        if (!descr.Request) {
            return Nothing();
        }

        return Hasher_->Calc(descr.Request->RequestLine().CGI.AsStringBuf());
    }

    TError DoRun(const TConnDescr& descr) const noexcept override {
        auto hash = CalcHash(descr);
        if (!hash && RandomizeEmptyMatch_) {
            hash = RandomNumber<NSrvKernel::TRequestHash>();
        }
        if (CombineHashes_ && descr.Hash) {
            hash = hash ? CombineHashes(*hash, descr.Hash) : descr.Hash;
        }
        return Submodule_->Run(descr.Copy(hash.GetOrElse(0)));
    }

private:
    TVector<TString> Parameters_;
    THolder<NModCgiHasher::ICgiHasher> Hasher_;
    NModCgiHasher::ICgiHasher::TOpts HasherOpts_;
    bool RandomizeEmptyMatch_ = true;
    bool CombineHashes_ = false;
};

IModuleHandle* NModCgiHasher::Handle() {
    return TModule::Handle();
}
