/**
 * @file   lleventfilter.h
 * @author Nat Goodspeed
 * @date   2009-03-05
 * @brief  Define LLEventFilter: LLEventStream subclass with conditions
 * 
 * $LicenseInfo:firstyear=2009&license=viewergpl$
 * Copyright (c) 2009, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LLEVENTFILTER_H)
#define LL_LLEVENTFILTER_H

#include "llevents.h"
#include "stdtypes.h"
#include "lltimer.h"
#include <boost/function.hpp>

/**
 * Generic base class
 */
class LL_COMMON_API LLEventFilter: public LLEventStream
{
public:
    /// construct a standalone LLEventFilter
    LLEventFilter(const std::string& name="filter", bool tweak=true):
        LLEventStream(name, tweak)
    {}
    /// construct LLEventFilter and connect it to the specified LLEventPump
    LLEventFilter(LLEventPump& source, const std::string& name="filter", bool tweak=true);

    /// Post an event to all listeners
    virtual bool post(const LLSD& event) = 0;
};

/**
 * Pass through only events matching a specified pattern
 */
class LLEventMatching: public LLEventFilter
{
public:
    /// Pass an LLSD map with keys and values the incoming event must match
    LLEventMatching(const LLSD& pattern);
    /// instantiate and connect
    LLEventMatching(LLEventPump& source, const LLSD& pattern);

    /// Only pass through events matching the pattern
    virtual bool post(const LLSD& event);

private:
    LLSD mPattern;
};

/**
 * Wait for an event to be posted. If no such event arrives within a specified
 * time, take a specified action. See LLEventTimeout for production
 * implementation.
 *
 * @NOTE This is an abstract base class so that, for testing, we can use an
 * alternate "timer" that doesn't actually consume real time.
 */
class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter
{
public:
    /// construct standalone
    LLEventTimeoutBase();
    /// construct and connect
    LLEventTimeoutBase(LLEventPump& source);

    /// Callable, can be constructed with boost::bind()
    typedef boost::function<void()> Action;

    /**
     * Start countdown timer for the specified number of @a seconds. Forward
     * all events. If any event arrives before timer expires, cancel timer. If
     * no event arrives before timer expires, take specified @a action.
     *
     * This is a one-shot timer. Once it has either expired or been canceled,
     * it is inert until another call to actionAfter().
     *
     * Calling actionAfter() while an existing timer is running cheaply
     * replaces that original timer. Thus, a valid use case is to detect
     * idleness of some event source by calling actionAfter() on each new
     * event. A rapid sequence of events will keep the timer from expiring;
     * the first gap in events longer than the specified timer will fire the
     * specified Action.
     *
     * Any post() call cancels the timer. To be satisfied with only a
     * particular event, chain on an LLEventMatching that only passes such
     * events:
     *
     * @code
     * event                                                 ultimate
     * source ---> LLEventMatching ---> LLEventTimeout  ---> listener
     * @endcode
     *
     * @NOTE
     * The implementation relies on frequent events on the LLEventPump named
     * "mainloop".
     */
    void actionAfter(F32 seconds, const Action& action);

    /**
     * Like actionAfter(), but where the desired Action is LL_ERRS
     * termination. Pass the timeout time and the desired LL_ERRS @a message.
     *
     * This method is useful when, for instance, some async API guarantees an
     * event, whether success or failure, within a stated time window.
     * Instantiate an LLEventTimeout listening to that API and call
     * errorAfter() on each async request with a timeout comfortably longer
     * than the API's time guarantee (much longer than the anticipated
     * "mainloop" granularity).
     *
     * Then if the async API breaks its promise, the program terminates with
     * the specified LL_ERRS @a message. The client of the async API can
     * therefore assume the guarantee is upheld.
     *
     * @NOTE
     * errorAfter() is implemented in terms of actionAfter(), so all remarks
     * about calling actionAfter() also apply to errorAfter().
     */
    void errorAfter(F32 seconds, const std::string& message);

    /**
     * Like actionAfter(), but where the desired Action is a particular event
     * for all listeners. Pass the timeout time and the desired @a event data.
     * 
     * Suppose the timeout should only be satisfied by a particular event, but
     * the ultimate listener must see all other incoming events as well, plus
     * the timeout @a event if any:
     * 
     * @code
     * some        LLEventMatching                           LLEventMatching
     * event  ---> for particular  ---> LLEventTimeout  ---> for timeout
     * source      event                                     event \
     *       \                                                      \ ultimate
     *        `-----------------------------------------------------> listener
     * @endcode
     * 
     * Since a given listener can listen on more than one LLEventPump, we can
     * set things up so it sees the set union of events from LLEventTimeout
     * and the original event source. However, as LLEventTimeout passes
     * through all incoming events, the "particular event" that satisfies the
     * left LLEventMatching would reach the ultimate listener twice. So we add
     * an LLEventMatching that only passes timeout events.
     *
     * @NOTE
     * eventAfter() is implemented in terms of actionAfter(), so all remarks
     * about calling actionAfter() also apply to eventAfter().
     */
    void eventAfter(F32 seconds, const LLSD& event);

    /// Pass event through, canceling the countdown timer
    virtual bool post(const LLSD& event);

    /// Cancel timer without event
    void cancel();

protected:
    virtual void setCountdown(F32 seconds) = 0;
    virtual bool countdownElapsed() const = 0;

private:
    bool tick(const LLSD&);

    LLBoundListener mMainloop;
    Action mAction;
};

/// Production implementation of LLEventTimoutBase
class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase
{
public:
    LLEventTimeout();
    LLEventTimeout(LLEventPump& source);

protected:
    virtual void setCountdown(F32 seconds);
    virtual bool countdownElapsed() const;

private:
    LLTimer mTimer;
};

#endif /* ! defined(LL_LLEVENTFILTER_H) */
