/* doscan - Denial Of Service Capable Auditing of Networks       -*- C++ -*-
 * Copyright (C) 2003 Florian Weimer
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef EVENT_QUEUE_H
#define EVENT_QUEUE_H

#include "ticks.h"

#include <set>
#include <functional>

/*
 * event_queue
 *
 * An event_queue object handles file descriptor activity and
 * timeouts.  Pure timeout handlers use event_queue::handler, handlers
 * for file descriptors use event_queue::fd_handler.  For every file
 * descriptor, there is an associated timeout (and fd_handler derives
 * from handler).
 *
 * handler and fd_handler objects may only be created using operator
 * new.  Memory they are automatically added to the corresponding
 * event queue, which is also responsible for freeing the handlers.
 * Destructors of handlers shall not perform any complicated activity
 * (such as adding new handlers).
 *
 * When you have create some handler objects, you may invoke
 * event_queue::run().  This will start the queue runner.  run() will
 * return if there are no more active handlers.
 *
 * Explicit deallocation of handlers is not recommended.  Handlers are
 * automatically deallocated when one of the on_*() member functions
 * returns false.  To trigger deallocation, you can move the handler
 * to an application-specific final state and call set_timeout(0); the
 * on_timeout() member function detects this and returns false.  For
 * fd_handler objects. you may also want to call unwatch().
 *
 */

class event_queue {
public:
  class handler;
  class fd_handler;

private:
  struct handler_compare_timeout
    : public std::binary_function<bool, const handler*, const handler*>
  {
    bool operator()(const handler*, const handler*) const;
  };
  typedef std::multiset<handler*, handler_compare_timeout> timeouts_t;

public:
  class handler {
    friend class event_queue;
    friend class handler_compare_timeout;

    handler();                  // do not call
    handler(const handler&);    // do not call
    handler& operator=(const handler&); // do not call

  protected:
    event_queue& m_queue;

  private:
    ticks_t timeout;
    timeouts_t::iterator timeout_ptr;

    bool compare_timeout(const handler& that) const;

  public:
    handler(event_queue&);
    virtual ~handler();

    event_queue& queue() const;

    virtual bool on_timeout(ticks_t) = 0;
    // Returns true if the handler shall continue, otherwise it will
    // be deleted.  The argument indicates the time when the timeout
    // occured (this might not be the current time).

    void set_absolute_timeout(ticks_t);
    void set_relative_timeout(ticks_t);
    void set_immediate_timeout();
    void set_infinite_timeout();
  };

  class fd_handler : public handler {
    fd_handler();               // do not call
    fd_handler(const fd_handler&); // do not call
    fd_handler& operator=(const fd_handler&); // do not call

    int real_fd;

    bool compare_fd(const fd_handler& that) const;

  public:
    typedef enum {
      watch_read = 1, watch_write = 2, watch_read_write = 3
    } watch_options;

    typedef enum {
      activity_read = 1, activity_write = 2, activity_read_write = 3,
      activity_error
    } activity;

    fd_handler(event_queue& q, int fd, watch_options);
    ~fd_handler();

    int fd() const;
    // Returns the watched file descriptor.

    virtual bool on_activity(activity) = 0;
    // Returns true if the handler shall continue, otherwise it's
    // deleted.

    void watch(int fd, watch_options);
    void watch(watch_options);

    void unwatch();
    // No longer watch fd.  We still wait for the timeout.
  };

private:
  timeouts_t timeouts;

  // Saves the time at the start of a dispatching run.
  ticks_t dispatch_start_ticks;

protected:

  // The following routines have to be overridden by decending
  // classes.  They glue together the fd_handler activity and the I/O
  // multiplexing implementation.  add_fd() adds a fresh handler,
  // update_fd() changes an existing handler, and remove_fd() removes
  // a handler.

  virtual void add_fd(fd_handler*, fd_handler::watch_options) = 0;
  virtual void update_fd(fd_handler*, fd_handler::watch_options) = 0;
  virtual void remove_fd(fd_handler*);

  // Before you can invoke fd_handler::on_activity(), you should use
  // this routine.  It saves the current time so that we get a
  // consistent cut.

  void dispatch_start();

  // Should be invoked after all calls to fd_handler::on_activity().

  void dispatch_end();

  // next_timeout() returns the next (relevative) timeout in
  // milliseconds, zero if an immediate timeout is required, and a
  // -1 for an infinite timeout.

  int next_timeout();

public:
  event_queue();
  virtual ~event_queue();

  virtual void run() = 0;

  // Creates an event queue that is optimized for this system.
  static event_queue* create(unsigned size_hint);

  // Prints the listeners to std::cerr.
  void dump();
};

// handler_compare_timeout
inline bool
event_queue::handler_compare_timeout::operator()(const handler* left,
                                                 const handler* right) const
{
  return left->timeout < right->timeout;
}

// handler

inline bool
event_queue::handler::compare_timeout(const event_queue::handler& that) const
{
  return timeout < that.timeout;
}

inline event_queue&
event_queue::handler::queue() const
{
  return m_queue;
}

inline void
event_queue::handler::set_relative_timeout(ticks_t ticks)
{
  set_absolute_timeout(ticks_get_cached() + ticks);
}

inline void
event_queue::handler::set_immediate_timeout()
{
  set_absolute_timeout(0);
}

inline void
event_queue::handler::set_infinite_timeout()
{
  set_absolute_timeout(TICKS_LAST);
}

// fd_handler

inline bool
event_queue::fd_handler::compare_fd(const fd_handler& that) const
{
  return fd() < that.fd();
}

inline int
event_queue::fd_handler::fd() const
{
  return real_fd;
}

inline void
event_queue::fd_handler::watch(watch_options w)
{
  m_queue.update_fd(this, w);
}

// event_queue

inline void
event_queue::dispatch_start()
{
  dispatch_start_ticks = ticks_get();
}

#endif // EVENT_QUEUE_H
