/* Timer.m
 * Written By:  Thomas Burkholder
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 */


#import "Timer.h"

#define DEFAULTPERIOD 1.0
#define LOWPERIOD 0.05
#define HIGHPERIOD MAXDOUBLE

#define DEFAULTSYNC 60
#define LOWSYNC 1
#define HIGHSYNC MAXINT

#define DEFAULTSTART 0.0
#define LOWSTART MINDOUBLE
#define HIGHSTART MAXDOUBLE

#define LOWWRAP MINDOUBLE
#define HIGHWRAP MAXDOUBLE

#define DEFAULTINCREMENT 1.0
#define LOWINCREMENT MINFLOAT
#define HIGHINCREMENT MAXFLOAT

#define DEFAULTPRIORITY NX_BASETHRESHOLD
#define LOWPRIORITY 0
#define HIGHPRIORITY 30


//#define MAX(_x,_y) (_x>_y)?_x:_y
//#define MIN(__x,__y) (__x<__y)?__x:__y
#define CONSTRAIN(__a,__low,__high) MIN(MAX(__a,__low),__high)

@implementation Timer

// function that does what's necessary to the object.  To maximise speed, 
// methodFor: is used.
void handle(DPSTimedEntry tag, double now, char *userdata)
{
	Timer *self = (id)userdata;

	self->value += self->incrementBy;
	self->syncCounter++;
	if (self->bFlags.sync && (self->syncCounter >= self->syncValue)) {
		[self synchronize:self];
		self->syncCounter = 0;
	}
	if (self->bFlags.wrap && (self->value >= self->wrapValue))
		self->value = self->startValue;
	if (self->actionFunc)
		self->actionFunc(self->target,self->action,self);
	return;
}

// initializing and freeing
+ initialize
{
	if (self == [Timer class])
		[self setVersion:1];
	return self;
}

- init
{
	[super init];

	entry = NULL;
	period = DEFAULTPERIOD;
	priority = DEFAULTPRIORITY;

	bFlags.visibleDebug = NO;
	bFlags.wrap = YES;
	bFlags.sync = NO;

	syncValue = DEFAULTSYNC;
	value = 0.0;
	startValue = DEFAULTSTART;
	wrapValue = 60.0;
	incrementBy = DEFAULTINCREMENT;

	target = nil;
	action = (SEL)0;
	actionFunc = NULL;
	return self;
}

- free
{
	if (entry) {
		[self stop:self];
	}
	return [super free];
}

// debugging methods
- logError:(char *)str
{
	if (bFlags.visibleDebug)
		NXRunAlertPanel("ERROR",str,NULL,NULL,NULL);
	NXLogError(str);
	return self;
}

// private methods
- stopEntry
{
	if (entry == NULL) {
		[self logError:"Stop called on stopped timer!\n"];
		return nil;
	} else {
		DPSRemoveTimedEntry(entry);
		entry = NULL;
	}
	return self;
}

- startEntry
{
	if (entry != NULL) {
		[self stopEntry];
	}

	// get function for method, so we can be super-efficient
	if (target && action)
		actionFunc = [target methodFor:action];

	// make a new timed entry
	entry = DPSAddTimedEntry(period,(DPSTimedEntryProc)handle,self,priority);

	// check it
	if ((int)entry == -1) {
		[self logError:"Couldn't start timer!\n"];
		entry = NULL;
		return nil;
	}
	return self;
}

// actions
- start:sender
{
	// stop timer if necessary
	value = startValue;
	syncTime = [self currentTime];
	syncCounter = 0;
	[self startEntry];
	return self;
}

- stop:sender
{
	return [self stopEntry];
}

- pause:sender
{
	return [self stopEntry];;
}

- resume:sender
{
	return [self startEntry];
}

// just in case anyone wants to fool with the timer directly
- (DPSTimedEntry) entry
{
	return entry;
}

- (unsigned int)currentTime
{
	struct timeval curTime;
	gettimeofday (&curTime, NULL);
	return (curTime.tv_sec) * 1000 + curTime.tv_usec / 1000;
}

- synchronize:sender;
{
	int correctedTicks = (([self currentTime]-syncTime)/((int)(period*1000)));
	if (bFlags.wrap) {
		correctedTicks = correctedTicks % (int)((wrapValue-startValue)/incrementBy);
	}
	value = correctedTicks*incrementBy;
	return self;
}

// methods to get/set the value of the timer
- (float)floatValue { return value; }
- setFloatValue:(float)aValue { value = aValue; return self; }
- (int)intValue { return (int)value; }
- setIntValue:(int)aValue { value = (float)aValue; return self; }
- (const char *)stringValue { static char str[20]; sprintf(str,"%f",value); return str; }
- setStringValue:(const char *)aValue { value = atof(aValue); return self; }
- (double)doubleValue { return (double)value; }
- setDoubleValue:(double)aValue { value = (double)aValue; return self; }

// methods to get/set behaviours
- setIncrementBy:(float)aValue { incrementBy = aValue; return self; }
- (float)incrementBy { return incrementBy; }
- setStartValue:(float)aValue { startValue = aValue; return self; }
- (float)startValue { return startValue; }
- setWrap:(BOOL)yn { bFlags.wrap = yn; return self; }
- (BOOL)wrap { return (BOOL)(bFlags.wrap); }
- setWrapValue:(float)aValue { wrapValue = CONSTRAIN(aValue,LOWWRAP,HIGHWRAP); return self; }
- (float)wrapValue { return wrapValue; }
- (int)syncValue { return syncValue; }
- setSyncValue:(int)aValue { syncValue = CONSTRAIN(aValue,LOWSYNC,HIGHSYNC); return self; }
- (BOOL)sync { return (BOOL)(bFlags.sync); }
- setSync:(BOOL)yn { bFlags.sync = yn; return self; }
- (BOOL)visibleDebug { return (BOOL)(bFlags.visibleDebug); }
- setVisibleDebug:(BOOL)yn { bFlags.visibleDebug = yn; return self; }
- (int)priority { return priority; }
- setPriority:(int)aPriority { priority = CONSTRAIN(aPriority,LOWPRIORITY,HIGHPRIORITY); return self; }
- (double)period { return period; }
- setPeriod:(double)aPeriod { period = CONSTRAIN(aPeriod,LOWPERIOD,HIGHPERIOD); return self; }
- (SEL)action { return action; }

- setAction:(SEL)anAction
{
	action = anAction;
	// in case someone decides to change targets while the timer is operational
	if (target && action)
		actionFunc = [target methodFor:action];
	return self; 
}

- target
{
	return target;
}

- setTarget:anObject
{
	target = anObject;
	// in case someone decides to change targets while the timer is operational
	if (target && action)
		actionFunc = [target methodFor:action];
	return self;
}

// archival methods
- read:(NXTypedStream *)stream
{
	float sv;
	[super read:stream];
	switch (NXTypedStreamClassVersion(stream, "Timer")) {
		case 0 :  // syncValue was a float
			NXReadTypes(stream,"@:dicfffff", &target, &action, &period,
							&priority, &bFlags, &sv,
							&startValue, &wrapValue, &value, 
							&incrementBy);
			syncValue = (int)sv;
			break;
		default:
			NXReadTypes(stream,"@:diciffff", &target, &action, &period,
							&priority, &bFlags, &syncValue,
							&startValue, &wrapValue, &value, 
							&incrementBy);
	}
	entry = NULL;
	actionFunc = NULL;
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteTypes(stream,"@:diciffff", &target, &action, &period,
							&priority, &bFlags, &syncValue,
							&startValue, &wrapValue, &value,
							&incrementBy);
	return self;
}

@end
