/*
 * Simple event loop to multiplex the different subsystems we need
 * Header file
 *
 * $Id: event.h,v 1.7 2003/04/14 19:18:04 hsteoh Exp hsteoh $
 * ---------------------------------------------------------------------------
 * NOTES:
 * - this requires a POSIX-compliant system, because it uses UNIX fd's and
 *   select(), and gettimeofday() which is a BSD interface.
 */

#ifndef EVENT_H
#define EVENT_H

#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <list.h>			// MUST be prog/lib version!!


class eventloop;			// forward decl
class eventhandler {
public:
  eventhandler() {}
  virtual ~eventhandler();

  virtual void read_ready(eventloop *src, int fd)=0;
  virtual void write_ready(eventloop *src, int fd)=0;
};

class timerhandler {
public:
  // STATIC should be used if the timer handler is managed by another object;
  // DYNAMIC means the handler will be automatically destructed once it's
  // removed from the timer queue.
  enum type_t { STATIC=0, DYNAMIC };
private:
  type_t handlertype;
public:
  timerhandler(type_t timertype) : handlertype(timertype) {}
  virtual ~timerhandler();

  virtual void tick(eventloop *src, timeval t)=0;
  type_t type() { return handlertype; }
};

// Convenience functions
int operator< (timeval t1, timeval t2);
int operator<= (timeval t1, timeval t2);
timeval &operator+= (timeval &t1, timeval t2);
timeval &operator-= (timeval &t1, timeval t2);

// (Used internally by class eventloop)
struct timer_entry {
  int id;
  enum timer_t { PERIODIC, ONETICK } type;
  timeval target;
  timeval period;			// if periodic
  timerhandler *handler;		// [R]

  // Notes:
  // - the manpage doesn't document this, but tv_usec is basically the
  //   microsecond part of the time, and is always between 0 and 999,999
  //   inclusive.
  inline int operator< (timer_entry &t) { return target < t.target; }
};

// Scheduler for timers (used internally by class eventloop)
class timerqueue {
  elist<timer_entry> queue;
  static int nextid;

  void insert(timer_entry ent);
public:
  timerqueue() {}
  ~timerqueue();

  // Returns timer id which can be used to unschedule it
  int schedule(timeval target, timerhandler *handler);
  int schedule(timeval target, timeval period, timerhandler *handler);
  void unschedule(int id);

  int num_timers() { return queue.num_elem(); }
  timeval next_scheduled();		// time of next scheduled timer tick
  void ticknext(eventloop *src, timeval curtime);	// trigger all timers
					// whose target times are <= curtime.
};

class eventloop {
public:
  enum type_t { READER, WRITER, READWRITER };
private:
  int max_fd;

  struct handler_entry {
    int fd;
    eventhandler *handler;		// [R]
  };
  elist<handler_entry> readers;
  elist<handler_entry> writers;

  // Timers
  timerqueue timers;			// timer scheduler

  // Reentrancy-handling stuff
  int reentrant_level;			// >0 means only read-only ops allowed
  elist<int> dead_readers;
  elist<int> dead_writers;
  elist<handler_entry> new_readers;
  elist<handler_entry> new_writers;

  elistiter<handler_entry> search(elist<handler_entry> &list, int fd);
  int remove(elist<handler_entry> &list, int fd);
  void update_maxes();
  void make_fdset(elist<handler_entry> &list, fd_set *set);
  timeval *calc_wait(timeval *t);

  void postponed_del(elist<handler_entry> &addlist, elist<int> &dellist,
                     int fd);
  void dispatch(elist<handler_entry> &list, fd_set *set,
                void (eventhandler::*method)(eventloop *src, int fd));
  void sync();				// do delayed additions & removals
  void sweep(elist<handler_entry> &target, elist<int> &delayed);
  void fill(elist<handler_entry> &target, elist<handler_entry> &delayed);
  void fire_timers();
public:
  eventloop();
  ~eventloop();

  // File descriptors
  void register_handler(type_t type, int fd, eventhandler *handler); // ([R])
  void unregister_handler(type_t type, int fd);

  // Timers. The schedule() functions return a timer ID which may be used for
  // cancelling the timer later.
  // The sec+usec parameters are relative to the current time.
  int schedule(long sec, long usec, timerhandler *handler);
  int schedule(long first_sec, long first_usec, long period_sec,
               long period_usec, timerhandler *handler);
  inline void unschedule(int timer_id) { // NOOP if ID isn't in timer queue
    timers.unschedule(timer_id);
  }

  void run(int *exitflag);
};


#endif // EVENT_H
