/* 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 <map>
#include <set>

#include "CallMgr.h"
#include "Io.h"
#include "IoCallee.h"
#include "Time.h"
#include "TimeCallee.h"

namespace Blinker {

/// constructor
CallMgr::CallMgr()
{
}

/// destructor
CallMgr::~CallMgr()
{
}

/**
 * @brief cancel callback at read I/O event
 * @param[in] callee whom not to call
 * @param[in] io I/O object not to monitor for readability any more
 */
void CallMgr::cancelIoReadCall(IoCallee *callee, Io *io)
{
  // remove callee for this I/O object
  IoCallees &callees = m_iosRead[io];
  callees.erase(callee);

  // no more callees -> remove I/O object
  if (callees.empty())
    m_iosRead.erase(io);
}

/**
 * @brief request callback at read I/O event
 * @param[in] callee whom to call
 * @param[in] io I/O object to monitor for readability
 */
void CallMgr::requestIoReadCall(IoCallee *callee, Io *io)
{
  m_iosRead[io].insert(callee);
}

/**
 * @brief cancel callback at write I/O event
 * @param[in] callee whom not to call
 * @param[in] io I/O object not to monitor for writability any more
 */
void CallMgr::cancelIoWriteCall(IoCallee *callee, Io *io)
{
  // remove callee for this I/O object
  IoCallees &callees = m_iosWrite[io];
  callees.erase(callee);

  // no more callees -> remove I/O object
  if (callees.empty())
    m_iosWrite.erase(io);
}

/**
 * @brief request callback at write I/O event
 * @param[in] callee whom to call
 * @param[in] io I/O object to monitor for writability
 */
void CallMgr::requestIoWriteCall(IoCallee *callee, Io *io)
{
  m_iosWrite[io].insert(callee);
}

/**
 * @brief cancel callback at certain time
 * @param[in] callee whom not to call
 */
void CallMgr::cancelTimeCall(TimeCallee *callee)
{
  // find time a call is registered at
  TimeCalleeMap::iterator itCallee;
  itCallee = m_timeCallees.find(callee);
  if (itCallee == m_timeCallees.end())
    return; // no call registered
  const Time &time = itCallee->second;

  // remove registered call
  m_times[time].erase(callee);
  m_timeCallees.erase(itCallee);
}

/**
 * @brief request callback at certain time
 * @param[in] callee whom to call
 * @param[in] time when to call
 *
 * this cancels a previous time call request from callee
 */
void CallMgr::requestTimeCall(TimeCallee *callee, const Time &time)
{
  // cancel previous call
  cancelTimeCall(callee);

  // register call at new time
  m_times[time].insert(callee);
  m_timeCallees[callee] = time;
}

/// run call manager
void CallMgr::run()
{
  while (!m_times.empty() || !m_iosRead.empty() || !m_iosWrite.empty()) {

    // get time until first call time
    Time timeout;
    if (!m_times.empty()) {
      timeout = m_times.begin()->first - Time::now();
    } else {
      timeout.fromMs(100);
    }

    // time already passed or reached
    if (timeout <= Time::zero) {

      // get time callees and remove entry from time map
      TimeCallees timeCallees = m_times.begin()->second;
      m_times.erase(m_times.begin());

      // call time callees
      TimeCallees::const_iterator itTC;
      for (itTC = timeCallees.begin(); itTC != timeCallees.end(); ) {
        TimeCallees::const_iterator itTCE = itTC; // save iterator for erase
        TimeCallee *tc = *itTC; // save pointer to time callee for callback
        itTC++; // advance loop iterator to next element
        m_timeCallees.erase(*itTCE); // first remove request from callee map
        tc->timeCall(); // then call callee
      }

    } // if timeout

    // wait for I/O events
    IoMap::const_iterator itM;
    Io::Set rd, wr;
    for (itM = m_iosRead.begin(); itM != m_iosRead.end(); ++itM)
      rd.insert(itM->first);
    for (itM = m_iosWrite.begin(); itM != m_iosWrite.end(); ++itM)
      wr.insert(itM->first);
    Io::wait(rd, wr, timeout);

    // call I/O callees with pending events
    Io::Set::const_iterator itS;
    IoCallees::const_iterator itIC;
    for (itS = rd.begin(); itS != rd.end(); ++itS) {
      const IoCallees &ioCallees = m_iosRead[*itS];
      for (itIC = ioCallees.begin(); itIC != ioCallees.end(); ++itIC)
        (*itIC)->ioReadCall(*itS);
    }
    for (itS = wr.begin(); itS != wr.end(); ++itS) {
      const IoCallees &ioCallees = m_iosWrite[*itS];
      for (itIC = ioCallees.begin(); itIC != ioCallees.end(); ++itIC)
        (*itIC)->ioWriteCall(*itS);
    }

  } // while m_times m_iosRead m_iosWrite
}

} // namespace Blinker

