#ifndef EVENT_LOOP_H
#define EVENT_LOOP_H


#include "Device.h"

#include <functional>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>


class Service
{
public:
  virtual Duration frequency() = 0;
  virtual void update(Duration a_dt) = 0;
};


class DeviceNotifierService : public Service
{
public:
  void registerDeviceNotifier(DeviceNotifier& device) {
    m_notifiers.push_back(device);
  }

  void update(Duration a_dt) override {
/*
    POLLIN   There is data to read.
    POLLPRI  There  is  urgent  data  to read
    POLLOUT  Writing now will not block.
    POLLERR  Error condition (output only).
    POLLHUP  Hang up (output only).
    POLLNVAL Invalid request: fd not open (output only).
*/

    WaitCanRead       = 1,
    WaitCanWrite      = 2,
    WaitForException  = 4,
    WaitForTimeout    = 8
    
    const int count = m_notifiers.size();
    struct pollfd fds[count];
    int timeoutMs = 0;

    for (int i = 0; i < count; i++) {
      auto notifier = m_notifiers[i];
      fds[i].fd = notifier.getDeviceHandle();
      fds[i].events = ((notifier.m_flags & WaitCanRead) ? POLLIN : 0) |
                      ((notifier.m_flags & WaitCanWrite) ? POLLOUT : 0);
      fds[i].revents = 0;

      // TODO: need to think about if the loop should never block and go flat out
      // if it can, or be limited by a rate which some task sets or if no task with
      // a timeout, then it will block indefinately until there is any i/o events ('input')
      // This trade off is one of power consumption vs speed
      if (notifier.m_flags & WaitTimeout) {
        if (!timeoutMs) {
          timeoutMs = notifier.m_timeout / 1000;
        } else {
          int milliSec = notifier.m_timeout / 1000;
          if (milliSec < timeoutMs)
            timeoutMs = milliSec;
        }
      }
    }

    int res = poll(fds, count, timeoutMs);

    if (res <= 0) {
      if (res == 0) { // timeout
      } else {
        // error, in errno
      }
    } else {
      for (int i = 0; i < count; i++) {
        auto notifier = m_notifiers[i];
        if (fds[i].revents & POLLIN)
          notifier.readyRead();
        if (fds[i].revents & POLLOUT)
          notifier.readyWrite();
        if (fds[i].revents & POLLERR)
          notifier.error();
        if (fds[i].revents & POLLHUP)
          notifier.closed();
        if (fds[i].revents & POLLNVAL)
          notifier.invalid();
      }
    }


/*
    fd_set rfds, wfds, xfds;
    struct timeval tv = { 0, 0 };
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&xfds);
    int highestFd = 0;
    for (auto notifier : m_notifiers) {
      int fd = notifier.getDeviceHandle();
      if (notifier.m_flags & (WaitCanRead | WaitCanWrite | WaitForException) && fd > highestFd)
        highestFd = fd;
      if (notifier.m_flags & WaitCanRead)
        FD_SET(fd, &rfds);
      if (notifier.m_flags & WaitCanWrite)
        FD_SET(fd, &wfds);
      if (notifier.m_flags & WaitForException)
        FD_SET(fd, &xfds);
      // TODO: merge timeouts
      // tv = min(tv, notifier.m_timeout)
    }
    int res = select(highestFd+1, &rfds, &wfds, &xfds, &tv);
    if (res == -1) {
      // error
    } else {
      if (res == 0) {
        // timeout
      } else {
        FD_ISSET(fd, &rfds)
        for (auto notifier : m_notifiers) {
          int fd = notifier.getDeviceHandle();
          if (notifier.m_flags & (WaitCanRead | WaitCanWrite | WaitForException) && fd > highestFd)
            highestFd = fd;
          if (notifier.m_flags & WaitCanRead)
            FD_SET(fd, &rfds);
          if (notifier.m_flags & WaitCanWrite)
            FD_SET(fd, &wfds);
          if (notifier.m_flags & WaitForException)
            FD_SET(fd, &xfds);
          // TODO: merge timeouts
          // tv = min(tv, notifier.m_timeout)
    }
      }
    }
*/
  }

  // Call very frequently
  Duration frequency() override { return Duration(0); }
private:
  std::vector<DeviceNotifier>  m_notifiers;
};




class EventLoop
{
public:
  EventLoop();
  ~EventLoop();

  // Works out dt, then calls update for all the registered services
  void update();
private:
};


#endif // EVENT_LOOP_H


