/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Roman Krenicky
 *
 * $Id: controller.cpp,v 1.24 2005/11/23 17:51:35 kutzner Exp $
 *
 */

/*
  controller.cpp
  created: 1.12.2004
  last change: 30.7.2005
*/
/*! @file
    the logic stuff is done here (i.e. decisions, when to do what, how to react
    on events, ...)
*/

#include "globalheader.h"

#include <config.h>
#include "controller.h"
#include "pluginname.h"
#include "misc.h"
extern "C"
{
  #include <time.h>
  #include <stdlib.h>
  #include <math.h>
}


//! minimum time gap which is considered a hole (in seconds)
#define  HOLESIZE             (30*60)     // 30 minutes
//! minimum time coverage the EPG data of a time block must have to be
//! considered complete
#define  MINCOVERAGE          (0.917)     // 22 hours a day
//! time which the controller waits before sending its first (own) request
//! (in seconds)
#define  INIT_REQ_DELAY             10*60
//! delay between two succeeding transmissions of unrequested data (in seconds)
#define  DATA_SPREAD_DELAY          (10*60)
//! no. of future EPG blocks to request, including the current one
#define  FUTURE_BLOCKS              2  // today and tomorrow
//! delay between two complete transmissions of pending [backoff-]level 0
//! requests (in seconds)
#define  REQUEST_DELAY              (30*60)
//! time between two bunches of requests for incomplete blocks (in seconds);
//! should be large enough to receive (most of) the responses to the requests
//! (for @c request_answer_size calculation)
#define  REQ_PERIOD                 20
//! minimum no. of requests in one bunch of requests for incomplete blocks
#define  REQ_PERIOD_MIN             1
//! maximum no. of requests in one bunch of requests for incomplete blocks
#define  REQ_PERIOD_MAX             20
//! maximum (over-all) bandwidth which should be used when doing requests;
//! i.e. try to do requests with such frequency, that the plugin's over-all
//! bandwidth is @c REQ_BANDWIDTH (in bytes per second)
#define  REQ_BANDWIDTH              1000
//! period by which the EPG data is scanned for holes (in seconds)
#define  HOLE_SEARCH_DELAY          (60*60)
//! delay between two succeeding updates (in seconds)
#define  UPDATE_DELAY               (24*60*60)
//! time between two bunches of update requests (in seconds);
//! should be large enough to receive (most of) the responses to the requests
//! (for @c update_answer_size calculation)
#define  UPD_PERIOD                 20
//! minimum no. of update requests in one bunch of an update
#define  UPD_PERIOD_MIN             1
//! maximum no. of update requests in one bunch of an update
#define  UPD_PERIOD_MAX             20
//! maximum (over-all) bandwidth which should be used when doing update
//! requests; i.e. try to do requests with such frequency, that the plugin's
//! over-all bandwidth is @c UPD_BANDWIDTH (in bytes per second)
#define  UPD_BANDWIDTH              1000
//! period, for which we limit the number of incoming requests (in seconds)
#define  INCOMING_REQUEST_PERIOD    60
//! maximum number of incoming requests we accept per @c INCOMING_REQUEST_PERIOD
#define  INCOMING_REQUEST_MAXIMUM   500
//! period, for which we limit the number of incoming data messages (in seconds)
#define  INCOMING_DATA_PERIOD       60
//! maximum number of incoming data messages we accept per @c INCOMING_DATA_PERIOD
#define  INCOMING_DATA_MAXIMUM      100
// INCOMING_DATA_MAXIMUM  >  INCOMING_DATA_PERIOD * REQ_PERIOD_MAX/REQ_PERIOD !
//! maximum length of the @c block_send_queue
#define  MAX_SEND_QUEUE_LENGTH      100
//! start value for @c request_answer_size (in Bytes)
#define  RAS_DEFAULT                5000
//! number of transmitted requests after which to update @c request_answer_size
#define  RAS_UPDATE_BASE            10
//! sliding average factor for new values to be added to @c request_answer_size
#define  RAS_AVERAGE_FACTOR         0.2  // ~ 1-(3rd root of 1/2)
//! start value for @c update_answer_size (in Bytes)
#define  UAS_DEFAULT                RAS_DEFAULT
//! number of transmitted update requests after which to update
//! @c update_answer_size
#define  UAS_UPDATE_BASE            RAS_UPDATE_BASE
//! sliding average factor for new values to be added to @c update_answer_size
#define  UAS_AVERAGE_FACTOR         RAS_AVERAGE_FACTOR
//! a rough estimation of a request (both regular and update) message's size
//! [!= 0] (in Bytes)
#define  ESTIMATED_REQUEST_SIZE     100





/*! @param init_epg_access  object, the @c controller shall access the global
           vdr EPG data with
    @param init_igor_access  object, the @c controller shall access the IGOR with
*/
controller::controller (vdrepg_interface &init_epg_access,
                        epgigor_interface &init_igor_access)
   :  cThread("igorepg - controller"),
      epg_access(init_epg_access), igor_access(init_igor_access)
{
   internal_request = ir_none;
   request_period_start = 0;
   incoming_request_count = 0;
   data_period_start = 0;
   incoming_data_count = 0;
   last_request_reset = 0;
   ras_answer_bytes = 0;
   ras_request_count = 0;
   uas_answer_bytes = 0;
   uas_request_count = 0;
   request_answer_size = RAS_DEFAULT;
   update_answer_size = UAS_DEFAULT;
   Start();
   igor_access.register_rcvr(this);
} // controller()


/*! just a destructor, stops the controller thread if it is still running
*/
controller::~controller (void)
{
   igor_access.deregister_rcvr(this);
   Cancel (0);
} // ~controller()


/*! @brief sends an internal request to the controller
    @param request  the request to send, @c ir_type::ir_none is ignored
    @note internal requests may not be buffered so that only the last one
          is seen
    @note this method returns rather fast
*/
void controller::controller_request (ir_type request)
{
   if (request == ir_none)
      return;

   internal_request_mutex.Lock();
   internal_request = request;
   internal_request_mutex.Unlock();
   // notify:
   internal_request_cond.Broadcast();
} // controller_request (...)


/*! @brief stops the controller thread if it is running
*/
void controller::Stop (void)
{
   Cancel (0);
} // Stop()


/*! @brief calculates the number of past blocks corresponding to a value of
            vdr's @c Setup.EPGLinger
    @param epglinger  a value of @c Setup.EPGLinger (i.e. the EPG past in
                      minutes)
    @return the maximum number of past blocks covered @em completely by
            @a epglinger at @em any time; 0 if @a epglinger is negative
*/
unsigned int controller::epglinger2past_blocks (int epglinger)
{
   unsigned int retval;

   if (epglinger <= 0)
      return 0;
   // 'epglinger' is in minutes !
   retval = ((unsigned)epglinger*60) / TIME_BLOCK_SIZE;
   // blocks where we still have the data even at the end of a day:
   if (retval)
      retval--;
   return retval;
} // epglinger2past_blocks (...)


/*! @brief enters all incomplete EPG blocks for transmission
    @param past_blocks  number of past EPG blocks to request
    @param future_blocks  number of future EPG blocks to request, including
                          the current one

    finds incomplete blocks and enters them into @c requested_blocks[0] unless
    they're already included somewhere in @c requested_blocks

    the new blocks will be actually requested for when @c transmit_requests()
    is called
*/
void controller::enter_incomplete_blocks
   (unsigned int past_blocks, unsigned int future_blocks)
{
   time_t epg_start;  // beginning of the earliest EPG data we want to have
   list<blockentry> *incomplete_blocks = NULL;
   unsigned int i;

   if (past_blocks+future_blocks == 0)
      return;  // nothing to do

   epg_start = time(NULL);  // now
   epg_start -= (epg_start % TIME_BLOCK_SIZE);  // normalise
   epg_start -= past_blocks*TIME_BLOCK_SIZE;  // into the past


   // get list of incomplete blocks (to request for):
   incomplete_blocks = epg_access.get_incomplete_blocks
                                      (epg_start, TIME_BLOCK_SIZE,
                                       past_blocks+future_blocks,
                                       HOLESIZE, MINCOVERAGE);
   if (incomplete_blocks == NULL)
      esyslog ("%s plugin: couldn't get list of incomplete EPG blocks",
               PLUGINNAME);
   else  // (incomplete_blocks != NULL)
   {
      /* copy each block from the list of incomplete blocks to the set of
         requests */
      bool contained_already;
      requested_blocks_mutex.Lock();
      while ( !incomplete_blocks->empty() )
      {
         // is the next block already contained in 'requested_blocks'(?):
         contained_already = false;
         for (i=0; i < REQUEST_BACKOFF_DEPTH+2; i++)
         {
            if ( requested_blocks[i].count(incomplete_blocks->front()) )
            {
               contained_already = true;
               break;
            } // if ( requested_blocks[i].count(incomplete_blocks->front()) )
         } // for (unsigned int i=0; i < REQUEST_BACKOFF_DEPTH+2; i++)

         if (!contained_already)
            requested_blocks[0].insert (incomplete_blocks->front());

         incomplete_blocks->pop_front();
      } // while ( !incomplete_blocks->empty() )
      requested_blocks_mutex.Unlock();

      delete incomplete_blocks;
   } // else  -> (incomplete_blocks != NULL)
} // enter_incomplete_blocks (...)


/*! @brief removes entries from @c requested_blocks which are out of date
    @param past_blocks  number of past EPG blocks that are up to date
    @param future_blocks  number of future EPG blocks that are up to date,
                          including the current one
*/
void controller::remove_obsolete_incomplete_blocks
   (unsigned int past_blocks, unsigned int future_blocks)
{
   time_t timeblock_start;  // the earliest timeblock up to date
   time_t timeblock_end;  // the first timeblock after the last one up to date
   set<blockentry>::const_iterator block_iter;

   timeblock_start = time(NULL);  // now
   timeblock_start -= (timeblock_start % TIME_BLOCK_SIZE);  // normalise
   timeblock_end = timeblock_start;
   timeblock_start -= past_blocks*TIME_BLOCK_SIZE;  // into the past
   // into the future (note that future_blocks==0 means blocks up to the
   // previous one, i.e. excludes the current one):
   timeblock_end += future_blocks*TIME_BLOCK_SIZE;

   // find and remove obsolete entries:
   requested_blocks_mutex.Lock();
   for (unsigned int i=0; i < REQUEST_BACKOFF_DEPTH+2; i++)
   {
      block_iter = requested_blocks[i].begin();
      while (block_iter != requested_blocks[i].end())
      {
         // obsolete(?):
         if ( (block_iter->get_start_time() < timeblock_start) ||
              (block_iter->get_start_time() >= timeblock_end) )
         {
            requested_blocks[i].erase (block_iter);
            // unfortunately erasing invalidates our iterator:
            block_iter = requested_blocks[i].begin();
            continue;
         } // if ( (block_iter->get_start_time() < timeblock_start) || ... )
         block_iter++;
      } // while (block_iter != requested_blocks[i].end())
   } // for (unsigned int i=0; i < REQUEST_BACKOFF_DEPTH+2; i++)
   requested_blocks_mutex.Unlock();
} // remove_obsolete_incomplete_blocks (...)


/*! @brief removes definitely unsatisfied requests from @c requested_blocks

    removes unsatisfied requests from @c requested_blocks which won't be
    retransmitted again (because they have reached the maximum backoff level)

    @param past_blocks  number of past EPG blocks that are to be removed (and
                        all later ones)
*/
void controller::remove_unsatisfied_blocks (unsigned int past_blocks)
{
   /* INTERNAL NOTE: we don't move the blocks into requested_blocks[0] because
                     the EPG data may have been completed already (and we just
                     didn't notice) */

   time_t reset_limit;  // blocks for 'reset_limit' or later are removed
   set<blockentry>::const_iterator block_iter;

   reset_limit = time (NULL);  // now
   reset_limit -= (reset_limit % TIME_BLOCK_SIZE);  // normalise
   reset_limit -= past_blocks*TIME_BLOCK_SIZE;  // into the past

   // do the reset:
   requested_blocks_mutex.Lock();
   block_iter = requested_blocks[REQUEST_BACKOFF_DEPTH+1].begin();
   while (block_iter != requested_blocks[REQUEST_BACKOFF_DEPTH+1].end())
   {
      // candidate(?):
      if (block_iter->get_start_time() >= reset_limit)
      {
         requested_blocks[REQUEST_BACKOFF_DEPTH+1].erase (block_iter);
         // unfortunately erasing invalidates our iterator:
         block_iter = requested_blocks[REQUEST_BACKOFF_DEPTH+1].begin();
         continue;
      } // if (block_iter->get_start_time() >= reset_limit)
      block_iter++;
   } // while (block_iter != requested_blocks[...].end())
   requested_blocks_mutex.Unlock();
} // remove_unsatisfied_blocks (...)


/*! @brief (re-)transmits requests
    @param depth  only requests which have been transmitted no more than
                  @a depth times are transmitted (so @a depth may range from
                  0 to @c REQUEST_BACKOFF_DEPTH, anything above is reduced to
                  @c REQUEST_BACKOFF_DEPTH)
    @note manipulates @c requested_blocks
*/
void controller::transmit_requests (unsigned int depth)
{
   timespec period_sleep;  // delay between two request bunches
   unsigned int request_count;  // requests in current bunch (i.e. period)
   // no. of requests sent in the last period:
   unsigned int requests_sent_last_time = 0;
   unsigned int no_of_requests_to_send = 0;  // in the next (current) period
   float rate;  // plugin's over-all data-rate (bytes per second)
   time_t period_start;  // when the current period has started
   float actual_period;  // duration of the just finished period (in seconds)
   blockentry next_block;  // next block to send
   set<blockentry>::const_iterator block_iter;

   period_sleep.tv_sec = REQ_PERIOD;  period_sleep.tv_nsec = 0;

   /* the last set of 'requested_blocks' is for requests which won't be
      retransmitted any more */

   // mind the maximum:
   if (depth > REQUEST_BACKOFF_DEPTH)
      depth = REQUEST_BACKOFF_DEPTH;

   // reset the byte counters to calculate the initial rate correctly:
   igor_access.reset_bytes_received();
   igor_access.reset_bytes_sent();
   period_start = time(NULL);

   /* travers 'requested_blocks' backwards, because we will move upwards the
      requests we sent */
   request_count = 0;
   while (true)
   {
      // transmit the set with index 'depth':
      while (true)
      {
         // have all requests of the current period been sent(?):
         if (request_count >= no_of_requests_to_send)
         {
            float request_rate;
            time_t now;
            nanosleep (&period_sleep, NULL);
            request_count = 0;

            // update request_answer_size if needed:
            if (ras_request_count >= RAS_UPDATE_BASE)
            {
               size_t new_value =
                  (size_t)(ras_answer_bytes/(unsigned long)ras_request_count);
               request_answer_size =
                 (size_t)(RAS_AVERAGE_FACTOR*(float)new_value
                          + (1-RAS_AVERAGE_FACTOR)*(float)request_answer_size);
               ras_answer_bytes = 0;
               ras_request_count = 0;
            } // if (ras_request_count >= RAS_UPDATE_BASE)

            requests_sent_last_time = no_of_requests_to_send;

            // calculate rate and start new period:
            now = time(NULL);
            if (period_start >= now)
               actual_period = (float)REQ_PERIOD;
            else
               actual_period = (float)(now-period_start);
            period_start = now;
            rate = ( (float)igor_access.reset_bytes_received()
                     + (float)igor_access.reset_bytes_sent() )
                   / actual_period;
            request_rate = next_request_rate (rate, (float)REQ_BANDWIDTH,
                             (float)requests_sent_last_time/actual_period,
                             /*regular*/ true);
            // TODO: overflow(?):
            no_of_requests_to_send =
                        (unsigned int)(request_rate*(float)REQ_PERIOD + 0.5);
            if (no_of_requests_to_send < REQ_PERIOD_MIN)
               no_of_requests_to_send = REQ_PERIOD_MIN;
            if (no_of_requests_to_send > REQ_PERIOD_MAX)
               no_of_requests_to_send = REQ_PERIOD_MAX;

            ras_request_count += no_of_requests_to_send;
         } // if (request_count >= no_of_requests_to_send)


         // get next block and move it upwards in 'requested_blocks[]':
         requested_blocks_mutex.Lock();
         block_iter = requested_blocks[depth].begin();
         if (block_iter == requested_blocks[depth].end())
         { // set empty
            requested_blocks_mutex.Unlock();
            break;
         } // if (block_iter == requested_blocks[depth].end())
         next_block = *block_iter;
         requested_blocks[depth+1].insert (*block_iter);
         requested_blocks[depth].erase (block_iter);
         requested_blocks_mutex.Unlock();

         // transmit request:
         if ( !igor_access.send_req(next_block.get_channel(),
                                   next_block.get_start_time()) )
         {
            if ( !igor_access.connected() )
               break;
            /* if we fail otherwise, we simply ignore it
               we WON'T move the blockentry backwards again in
               'requested_blocks[]', as this might result in requesting that
               block each time this method is called */
         } // if ( !igor_access.send_req(...) )

         request_count++;
      } // while (true)
      if ( !igor_access.connected() )
         break;
      if (!depth)
         break;
      depth--;
   } // while (true)
} // transmit_requests (...)


/*! @brief sends out a block of EPG data

    if there are pending messages in the @c block_send_queue, the first one is
    selected, otherwise some block from the global epg-data-pool is picked

    @param seed  the last block from the global database sent, will be changed
                 if a new one is sent; it's not necessary, that the block
                 still exists in the database (or ever has); initially @a seed
                 should have an empty channel entry and a valid block
                 starttime
    @note call @c srand() before the first call to this method, otherwise all
          igorepg-plugins started at (broadly) the same time will choose to send
          approximately the same blocks at approximately the same time
*/
void controller::send_a_block (blockentry *seed)
{
   blockentry block_to_send;
   epgdata *data = NULL;
   bool queue_was_empty = true;

   // check the send queue first:
   block_send_queue_mutex.Lock();
   if ( !block_send_queue.empty() )
   {
      queue_was_empty = false;
      block_to_send = block_send_queue.front();
      block_send_queue.pop_front();
   } // if ( !block_send_queue.empty() )
   block_send_queue_mutex.Unlock();


   // fetch the data to send:
   if ( !queue_was_empty )
   {
      data = epg_access.get_epg (block_to_send.get_channel(),
                            block_to_send.get_start_time(),
                            block_to_send.get_start_time()+TIME_BLOCK_SIZE,
                            false);
   } // if ( !queue_was_empty )
   else if (seed != NULL)
   { // pick the next block from the global database
      data = epg_access.get_next_complete_block (*seed, TIME_BLOCK_SIZE, seed,
                                                 HOLESIZE, MINCOVERAGE);
      block_to_send = *seed;
   } // else if (seed != NULL)


   // send the selected block:
   if (data == NULL)
   {
      esyslog ("%s plugin: failed to fetch EPG data", PLUGINNAME);
      return;
   } // if (data == NULL)
   igor_access.send_data (block_to_send.get_channel(),
                         block_to_send.get_start_time(), *data);
   delete data;  // don't forget !!!
} // send_a_block()


/*! @brief performs an update of the EPG data
    @param past_blocks  number of past EPG blocks to request
    @param future_blocks  number of future EPG blocks to request, including
                          the current one

    sends out update requests
*/
void controller::update (unsigned int past_blocks, unsigned int future_blocks)
{
   time_t timeblock_start;  // the earliest timeblock to request
   time_t timeblock_end;  // the first timeblock after the last one to request
   time_t timeblock;  // timeblock to request next
   list<epgchannel> *channels;
   list<epgchannel>::iterator channel;  // channel to request next
   timespec period_sleep;  // delay between two request bunches
   unsigned int request_count = 0;  // requests in current bunch (i.e. period)
   // no. of requests sent in the last period:
   unsigned int requests_sent_last_time = 0;
   unsigned int no_of_requests_to_send = 0;  // in the next (current) period
   float rate;  // plugin's over-all data-rate (bytes per second)
   time_t period_start;  // when the current period has started
   float actual_period;  // duration of the just finished period (in seconds)

   if (past_blocks+future_blocks == 0)
      return;  // nothing to do

   period_sleep.tv_sec = UPD_PERIOD;  period_sleep.tv_nsec = 0;

   timeblock_start = time(NULL);  // now
   timeblock_start -= (timeblock_start % TIME_BLOCK_SIZE);  // normalise
   timeblock_end = timeblock_start;
   timeblock_start -= past_blocks*TIME_BLOCK_SIZE;  // into the past
   // into the future (note that future_blocks==0 means blocks up to the
   // previous one, i.e. excludes the current one):
   timeblock_end += future_blocks*TIME_BLOCK_SIZE;

   // get the channels to send requests for:
   channels = epg_access.get_channels();
   if (channels == NULL)
   {
      esyslog ("%s plugin: couldn't get list of channels", PLUGINNAME);
      return;
   } // if (channels == NULL)

   // reset the byte counters to calculate the initial rate correctly:
   igor_access.reset_bytes_received();
   igor_access.reset_bytes_sent();
   period_start = time(NULL);

   // traverse list of channels:
   for (channel=channels->begin(); channel != channels->end(); channel++)
   {
      // requests the timeblocks for this channel:
      for (timeblock=timeblock_start; timeblock < timeblock_end;
           timeblock += TIME_BLOCK_SIZE)
      {
         // have all requests of the current period been sent(?):
         if (request_count >= no_of_requests_to_send)
         {
            float request_rate;
            time_t now;
            nanosleep (&period_sleep, NULL);
            request_count = 0;

            // update update_answer_size if needed:
            if (uas_request_count >= UAS_UPDATE_BASE)
            {
               size_t new_value =
                  (size_t)(uas_answer_bytes/(unsigned long)uas_request_count);
               update_answer_size =
                 (size_t)(UAS_AVERAGE_FACTOR*(float)new_value
                          + (1-UAS_AVERAGE_FACTOR)*(float)update_answer_size);
               uas_answer_bytes = 0;
               uas_request_count = 0;
            } // if (uas_request_count >= UAS_UPDATE_BASE)

            requests_sent_last_time = no_of_requests_to_send;

            // calculate rate and start new period:
            now = time(NULL);
            if (period_start >= now)
               actual_period = (float)UPD_PERIOD;
            else
               actual_period = (float)(now-period_start);
            period_start = now;
            rate = ( (float)igor_access.reset_bytes_received()
                     + (float)igor_access.reset_bytes_sent() )
                   / actual_period;
            request_rate = next_request_rate (rate, (float)UPD_BANDWIDTH,
                             (float)requests_sent_last_time/actual_period,
                             /*regular*/ false);
            // TODO: overflow(?):
            no_of_requests_to_send =
                        (unsigned int)(request_rate*(float)UPD_PERIOD + 0.5);
            if (no_of_requests_to_send < UPD_PERIOD_MIN)
               no_of_requests_to_send = UPD_PERIOD_MIN;
            if (no_of_requests_to_send > UPD_PERIOD_MAX)
               no_of_requests_to_send = UPD_PERIOD_MAX;

            uas_request_count += no_of_requests_to_send;
         } // if (request_count >= no_of_requests_to_send)

         // transmit request:         
         if ( !igor_access.send_req(*channel, timeblock, /*update*/true) )
         {
            if ( !igor_access.connected() )
               break;
         } // if ( !igor_access.send_req(*channel, timeblock, /*update*/true) )

         request_count++;
      } // for (timeblock=timeblock_start; timeblock < timeblock_end;
      if ( !igor_access.connected() )
         break;
   } // for (channel=channels->begin(); channel != channels->end(); channel++)
   delete channels;
} // update (...)


/*! @brief calculates the number of requests to send per second
    @param data_rate  the plugin's current rate in bytes per second
    @param bandwidth  the bandwidth to use (in bytes per second)
    @param current_request_rate  the number of requests per second sent in the
                near past (and thus contributing to @a data_rate)
    @param regular  @c true if the rate of regular requests is requested fo,
                    @c false to get the rate of update requests
    @return number of requests per second to send in the near future, so that
            the plugin's over-all rate reaches @a bandwidth (estimated; taking
            into account the responses, too) [>= 0]
*/
float controller::next_request_rate (float data_rate, float bandwidth,
                                     float current_request_rate, bool regular)
{
   // all data transfered per request (including responses etc.):
   float estimated_request_overhead;
   float unused_bandwidth;  // may be negative
   float request_rate_to_use_additionally;  // may be negative
   float retval;
   // if all traffic is caused by requests and responses only:
   float request_rate_limit;


   if (regular)
      // we receive a response and send it out again immediately:
      estimated_request_overhead = (float)ESTIMATED_REQUEST_SIZE
                                   + 2*(float)request_answer_size;
   else // update
      // we receive a response but don't send it out again:
      estimated_request_overhead = (float)ESTIMATED_REQUEST_SIZE
                                   + (float)update_answer_size;

   /* If we receive no responses for some time (and thus the rate stays below
      the bandwidth we try to reach), the number of requests sent will grow
      each time. We limit it here, so that the estimated traffic it causes
      doesn't exceed the bandwidth. */
   request_rate_limit = bandwidth / estimated_request_overhead;


   // the following (2) values are negative if data_rate > bandwidth:
   unused_bandwidth = bandwidth - data_rate;
   request_rate_to_use_additionally =
                           unused_bandwidth / estimated_request_overhead;

   retval = current_request_rate + request_rate_to_use_additionally;
   if (retval < 0)
      retval = 0;
   else if (retval > request_rate_limit)
      retval = request_rate_limit;


   return retval;
} // next_request_rate (...)


/*! @brief performs the main actions of the plugin

    This method runs and controls the main (active) tasks of the plugin (active
    means: excluding reactions on incoming messages).

    @note this method @em never @em returns
*/
void controller::Action (void)
{
   // perform the thread's activity
   // (mainly requesting lacking data and spreading the present one)
   timespec sleeptime;
   // last block sent (apart from sending queue):
   blockentry last_block;  // empty
   // the value of Setup.EPGLinger when the last requests have been sent:
   int last_epglinger = Setup.EPGLinger;
   // remaining delays until next action in milliseconds(!):
   enum { _data_index=0, _request_index, _hole_index, _update_index };
   int remaining_delay[] = { DATA_SPREAD_DELAY*1000, REQUEST_DELAY*1000,
                             HOLE_SEARCH_DELAY*1000, UPDATE_DELAY*1000 };
   unsigned int delays_count =
                        sizeof(remaining_delay)/sizeof(remaining_delay[0]);
   int delay;  // next delay in ms
   unsigned int request_counter = 0;  // no. of request cycles
   unsigned int request_level;  // request level for next request cycle
   // indicates, whether an EPG scan has been requested (via ir_scan):
   bool do_scan;
   unsigned int i;


   // initialise the pseudo-random number generator:
   srand ((unsigned int)time(NULL));


   /* wait for INIT_REQ_DELAY seconds before starting to send requests for
      incomplete blocks:
   */
   /* NOTE: we don't use the second parameter here, because we would have to
            use errno as well and that's no good idea in multi-threaded
            programs
   */
   sleeptime.tv_sec = INIT_REQ_DELAY;  sleeptime.tv_nsec = 0;
   nanosleep (&sleeptime, NULL);
 

   // send requests for all incomplete EPG blocks:
   last_epglinger = Setup.EPGLinger;
   enter_incomplete_blocks (epglinger2past_blocks(last_epglinger),
                            FUTURE_BLOCKS);
   if ( igor_access.connected() )
   {
      transmit_requests (0);
      request_counter++;
   } // if ( igor_access.connected() )

   // main loop:
   while (true)
   {
      // keep communicating with the IGOR:
      while (true)
      {
         // check connection:
         if ( !igor_access.connected() )
         {
            esyslog ("%s plugin: not connected to IGOR", PLUGINNAME);
            igor_access.deregister_rcvr(this);
            break;
         } // if ( !igor_access.connected() )

         // find the minimum remaining delay:
         delay = remaining_delay[0];
         for (i=1; i < delays_count; i++)
            if ( remaining_delay[i] < delay )
               delay = remaining_delay[i];

         // delay (and wait for internal requests):
            /* unfortunately TimedWait produces an overflow if we request
               a timeout of more than 2146483 ms (as of vdr v.1.3.23), so
               we have to split up large sleep times */
            int next_delay;  // the next sleeptime
            int delay_remaining = delay;  // sleeptime left

         do_scan = false;
         while (delay_remaining)
         {
            ir_type request;

            if (delay_remaining > 2000000)
            {
               next_delay = 2000000;
               delay_remaining -= 2000000;
            } // if (delay_remaining > 2000000)
            else
            {
               next_delay = delay_remaining;
               delay_remaining = 0;
            } // else

            internal_request_mutex.Lock();
            // check for pending request:
            if (internal_request == ir_none)
               internal_request_cond.TimedWait (internal_request_mutex,
                                                next_delay);
            request = internal_request;
            internal_request = ir_none;
            internal_request_mutex.Unlock();
            // evaluate request:
            if (request != ir_none)
            {
               switch (request)
               {
                  case ir_scan:
                        do_scan = true;
                        delay = 0;  // we ignore the time we slept
                        break;
                  case ir_reconnect:  // nothing to do here
                  case ir_none:  // only for the compiler
                        break;
               } // switch (request)
               if (do_scan)
                  break;

               // we repeat the sleep:
               delay_remaining += next_delay;
            } // if (request != ir_none)
         } // while (delay_remaining)

         // update remaining delays (note that this may be wrong if an internal
         // request has arrived):
         for (i=0; i < delays_count; i++)
            remaining_delay[i] = remaining_delay[i] - delay;

         // check connection:
         if ( !igor_access.connected() )
         {
            esyslog ("%s plugin: not connected to IGOR", PLUGINNAME);
            igor_access.deregister_rcvr(this);
            break;
         } // if ( !igor_access.connected() )

         // has Setup.EPGLinger changed (?):
         if (Setup.EPGLinger != last_epglinger)
         {
            int new_epglinger = Setup.EPGLinger;

            // has our past increased or decreased(?):
            if (new_epglinger > last_epglinger)
            {
               // look for further holes (our past has increased):
               enter_incomplete_blocks (epglinger2past_blocks(new_epglinger),
                                        FUTURE_BLOCKS);
            } // if (new_epglinger > last_epglinger)
            else if (new_epglinger < last_epglinger)
            {
               // drop obsolete requests (our past has decreased):
               remove_obsolete_incomplete_blocks
                        (epglinger2past_blocks(new_epglinger), FUTURE_BLOCKS);
            } // else if (new_epglinger < last_epglinger)

            last_epglinger = new_epglinger;
         } // if (Setup.EPGLinger != last_epglinger)


         // do actions for timers which have elapsed (in some nice order,
         // if several timers have elapsed):
         if ( !remaining_delay[_update_index] )
         {
            // do an EPG update:
            update (epglinger2past_blocks(last_epglinger), FUTURE_BLOCKS);
            remaining_delay[_update_index] = UPDATE_DELAY*1000;
         } // if ( !remaining_delay[_update_index] )
            // NOTE: do_scan requires to search holes BEFORE sending requests
         if ( !remaining_delay[_hole_index] || do_scan )
         {
            time_t now = time (NULL);  // just what it says

            // reset unsatisfied future requests for retransmission sometimes:
               // reset period start if it is in the future:
            if (last_request_reset > now)
               last_request_reset = 0;
               // reset requests if period is exceeded:
            if (now - last_request_reset >= TIME_BLOCK_SIZE)
            {
               last_request_reset = now;
               remove_unsatisfied_blocks (1);  // yesterday, ...
            } // if (now - last_request_reset >= TIME_BLOCK_SIZE)


            // remove obsolete entries:
            remove_obsolete_incomplete_blocks
                        (epglinger2past_blocks(last_epglinger), FUTURE_BLOCKS);
            // look for holes in EPG-data again:
            enter_incomplete_blocks (epglinger2past_blocks(last_epglinger),
                                     FUTURE_BLOCKS);
            remaining_delay[_hole_index] = HOLE_SEARCH_DELAY*1000;
         } // if ( !remaining_delay[_hole_index] || do_scan )
            // NOTE: do_scan requires to search holes BEFORE sending requests
         if ( !remaining_delay[_request_index] || do_scan )
         {
            // we implement backoff by counting the number of consecutive least
            // significant 1-bits of the counter
            unsigned int tmp = request_counter;
            for (request_level=0; tmp & 1; request_level++)
               tmp >>= 1;
            // if we have to do a scan, we need _all_ the data:
            if (do_scan)
               request_level = REQUEST_BACKOFF_DEPTH;
            transmit_requests (request_level);
            request_counter++;
            if (request_level == REQUEST_BACKOFF_DEPTH)
               request_counter = 0;
            remaining_delay[_request_index] = REQUEST_DELAY*1000;
         } // if ( !remaining_delay[_request_index] || do_scan )
         if ( !remaining_delay[_data_index] )
         {
            // send some piece of EPG data:
            send_a_block (&last_block);
            remaining_delay[_data_index] = DATA_SPREAD_DELAY*1000;
         } // if ( !remaining_delay[_data_index] )
      } // while (true)


      // we are not connected to the IGOR here

      // wait for internal requests (there's nothing else to do)
      while (true)
      {
         ir_type request;

         internal_request_mutex.Lock();
         // check for pending request:
         if (internal_request == ir_none)
            internal_request_cond.Wait (internal_request_mutex);
         request = internal_request;
         internal_request = ir_none;
         internal_request_mutex.Unlock();

         // evaluate request:
         switch (request)
         {
            case ir_reconnect:
                  // try to reconnect:
                  if (igor_access.reconnect())
                     igor_access.register_rcvr (this);
                  break;
            case ir_scan:  // nothing to do here
            case ir_none:  // just for the compiler
                  break;
         } // switch (request)
         // if we have a connection again, we may leave this part:
         if (igor_access.connected())
            break;
      } // while (true)
   } // while (true)
} // Action()


// inherited documentation
void controller::receive_data (const epgchannel &channel, time_t timeblock,
                               bool update, const epgdata &data, bool final)
{
   bool isnew;

   /*dsyslog ("%s plugin: received data for channel %s timeblock %lu",
            PLUGINNAME, channel.c_str(), (unsigned long)timeblock);*/

   // allow only a certain no. of data messages per time:
      // check period first (exceeded?):
   time_t now = time(NULL);
      // reset period start if it is in the future:
   if (data_period_start > now)
      data_period_start = 0;
   if (now - data_period_start >= INCOMING_DATA_PERIOD)
   {
      data_period_start = now;
      incoming_data_count = 0;
   } // if (now - data_period_start >= INCOMING_DATA_PERIOD)
      // check data message count (can we accept another data message?):
   if (incoming_data_count >= INCOMING_DATA_MAXIMUM)
   {
      esyslog ("%s plugin: received too many data messages, "
               "ignoring some of them", PLUGINNAME);
      return;  // ignore data message
   } // if (incoming_data_count >= INCOMING_DATA_MAXIMUM)
      // we are goning to accept this data message:
   incoming_data_count++;


   // don't enter the EPG data, if the channel is unknown to us (unless we're
   // the root node), but rather send it on immediately:
   if ( !final && !update && !epg_access.channel_is_present(channel) )
   {
      /*dsyslog ("%s plugin: sending on data for timeblock %lu",
               PLUGINNAME, (unsigned long)timeblock);*/
      igor_access.send_on_last_received_message();
      return;
   } // if ( !final && !update && !epg_access.channel_is_present(channel) )


   // store the data and check, whether it's new information for us:
   if ( !epg_access.put_epg(channel, data, &isnew) )
   {
      esyslog ("%s plugin: failed to insert EPG data", PLUGINNAME);
      isnew = false;
   } // if ( !epg_access.put_epg(channel, data, &isnew) )

   /* if this is the response to a [regular] request we performed, send
      the data immediately again (we rely on our logged information here,
      not on the 'mode' entry coming from outside):
   */
   if (final && !update)
   {
      set<blockentry>::const_iterator block_iter;
      bool was_requested_for = false;

      // is this block contained in 'requested_blocks'? :
      requested_blocks_mutex.Lock();
      for (unsigned int i=0; i < REQUEST_BACKOFF_DEPTH+2; i++)
      {
         block_iter = requested_blocks[i].find(blockentry(channel,timeblock));
         if (block_iter != requested_blocks[i].end())
         {
            was_requested_for = true;
            requested_blocks[i].erase (block_iter);
            break;  // each block is contained only once
         } // if (block_iter != requested_blocks[i].end())
      } // for (unsigned int i=0; i < REQUEST_BACKOFF_DEPTH+2; i++)
      requested_blocks_mutex.Unlock();
      if (was_requested_for)
      {
         ras_answer_bytes += data.data.str().length();
         igor_access.send_data (channel, timeblock, data);
      } // if (was_requested_for)
   } // if (final && !update)
   else if (update)
      uas_answer_bytes += data.data.str().length();


   /* if this has been new information for us, put it into the send queue, to
      send it out again (unless we would be the destination or this is an
      update response [to avoid update ping-pong]):
   */
   if ( isnew && !final && !update )
   {
      block_send_queue_mutex.Lock();
      if (block_send_queue.size() < MAX_SEND_QUEUE_LENGTH)
         block_send_queue.push_back (blockentry(channel,timeblock));
      block_send_queue_mutex.Unlock();
   } // if ( isnew && !final && !update )
} // receive_data (...)


// inherited documentation
void controller::receive_req (const epgchannel &channel, time_t timeblock,
                              bool update, const sa_igor &requestor, bool final)
{
   epgdata *data = NULL;

   /*dsyslog ("%s plugin: received request for channel %s timeblock %lu",
            PLUGINNAME, channel.c_str(), (unsigned long)timeblock);*/

   // allow only a certain no. of requests per time:
      // check period first (exceeded?):
   time_t now = time(NULL);
      // reset period start if it is in the future:
   if (request_period_start > now)
      request_period_start = 0;
   if (now - request_period_start >= INCOMING_REQUEST_PERIOD)
   {
      request_period_start = now;
      incoming_request_count = 0;
   } // if (now - request_period_start >= INCOMING_REQUEST_PERIOD)
      // check request count (can we accept another request?):
   if (incoming_request_count >= INCOMING_REQUEST_MAXIMUM)
   {
      esyslog ("%s plugin: received too many requests, ignoring some of them",
               PLUGINNAME);
      return;  // ignore request
   } // if (incoming_request_count >= INCOMING_REQUEST_MAXIMUM)
      // we are goning to accept this request:
   incoming_request_count++;


   // if we're not the root node (i.e. !final) and this is no update request
   // (i.e. !update), we send complete blocks (relative to HOLESIZE and
   // MINCOVERAGE) as responses only:
   data = epg_access.get_epg (channel, timeblock, timeblock+TIME_BLOCK_SIZE,
                              !final && !update, HOLESIZE, MINCOVERAGE);
      // we don't want empty data:
   if ( (data != NULL) && data->data.str().empty() )
      {  delete data; data = NULL;  }

   if (data != NULL)
   {
      /*dsyslog ("%s plugin: sending timeblock %lu as response", PLUGINNAME,
               (unsigned long)timeblock);*/
      igor_access.send_resp (channel, timeblock, update, *data, requestor);
      delete data;  // don't forget !!!
      data = NULL;
   } // if (data != NULL)
   else  // (data == NULL)
   {
      esyslog ("%s plugin: failed to fetch EPG data", PLUGINNAME);
      if (!final && !update)
      {
         /*dsyslog ("%s plugin: sending on request for timeblock %lu",
                  PLUGINNAME, (unsigned long)timeblock);*/
         igor_access.send_on_last_received_message();
      } // if (!final && !update)
   } // else  -> (data == NULL)
} // receive_req (...)
