/*
 * Simple generic event loop to multiplex the different subsystems we need
 * Implementation file
 *
 * $Id: event.cc,v 1.10 2003/04/14 19:52:18 hsteoh Exp hsteoh $
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>

#include "event.h"
#include "exception.h"



/*
 *
 * Class eventhandler
 *
 */

eventhandler::~eventhandler() {}



/*
 *
 * General convenience operators
 *
 */

int operator< (timeval t1, timeval t2) {
  return (t1.tv_sec != t2.tv_sec) ? t1.tv_sec < t2.tv_sec
                                  : t1.tv_usec < t2.tv_usec;
}

int operator<= (timeval t1, timeval t2) {
  return (t1.tv_sec != t2.tv_sec) ? t1.tv_sec < t2.tv_sec
                                  : t1.tv_usec <= t2.tv_usec;
}

timeval &operator+= (timeval &t1, timeval t2) {
  t1.tv_usec += t2.tv_usec;
  if (t1.tv_usec > 1000000) {
    t1.tv_sec += t1.tv_usec/1000000;
    t1.tv_usec %= 1000000;
  }
  t1.tv_sec += t2.tv_sec;
  return t1;
}

// WARNING: this function assumes that 0 <= tv_usec < 1000000 for both
// operands. It may return strange results if this is not true.
timeval &operator-= (timeval &t1, timeval t2) {
  t1.tv_usec -= t2.tv_usec;
  if (t1.tv_usec < 0) {
    t1.tv_sec--;			// borrow a second
    t1.tv_usec += 1000000;
  }
  t1.tv_sec -= t2.tv_sec;
  return t1;
}



/*
 *
 * Class timerhandler
 *
 */

timerhandler::~timerhandler() {}



/*
 *
 * Class timerqueue
 *
 */

int timerqueue::nextid = 0;

void timerqueue::insert(timer_entry ent) {
  elistiter<timer_entry> it, prev;

  // Insert in sorted order
  prev.invalidate();
  for (it=queue.headp(); it; it++) {
    if (ent < *it) {
      queue.insert(ent, prev);
      return;				// done
    }
    prev=it;
  }

  // We're at end of list, just append the new entry.
  queue.insert(ent, prev);
}

timerqueue::~timerqueue() {
  elistiter<timer_entry> null;
  timer_entry ent;

  // This is necessary because although timer_entry is embedded, timerhandler*
  // could potentially be an owner pointer. We can't put this in timer_entry's
  // dtor 'cos we're doing lots of copying around, and reference counts would
  // likely be incorrect by now.
  while (queue.num_elem() > 0) {
    ent = queue.remove(null);
    if (ent.handler->type() == timerhandler::DYNAMIC) {
      delete ent.handler;
    }
  }
}

// FIXME: we should be careful of ID uniqueness issues. Shouldn't be a problem
// normally, since sizeof(int) is big; but for long running servers this is
// a real issue that must be addressed.
int timerqueue::schedule(timeval target, timerhandler *handler) {
  timer_entry ent;

  // Create new timer entry
  ent.id = nextid++;
  ent.type = timer_entry::ONETICK;
  ent.target = target;
  ent.handler = handler;

  insert(ent);				// add to queue

  return ent.id;
}

int timerqueue::schedule(timeval target, timeval period,
                         timerhandler *handler) {
  timer_entry ent;

  // Create new periodic timer
  ent.id = nextid++;
  ent.type = timer_entry::PERIODIC;
  ent.target = target;
  ent.period = period;
  ent.handler = handler;

  insert(ent);

  return ent.id;
}

// Does nothing if ID doesn't exist.
void timerqueue::unschedule(int id) {
  elistiter<timer_entry> it, prev;

  prev.invalidate();
  for (it=queue.headp(); it; it++) {
    if ((*it).id==id) {
      timer_entry ent = queue.remove(prev);
      if (ent.handler->type()==timerhandler::DYNAMIC)
        delete ent.handler;
    }
    prev=it;
  }
}

timeval timerqueue::next_scheduled() {
  elistiter<timer_entry> it = queue.headp();

  if (it) {
    return (*it).target;
  } else {
    // FIXME: should throw exception here; it's a bad idea to return 0 which
    // may cause select() to poll instead of block.
    timeval t = { 0, 0 };
    return t;
  }
}

// WARNING: this function can potentially enter a VERY long loop if some of
// the timers in the queue have a very ancient target time and a small period.
void timerqueue::ticknext(eventloop *src, timeval curtime) {
  while (queue.headp() && (*queue.headp()).target <= curtime) {
    elistiter<timer_entry> null;
    timer_entry ent = queue.remove(null);

    // trigger timer callback
    ent.handler->tick(src, curtime);

    // Reschedule timer if periodic
    if (ent.type==timer_entry::PERIODIC) {
      // Note: don't use curtime, because we might be a bit late here.
      ent.target += ent.period;
      insert(ent);			// reschedule timer
    } else {
      if (ent.handler->type()==timerhandler::DYNAMIC)
        delete ent.handler;
    }
  }
}



/*
 *
 * Class eventloop
 *
 */

elistiter<eventloop::handler_entry> eventloop::search(
	elist<eventloop::handler_entry> &list, int fd) {
  elistiter<handler_entry> it;

  for (it=list.headp(); it; it++) {
    if ((*it).fd == fd) return it;
  }
  it.invalidate();			// indicate not found
  return it;
}

int eventloop::remove(elist<handler_entry> &list, int fd) {
  elistiter<handler_entry> it, prev;

  for (it=list.headp(); it; it++) {
    if ((*it).fd == fd) {
      list.remove(prev);
      return 1;
    }
    prev=it;
  }
  return 0;
}

void eventloop::update_maxes() {
  elistiter<handler_entry> it;

  max_fd=0;
  for (it=readers.headp(); it; it++) {
    if ((*it).fd > max_fd)
      max_fd = (*it).fd;
  }
  for (it=writers.headp(); it; it++) {
    if ((*it).fd > max_fd)
      max_fd = (*it).fd;
  }
}

void eventloop::make_fdset(elist<handler_entry> &list, fd_set *set) {
  elistiter<handler_entry> it;

  FD_ZERO(set);
  for (it=list.headp(); it; it++) {
    int fd = (*it).fd;

    if (FD_ISSET(fd, set))	// sanity check
      throw exception("@Internal error: multiple handlers registered for "
                      "fd %d", fd);
    FD_SET(fd, set);
  }
}

timeval *eventloop::calc_wait(timeval *t) {
  timeval curtime;

  // If no timers, return NULL so that select() will block indefinitely.
  if (timers.num_timers()==0) return NULL;

  *t = timers.next_scheduled();

  if (gettimeofday(&curtime, NULL)==-1)
    throw exception("Unable to get current time: %s", strerror(errno));

  if (*t <= curtime) {
    t->tv_sec = t->tv_usec = 0;		// at least one timer pending; force
					// select() to return immediately
  } else {
    *t -= curtime;			// interval from now till next timer
  }
  return t;
}


// Notes:
// - There is no equivalent for postponed_add() because functionally, deletes
//   cancels adds, but adds do not necessarily cancel deletes (since the newly
//   added handler could be different from the one before).
void eventloop::postponed_del(elist<handler_entry> &addlist,
                              elist<int> &dellist,
                              int fd) {
  elistiter<handler_entry> it, prev, delpred;
  int need_delete=0;			// need flag 'cos delpred can be
					// legitimately NULL if entry to be
					// deleted is at the head of the list

  // Scan addlist for any postponedly added entry that would be cancelled by
  // this delete operation. Notes:
  // - this loop finds the *last* occurring match in addlist, since that is
  //   functionally what would be removed had the add not been postponed.
  prev.invalidate();
  delpred.invalidate();
  for (it=addlist.headp(); it; it++) {
    if ((*it).fd == fd) {
      delpred=prev;			// mark for removal
      need_delete=1;
    }
    prev=it;
  }

  // Actually remove the entry if any were found. If none were found, this
  // delete operation affects the real handler list, so we add it to the
  // postponed deletion list.
  if (need_delete) {
    addlist.remove(delpred);
  } else {
    dellist.append(fd);
  }
}

void eventloop::dispatch(elist<handler_entry> &list, fd_set *set,
                         void (eventhandler::*method)(eventloop *src, int fd))
{
  elistiter<handler_entry> it;

  reentrant_level++;
  for (it=list.headp(); it; it++) {
    int fd = (*it).fd;

    if (FD_ISSET(fd, set)) {
      ((*it).handler->*method)(this, fd);
      FD_CLR(fd, set);
    }
  }

  // Sanity check
  for (int i=0; i<max_fd+1; i++) {
    if (FD_ISSET(i, set)) {
      throw exception("@Selected fd %d but no handler found for it!\n", i);
    }
  }

  reentrant_level--;
}

void eventloop::sync() {
  if (reentrant_level==0) {		// don't sweep/fill if still unsafe
    sweep(readers, dead_readers);
    sweep(writers, dead_writers);
    fill(readers, new_readers);
    fill(writers, new_writers);
  }
}

void eventloop::sweep(elist<handler_entry> &target, elist<int> &delayed) {
  elistiter<int> it;

  for (it=delayed.headp(); it; it++) {
    // (ignore errors; it's too late to report a non-existent fd by now)
    remove(target, *it);
  }
  delayed.clear();			// no more backlog
}

void eventloop::fill(elist<handler_entry> &target,
                     elist<handler_entry> &delayed) {
  elistiter<handler_entry> it;

  for (it=delayed.headp(); it; it++) {
    handler_entry &newent = *it;

    if (!search(target, newent.fd)) {
      target.append(newent);
      if (newent.fd > max_fd) max_fd = newent.fd;
    } else {
      // This probably indicates a horrible reentrance condition that we
      // haven't covered yet... in any case, things will definitely break if
      // this happens, even if it's the caller's fault; so we can't just
      // ignore it.
      throw exception("@[Delayed] reader already registered for fd %d\n",
                      newent.fd);
    }
  } // endforeach(delayed)

  delayed.clear();			// no more backlog
}

void eventloop::fire_timers() {
  timeval curtime;

  if (gettimeofday(&curtime, NULL)==-1)
    throw exception("Unable to get current time: %s", strerror(errno));

  // Fire off timers
  timers.ticknext(this, curtime);
}

eventloop::eventloop() {
  max_fd = 0;
  reentrant_level = 0;
}

eventloop::~eventloop() {
}

void eventloop::register_handler(type_t type, int fd, eventhandler *handler) {
  handler_entry newent;

  newent.fd = fd;
  newent.handler = handler;

  if (reentrant_level==0) {
    if (type==READER || type==READWRITER) {
      if (!search(readers, fd)) {
        readers.append(newent);
        if (fd > max_fd) max_fd = fd;
      } else {
        throw exception("@Reader already registered for fd %d\n", fd);
      }
    }
    if (type==WRITER || type==READWRITER) {
      if (!search(writers, fd)) {
        writers.append(newent);
        if (fd > max_fd) max_fd = fd;
      } else {
        throw exception("@Writer already registered for fd %d\n", fd);
      }
    }
  } else {				// in reentrance; delay addition
    if (type==READER || type==READWRITER)
      new_readers.append(newent);
    if (type==WRITER || type==READWRITER)
      new_writers.append(newent);
  }
}

void eventloop::unregister_handler(type_t type, int fd) {
  // If we're inside a list-altering function, do NOT attempt to do actual
  // remove; otherwise we may invalidate iterators and cause problems.
  if (reentrant_level==0) {
    if (type==READER || type==READWRITER) {
      if (!remove(readers, fd))
        throw exception("@No reader registered for fd %d, cannot "
                        "unregister\n", fd);
    }
    if (type==WRITER || type==READWRITER) {
      if (!remove(writers, fd))
        throw exception("@No writer registered for fd %d, cannot "
                        "unregister\n", fd);
    }
    update_maxes();
  } else {
    // we've been re-entered; schedule removal instead of actually doing it
    if (type==READER || type==READWRITER)
      postponed_del(new_readers, dead_readers, fd);
    if (type==WRITER || type==READWRITER)
      postponed_del(new_writers, dead_writers, fd);
  }
}

void eventloop::run(int *exitflag) {
  int n;
  fd_set readfds, writefds;
  timeval maxwait;			// max wait time until next timer

  if (reentrant_level > 0)
    throw exception("Attempt to call eventloop::run() reentrantly");

  while (!*exitflag) {
    make_fdset(readers, &readfds);
    make_fdset(writers, &writefds);

    // Select on fd's
    // Note: calc_wait() is called immediately here so that it can return
    // NULL if no timers are waiting.
    n = select(max_fd+1, &readfds, &writefds, NULL, calc_wait(&maxwait));

    if (n==-1) {
      if (errno!=EINTR) {		// ignore EINTR, it's just SIGCONT
        throw exception("@eventloop select() error: %s\n", strerror(errno));
      }
    } else if (n>0) {
      dispatch(readers, &readfds,  &eventhandler::read_ready);
      dispatch(writers, &writefds, &eventhandler::write_ready);
      sync();				// catchup on operations delayed to
					// prevent reentrancy problems
    }

    // Fire off expired timers
    // (Note: we do this after dispatching, so that any timer handlers that
    // unregisters event handlers won't invalidate readfds and writefds. Nasty
    // sequencing problems may arise otherwise.
    fire_timers();
  }
}

int eventloop::schedule(long sec, long usec, timerhandler *handler) {
  timeval curtime;
  timeval target = { sec + usec/1000000, usec % 1000000 };

  if (gettimeofday(&curtime, NULL)==-1)
    throw exception("Unable to get current time\n");

  target += curtime;			// schedule relative to current time
  return timers.schedule(target, handler);
}

int eventloop::schedule(long first_sec, long first_usec, long period_sec,
                        long period_usec, timerhandler *handler) {
  timeval curtime;
  timeval target = { first_sec + first_usec/1000000, first_usec % 1000000 };
  timeval period = { period_sec + period_sec/1000000, period_usec % 1000000 };

  if (gettimeofday(&curtime, NULL)==-1)
    throw exception("Unable to get current time\n");

  target += curtime;			// schedule relative to current time
  return timers.schedule(target, period, handler);
}

