/*
    File:       CTimer.cpp

    Contains:   Timer object


*/

#include "CTimer.h"
#include "IrDALog.h"

#if defined(hasTracing) && defined(hasCTimerTracing)

enum CTimerTraceCodes
{
    kLogNew = 1,
    kLogInit,
    kLogFree1,
    kLogFree2,
    kStartTimerDelay,
    kStartTimerSignal,
    
    kLogStopTimer1,
    kLogStopTimer2,
    kLogStopTimer3,
    kLogStopTimer4,
    kLogStopTimer5,
    
    kLogTimeout1,
    kLogTimeout2,
    kLogTimeout3,
    kLogTimeout4,
    kLogTimeout5,
    kLogGrimReaper
};

static
EventTraceCauseDesc TraceEvents[] = {
    {kLogNew,                   "CTimer: new, obj="},
    {kLogInit,                  "CTimer: init, fTimerSrc="},
    {kLogFree1,                 "CTimer: free, obj="},
    {kLogFree2,                 "CTimer: free, fTimerSrc="},
    {kStartTimerDelay,          "CTimer: Start timer, delay="},
    {kStartTimerSignal,         "CTimer: Start timer, signal=, new retain="},
    
    {kLogStopTimer1,            "CTimer: Stop timer, obj="},
    {kLogStopTimer2,            "CTimer: Stop timer, idle so nop.  retain stays="},
    {kLogStopTimer3,            "CTimer: Stop timer, cancel worked, retain before release="},
    {kLogStopTimer4,            "CTimer: Stop timer, cancel failed, retain still="},
    {kLogStopTimer5,            "CTimer: Stop timer, exit, retain count="},
    
    {kLogTimeout1,              "CTimer: timeout called, obj="},
    {kLogTimeout2,              "CTimer: timeout called, retain="},
    {kLogTimeout3,              "CTimer: timeout called, owner="},
    {kLogTimeout4,              "CTimer: timeout called, sender="},
    {kLogTimeout5,              "CTimer: timeout back from calling owner"},
    {kLogGrimReaper,            "CTimer: grim reaper"}
    
};
    #define XTRACE(x, y, z) IrDALogAdd ( x, y, z, TraceEvents, true )
#else
    #define XTRACE(x, y, z) ((void)0)
#endif


#define super OSObject
    OSDefineMetaClassAndStructors(CTimer, OSObject);
    
CTimer *
CTimer::cTimer(IOWorkLoop *work, OSObject *owner, Action callback)
{
    CTimer * obj = new CTimer;
    
    XTRACE(kLogNew, (int)obj >> 16, (short)obj);

    if (obj && !obj->init(work, owner, callback)) {
	obj->release();
	obj = nil;
    }
    return obj;
}

Boolean CTimer::init(IOWorkLoop *work, OSObject *owner, Action callback)
{
    IOReturn rc;
    
    fTimerSrc = nil;
    
    require(work, Failed);
    require(owner, Failed);
    require(callback, Failed);
	
    if (!super::init()) return false;
    
    fOwner    = owner;                  // save backpointer
    fCallback = callback;               // save the real callback, we're going to get it first
    fWorkLoop = work;                   // save workloop for free later
//  fTimerSrc = IOTimerEventSource::timerEventSource(owner, callback);      // make a timer
    fTimerSrc = IrDATimerEventSource::timerEventSource(this, &CTimer::timeout);
    require(fTimerSrc, Failed);
    
    fBusy = false;
    
    fGrimReaper = thread_call_allocate(grim_reaper, this);
    require(fGrimReaper, Failed);
	
    rc = work->addEventSource(fTimerSrc);
    require(rc == kIOReturnSuccess, Failed);
    
    XTRACE(kLogInit, (int)fTimerSrc >> 16, (short)fTimerSrc);
    return true;
	    
Failed:
    return false;
}

//
// free - this should now be impossible to have invoked while a timer is pending.
// 

void CTimer::free()
{
    XTRACE(kLogFree1, (int)this >> 16, (short)this);
    Boolean ok;
    
    check(fBusy == false);
    
    if (fGrimReaper) {
	ok = thread_call_cancel(fGrimReaper);
	check(ok == false);         // should *never* have work!
	thread_call_free(fGrimReaper);
	fGrimReaper = nil;
    }
    
    if (fTimerSrc) {
	XTRACE(kLogFree2, (int)fTimerSrc >> 16, (short)fTimerSrc);
	ok = fTimerSrc->SafeCancelTimeout();
	check(ok == false);         // should *not* have worked (no timer pending)
	
	fWorkLoop->removeEventSource(fTimerSrc);
	
	fTimerSrc->release();
	fTimerSrc = nil;
    }
	
    super::free();
}

//
// Start a timer to fire up after a while.
// Note the retain on self to prevent zombie
// timers getting invoked after they've been freed.
//

void CTimer::StartTimer(TTimeout delay, UInt32 sig)
{
    IOReturn rc;
    
    require(fTimerSrc, Fail);

    XTRACE (kStartTimerDelay,  delay>>16, delay);

    if (fBusy) StopTimer();			// cleanup
    require(fBusy == false, Fail);                  // sanity
    
    fBusy = true;                                   // mark timer as in-use
    this->retain();                                 // retain to prevent dangling
	
    fSig = sig;
    
    if (delay < 0)                                  // microsecond timer
	rc = fTimerSrc->setTimeoutUS(-delay);
    else
	rc = fTimerSrc->setTimeoutMS(delay);        // millisecond timer
	
    check(rc == kIOReturnSuccess);
    XTRACE (kStartTimerSignal, sig, this->getRetainCount());
    
Fail:
    return;
}

void CTimer::StopTimer()
{
    Boolean ok;
    XTRACE(kLogStopTimer1, (int)this >> 16, (short)this);
    
    if (fBusy == false) {                       // stopped, but we're already stopped (nop)
	XTRACE(kLogStopTimer2, 0, this->getRetainCount());
	return;
    }
    
    if (fTimerSrc) {
	ok = fTimerSrc->SafeCancelTimeout();    // returns ok if it worked
	if (ok) {                               // if cancel timer worked, then
	    XTRACE(kLogStopTimer3, 0, this->getRetainCount());
	    fBusy = false;                      // mark timer as available and                  
	    this->release();                    // we can release self safely
	} else {
	    XTRACE(kLogStopTimer4, 0, this->getRetainCount());
	}
    }
    XTRACE(kLogStopTimer5, 0, this->getRetainCount());
    return;
}

void
CTimer::timeout(OSObject *owner, IrDATimerEventSource *sender)
{
    CTimer  *obj;
    
    XTRACE(kLogTimeout1, (int)owner >> 16, (short)owner);
    obj = OSDynamicCast(CTimer, owner);
    require(obj, Fail);
    require(obj->fOwner, Fail);
    require(obj->fCallback, Fail);
    require(obj->fBusy, Fail);
    
    XTRACE(kLogTimeout2, 0, obj->getRetainCount());
    
    if (obj->getRetainCount() == 1) {           // if it's down to one, then we're been deleted but for our start timer
	thread_call_enter1(obj->fGrimReaper, 0);
	return;
    }
    obj->fBusy = false;
    obj->release(); 
    // temp debugging
    XTRACE(kLogTimeout3, (int)obj->fOwner >> 16, (short)obj->fOwner);
    XTRACE(kLogTimeout4, (int)sender >> 16, (short)sender);
    (*obj->fCallback)(obj->fOwner, sender);
    XTRACE(kLogTimeout5, 0xffff, 0xffff);
    
Fail:
    return;
}

/*static*/
void CTimer::grim_reaper(thread_call_param_t param0, thread_call_param_t param1)
{
    CTimer *obj;
    
    XTRACE(kLogGrimReaper, 0x1111, 0x1111);
    
    obj = OSDynamicCast(CTimer, (OSObject *)param0);
    require(obj, Fail);
    require(obj->fBusy, Fail);
    
    obj->fBusy = false;
    obj->release();

    XTRACE(kLogGrimReaper, 0xffff, 0xffff);
    return;
    
Fail:
    XTRACE(kLogGrimReaper, 0xdead, 0xbeef);
    return;
}
