/*
 *  ZThreads, a platform-independant, multithreading and 
 *  synchroniation library
 *
 *  Copyright (C) 2001, 2002 Eric Crahen, See LGPL.TXT for details
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *  SUNY @ Buffalo, hereby disclaims all copyright interest in the
 *  ZThreads library written by Eric Crahen
 */

#include "zthread/Timer.h"
#include "zthread/Guard.h"
#include "Time.h"

#include <assert.h>
#include <functional>

using namespace ZThread;
using namespace std;


TimerEvent::TimerEvent(const TimerEvent& e) 
 : _interval(e._interval), _timeExpired(e._timeExpired),_id(e._id), _periodic(e._periodic) {

}


TimerEvent::TimerEvent(unsigned long ms, unsigned long id, bool periodic)
 : _interval(ms), _id(id), _periodic(periodic) {
  
  _timeExpired = Timer::now() + ms;

}


TimerEvent& TimerEvent::operator=(const TimerEvent& e) {

  _id = e._id;
  _interval = e._interval;
  _timeExpired = e._timeExpired;
  _periodic = e._periodic;

  return *this;

}

unsigned long TimerEvent::getExpiration() const {

  unsigned long t = Timer::now();
  return (t >= _timeExpired) ? 0 : (_timeExpired - t);

}

bool TimerEvent::isExpired() const {
  return Timer::now() >= _timeExpired;
}



Timer::Timer(bool reuseKeys) throw(Synchronization_Exception) 
  : _reuseKeys(reuseKeys) {
}

Timer::~Timer() throw() {
}


/**
 * Insert a new event, using the key generator to create a new id. 
 */ 
unsigned long Timer::addEvent(unsigned long ms, bool periodic) 
  throw(Synchronization_Exception) {

  Guard<Lockable> g(_lock);

  // Get the next id
  unsigned long id;
  
  if(_reuseKeys && !_keys.empty()) {

    id = _keys.front();
    _keys.pop_front();

  } else id = _next++;

  _events.insert(TimerEvent(ms, id, periodic));
  _interrupt.signal();

  return id;

}


bool Timer::delEvent(unsigned long id) 
  throw(Synchronization_Exception) {

  Guard<Lockable> g(_lock);
  
  // Find the event, uses the implicit unsigned long cast on the TimerEvent
  EventList::iterator i = std::find(_events.begin(), _events.end(), id);
  if(i == _events.end())
    return false;
  
  // Remove it and wait for the event that will expire next.
  _events.erase(i);
  _interrupt.signal();

  // Return the key so it can be reused
  if(_reuseKeys)
    _keys.push_back(id);

  return true;

}

// Wait for an event
void Timer::run() throw () {

  try {

    Guard<Lockable> g(_lock);

    while(!isCanceled()) {
 
     try {

        // Wait for first event
        if(_events.begin() != _events.end()) { 

          TimerEvent event(*_events.begin());
          _interrupt.wait(_lock, event.getExpiration());

          // Distribute the notification
          notify(this, event);

          // Remove event from list, create a new one if periodic
          _events.erase(event);
          if(event.isPeriodic()) {

            event = TimerEvent(event.getInterval(), event.getId(), true);
            _events.insert(event);

          } else if(_reuseKeys) // Return the key so it can be reused
            _keys.push_back(event.getId()); 

        // Wait for an addition if there are no more events
        } else 
          _interrupt.wait(_lock); 


      } catch(Interrupted_Exception&) { 
        ZTDEBUG("Timer interrupted\n");
      } catch(Synchronization_Exception& e) { 
        ZTDEBUG("%s\n", e.what());
      }

    } /* while */

  } catch(...) {
    assert(0); // Unexpected synch exception
  }

}

unsigned long Timer::now() throw() {

  // Get the time relative to the time the library was loaded
  Time t = Time::now();
  return 1000*t.seconds() + t.milliseconds();

}
