//
// timers.cc       : Timer Management Class
// authors         : John Heidemann, Fabio Silva and Alefiya Hussain 
//
// Copyright (C) 2000-2002 by the University of Southern California
// $Id: timers.cc,v 1.2 2002/11/26 22:45:40 haldar Exp $
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License,
// version 2, as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
//


#include <stdlib.h>
#include <stdio.h>

#include "timers.hh"

// Creates the Timer Management class. Creates the eventqueue The
// eventqueue is used by the Timer class only.  Use the nextTimer and
// executeNextTimer functions to access the event queue

TimerManager::TimerManager()
{
  struct timeval tv;

  // Initialize basic stuff
  next_handle_ = 1;
  GetTime(&tv);
  SetSeed(&tv);

  // Initialize event queue
#ifdef NS_DIFFUSION
  eq_ = new DiffEventQueue(this);
#else
  eq_ = new EventQueue;
#endif // NS_DIFFUSION

#ifdef USE_THREADS
  queue_mtx_ = new pthread_mutex_t;
  pthread_mutex_init(queue_mtx_, NULL);
#endif // USE_THREADS
}

// This function adds a timer to the event queue. The timeout provided
// should be in milliseconds. cb specifies the function that will be
// called.

handle TimerManager::addTimer(int timeout, TimerCallback *cb)
{
  TimerEntry *entry;

#ifdef USE_THREADS
  pthread_mutex_lock(queue_mtx_);
#endif // USE_THREADS
  entry = new TimerEntry(next_handle_, timeout, cb);
  eq_->eqAddAfter(next_handle_, entry, timeout);
  next_handle_++;

#ifdef USE_THREADS
  pthread_mutex_unlock(queue_mtx_);
#endif // USE_THREADS
  return entry->hdl_;
}

// Applications can use this function to remove from the eventqueue a
// previously scheduled timer (the handle should be the one returned
// by the AddTimer function)

bool TimerManager::removeTimer(handle hdl)
{
#ifdef NS_DIFFUSION
  if (eq_->eqRemove(hdl) == false){
    fprintf(stderr, "Error: Can't remove event from queue !\n");
    return false;
  }
  return true;
#else
  QueueEvent *e = NULL;
  TimerEntry *entry;

#ifdef USE_THREADS
  pthread_mutex_lock(queue_mtx_);
#endif // USE_THREADS

  // Find the timer in the queue
  e = eq_->eqFindEvent(hdl);

  // If timer found, remove it from the queue
  if (e){
    entry = (TimerEntry *) e->payload_;
    if (eq_->eqRemove(hdl) == false){
      fprintf(stderr, "Error: Can't remove event from queue !\n");
      exit(-1);
    }

    // Call the application provided delete function

    delete entry;
    delete e;
  }
  else{
#ifdef USE_THREADS
    pthread_mutex_unlock(queue_mtx_);
#endif // USE_THREADS
    return false;
  }

#ifdef USE_THREADS
  pthread_mutex_unlock(queue_mtx_);
#endif // USE_THREADS
  return true;
#endif // NS_DIFFUSION
}

// Returns the expiration value for the first timer on the queue
void TimerManager::nextTimerTime(struct timeval *tv)
{
#ifdef USE_THREADS
  pthread_mutex_lock(queue_mtx_);
#endif // USE_THREADS
  eq_->eqNextTimer(tv);
#ifdef USE_THREADS
  pthread_mutex_unlock(queue_mtx_);
#endif // USE_THREADS
}

// Executes the first timer callback in the queue 
#ifdef NS_DIFFUSION
void TimerManager::diffTimeout(DiffEvent *e)
{
  TimerEntry *entry = (TimerEntry *) e->payload();

  // run it
  int new_timeout = entry->cb_->expire();

  if (new_timeout >= 0){
    if (new_timeout > 0){
      // Change the timer's timeout
      entry->timeout_ = new_timeout;
    }
    eq_->eqAddAfter(entry->hdl_, (TimerEntry *) entry, entry->timeout_);
  }
  else{
    delete entry;
  }
  delete e;
}
#else
void TimerManager::executeNextTimer()
{
#ifdef USE_THREADS
  pthread_mutex_lock(queue_mtx_);
#endif // USE_THREADS
  QueueEvent *e = eq_->eqPop();
  TimerEntry *entry = (TimerEntry *) e->payload_;

#ifdef USE_THREADS
  pthread_mutex_unlock(queue_mtx_);
#endif // USE_THREADS
  // run it
  int new_timeout = entry->cb_->expire();

  if (new_timeout >= 0){
    if (new_timeout > 0){
      // Change the timer's timeout
      entry->timeout_ = new_timeout;
    }
#ifdef USE_THREADS
    pthread_mutex_lock(queue_mtx_);
#endif // USE_THREADS
    eq_->eqAddAfter(entry->hdl_, (TimerEntry *) entry, entry->timeout_);
#ifdef USE_THREADS
    pthread_mutex_unlock(queue_mtx_);
#endif // USE_THREADS
  }
  else{
    delete entry;
  }
  delete e;
}

void TimerManager::executeAllExpiredTimers()
{
  struct timeval tv;

  // Get next timer's timeout
  nextTimerTime(&tv);

  // Remove all expired timers from the top of the queue, calling
  // expire() for each one of them
  while (tv.tv_sec == 0 && tv.tv_usec == 0){
    // Timer at the head of the queue has expired
    executeNextTimer();
    nextTimerTime(&tv);
  }
}
#endif // NS_DIFFUSION
