/**
 * @file   throttle.h
 * @author Nat Goodspeed
 * @date   2024-08-12
 * @brief  Throttle class
 *
 * $LicenseInfo:firstyear=2024&license=viewerlgpl$
 * Copyright (c) 2024, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_THROTTLE_H)
#define LL_THROTTLE_H

#include "apply.h"                  // LL::bind_front()
#include "llerror.h"
#include <functional>
#include <iomanip>                  // std::quoted()

class ThrottleBase
{
public:
    ThrottleBase(F64 interval):
        mInterval(interval)
    {}

protected:
    bool too_fast();                // not const: we update mNext
    F64 mInterval, mNext{ 0. };
};

/**
 * An instance of Throttle mediates calls to some other specified function,
 * ensuring that it's called no more often than the specified time interval.
 * Throttle is an abstract base class that delegates the behavior when the
 * specified interval is exceeded.
 */
template <typename SIGNATURE>
class Throttle: public ThrottleBase
{
public:
    Throttle(const std::string& desc,
             const std::function<SIGNATURE>& func,
             F64 interval):
        ThrottleBase(interval),
        mDesc(desc),
        mFunc(func)
    {}
    // Constructing Throttle with a member function pointer but without an
    // instance pointer requires you to pass the instance pointer/reference as
    // the first argument to operator()().
    template <typename R, class C>
    Throttle(const std::string& desc, R C::* method, F64 interval):
        Throttle(desc, std::mem_fn(method), interval)
    {}
    template <typename R, class C>
    Throttle(const std::string& desc, R C::* method, C* instance, F64 interval):
        Throttle(desc, LL::bind_front(method, instance), interval)
    {}
    template <typename R, class C>
    Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval):
        Throttle(desc, LL::bind_front(method, instance), interval)
    {}
    virtual ~Throttle() {}

    template <typename... ARGS>
    auto operator()(ARGS... args)
    {
        if (too_fast())
        {
            suppress();
            using rtype = decltype(mFunc(std::forward<ARGS>(args)...));
            if constexpr (! std::is_same_v<rtype, void>)
            {
                return rtype{};
            }
        }
        else
        {
            return mFunc(std::forward<ARGS>(args)...);
        }
    }

protected:
    // override with desired behavior when calls come too often
    virtual void suppress() = 0;
    const std::string mDesc;

private:
    std::function<SIGNATURE> mFunc;
};

/**
 * An instance of LogThrottle mediates calls to some other specified function,
 * ensuring that it's called no more often than the specified time interval.
 * When that interval is exceeded, it logs a message at the specified log
 * level. It uses LL_MUMBLES_ONCE() logic to prevent spamming, since a too-
 * frequent call may well be spammy.
 */
template <LLError::ELevel LOGLEVEL, typename SIGNATURE>
class LogThrottle: public Throttle<SIGNATURE>
{
    using super = Throttle<SIGNATURE>;
public:
    LogThrottle(const std::string& desc,
                const std::function<SIGNATURE>& func,
                F64 interval):
        super(desc, func, interval)
    {}
    template <typename R, class C>
    LogThrottle(const std::string& desc, R C::* method, F64 interval):
        super(desc, method, interval)
    {}
    template <typename R, class C>
    LogThrottle(const std::string& desc, R C::* method, C* instance, F64 interval):
        super(desc, method, instance, interval)
    {}
    template <typename R, class C>
    LogThrottle(const std::string& desc, R C::* method, const C* instance, F64 interval):
        super(desc, method, instance, interval)
    {}

private:
    void suppress() override
    {
        // Using lllog(), the macro underlying LL_WARNS() et al., allows
        // specifying compile-time LOGLEVEL. It does NOT support a variable
        // LOGLEVEL, which is why LOGLEVEL is a non-type template parameter.
        // See llvlog() for variable support, which is a bit more expensive.
        // true = only print the log message once
        lllog(LOGLEVEL, true, "LogThrottle") << std::quoted(super::mDesc)
                                             << " called more than once per "
                                             << super::mInterval
                                             << LL_ENDL;
    }
};

#endif /* ! defined(LL_THROTTLE_H) */