/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#include <set>
#include <sys/select.h>

#include "Io.h"
#include "Time.h"

namespace Blinker {

/**
 * @brief wait for I/O events
 * @param[in] read I/O objects to check for readability
 * @param[out] read I/O objects that are readable
 * @param[in] write I/O objects to check for writability
 * @param[out] write I/O objects that are writable
 * @param[in] timeout maximum time to wait
 */
void Io::wait(Set &read, Set &write, const Time &timeout)
{
  static const Time maxTimeout(1); // maximum timeout for I/O wait

  // get maximum file descriptor, read set and write set
  int max = 0;
  fd_set fd_rd;
  FD_ZERO(&fd_rd);
  for (Set::const_iterator it = read.begin(); it != read.end(); ++it) {
    if ((*it)->m_fd > max)
      max = (*it)->m_fd;
    FD_SET((*it)->m_fd, &fd_rd);
  }
  fd_set fd_wr;
  FD_ZERO(&fd_wr);
  for (Set::const_iterator it = write.begin(); it != write.end(); ++it) {
    if ((*it)->m_fd > max)
      max = (*it)->m_fd;
    FD_SET((*it)->m_fd, &fd_wr);
  }

  // get timeout
  struct timeval to;
  if (timeout < Time::zero) { // don't use negative timeout
    Time::zero.toTimeval(to);
  } else if (timeout > maxTimeout) { // stay responsive -> no more than max
    maxTimeout.toTimeval(to);
  } else {
    timeout.toTimeval(to);
  }

  // wait for I/O event
  fd_set fd_err;
  FD_ZERO(&fd_err);
  int res = select(max + 1, &fd_rd, &fd_wr, &fd_err, &to);

  // error or timeout
  if (res <= 0) {
    // return with empty sets
    read.clear();
    write.clear();
    return;
  }

  // remove file descriptors without event pending from sets
  Set::iterator it;
  it = read.begin();
  while (it != read.end()) {
    if (FD_ISSET((*it)->m_fd, &fd_rd)) {
      ++it;
    } else {
      Set::iterator del = it;
      ++it;
      read.erase(del);
    }
  } // while it
  it = write.begin();
  while (it != write.end()) {
    if (FD_ISSET((*it)->m_fd, &fd_wr)) {
      ++it;
    } else {
      Set::iterator del = it;
      ++it;
      write.erase(del);
    }
  } // while it
}

/// constructor
Io::Io():
  m_fd(-1)
{
}

} // namespace Blinker

