/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#import "OFTimer.h"
#import "OFDate.h"
#import "OFRunLoop.h"
#import "OFThread.h"

#import "OFInvalidArgumentException.h"
#import "OFNotImplementedException.h"

#import "autorelease.h"
#import "macros.h"

@implementation OFTimer
+ (instancetype)scheduledTimerWithTimeInterval: (double)interval
					target: (id)target
				      selector: (SEL)selector
				       repeats: (BOOL)repeats
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					    target: target
					  selector: selector
					   repeats: repeats] autorelease];

	[[OFRunLoop currentRunLoop] addTimer: timer];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}

+ (instancetype)scheduledTimerWithTimeInterval: (double)interval
					target: (id)target
				      selector: (SEL)selector
					object: (id)object
				       repeats: (BOOL)repeats
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					    target: target
					  selector: selector
					    object: object
					   repeats: repeats] autorelease];

	[[OFRunLoop currentRunLoop] addTimer: timer];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}

+ (instancetype)scheduledTimerWithTimeInterval: (double)interval
					target: (id)target
				      selector: (SEL)selector
					object: (id)object1
					object: (id)object2
				       repeats: (BOOL)repeats
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					    target: target
					  selector: selector
					    object: object1
					    object: object2
					   repeats: repeats] autorelease];

	[[OFRunLoop currentRunLoop] addTimer: timer];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}

#ifdef OF_HAVE_BLOCKS
+ (instancetype)scheduledTimerWithTimeInterval: (double)interval
				       repeats: (BOOL)repeats
					 block: (of_timer_block_t)block
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					   repeats: repeats
					     block: block] autorelease];

	[[OFRunLoop currentRunLoop] addTimer: timer];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}
#endif

+ (instancetype)timerWithTimeInterval: (double)interval
			       target: (id)target
			     selector: (SEL)selector
			      repeats: (BOOL)repeats
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					    target: target
					  selector: selector
					   repeats: repeats] autorelease];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}

+ (instancetype)timerWithTimeInterval: (double)interval
			       target: (id)target
			     selector: (SEL)selector
			       object: (id)object
			      repeats: (BOOL)repeats
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					    target: target
					  selector: selector
					    object: object
					   repeats: repeats] autorelease];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}

+ (instancetype)timerWithTimeInterval: (double)interval
			       target: (id)target
			     selector: (SEL)selector
			       object: (id)object1
			       object: (id)object2
			      repeats: (BOOL)repeats
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					    target: target
					  selector: selector
					    object: object1
					    object: object2
					   repeats: repeats] autorelease];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}

#ifdef OF_HAVE_BLOCKS
+ (instancetype)timerWithTimeInterval: (double)interval
			      repeats: (BOOL)repeats
				block: (of_timer_block_t)block
{
	void *pool = objc_autoreleasePoolPush();
	OFDate *fireDate = [OFDate dateWithTimeIntervalSinceNow: interval];
	id timer = [[[self alloc] initWithFireDate: fireDate
					  interval: interval
					   repeats: repeats
					     block: block] autorelease];

	[timer retain];
	objc_autoreleasePoolPop(pool);

	return [timer autorelease];
}
#endif

- init
{
	Class c = [self class];
	[self release];
	@throw [OFNotImplementedException exceptionWithClass: c
						    selector: _cmd];
}

- OF_initWithFireDate: (OFDate*)fireDate_
	     interval: (double)interval_
	       target: (id)target_
	     selector: (SEL)selector_
	       object: (id)object1_
	       object: (id)object2_
	    arguments: (uint8_t)arguments_
	      repeats: (BOOL)repeats_
{
	self = [super init];

	@try {
		fireDate = [fireDate_ retain];
		interval = interval_;
		target = [target_ retain];
		selector = selector_;
		object1 = [object1_ retain];
		object2 = [object2_ retain];
		arguments = arguments_;
		repeats = repeats_;
		isValid = YES;
		condition = [[OFCondition alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithFireDate: (OFDate*)fireDate_
	  interval: (double)interval_
	    target: (id)target_
	  selector: (SEL)selector_
	   repeats: (BOOL)repeats_
{
	return [self OF_initWithFireDate: fireDate_
				interval: interval_
				  target: target_
				selector: selector_
				  object: nil
				  object: nil
			       arguments: 0
				 repeats: repeats_];
}

- initWithFireDate: (OFDate*)fireDate_
	  interval: (double)interval_
	    target: (id)target_
	  selector: (SEL)selector_
	    object: (id)object
	   repeats: (BOOL)repeats_
{
	return [self OF_initWithFireDate: fireDate_
				interval: interval_
				  target: target_
				selector: selector_
				  object: object
				  object: nil
			       arguments: 1
				 repeats: repeats_];
}

- initWithFireDate: (OFDate*)fireDate_
	  interval: (double)interval_
	    target: (id)target_
	  selector: (SEL)selector_
	    object: (id)object1_
	    object: (id)object2_
	   repeats: (BOOL)repeats_
{
	return [self OF_initWithFireDate: fireDate_
				interval: interval_
				  target: target_
				selector: selector_
				  object: object1_
				  object: object2_
			       arguments: 2
				 repeats: repeats_];
}

#ifdef OF_HAVE_BLOCKS
- initWithFireDate: (OFDate*)fireDate_
	   interval: (double)interval_
	    repeats: (BOOL)repeats_
	      block: (of_timer_block_t)block_
{
	self = [super init];

	@try {
		fireDate = [fireDate_ retain];
		interval = interval_;
		repeats = repeats_;
		block = [block_ copy];
		isValid = YES;
		condition = [[OFCondition alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
#endif

- (void)dealloc
{
	[fireDate release];
	[target release];
	[object1 release];
	[object2 release];
#ifdef OF_HAVE_BLOCKS
	[block release];
#endif
	[condition release];

	[super dealloc];
}

- (of_comparison_result_t)compare: (id <OFComparing>)object_
{
	OFTimer *otherTimer;

	if (![object_ isKindOfClass: [OFTimer class]])
		@throw[OFInvalidArgumentException
		    exceptionWithClass: [self class]
			      selector: _cmd];

	otherTimer = (OFTimer*)object_;

	return [fireDate compare: otherTimer->fireDate];
}

- (void)fire
{
	OF_ENSURE(arguments <= 2);

#ifdef OF_HAVE_BLOCKS
	if (block != NULL)
		block(self);
	else {
#endif
		switch (arguments) {
		case 0:
			[target performSelector: selector];
			break;
		case 1:
			[target performSelector: selector
				     withObject: object1];
			break;
		case 2:
			[target performSelector: selector
				     withObject: object1
				     withObject: object2];
			break;
		}
#ifdef OF_HAVE_BLOCKS
	}
#endif

	[condition lock];
	@try {
		done = YES;
		[condition signal];
	} @finally {
		[condition unlock];
	}

	if (repeats && isValid) {
		OFDate *old = fireDate;
		fireDate = [[OFDate alloc]
		    initWithTimeIntervalSinceNow: interval];
		[old release];

		[[OFRunLoop currentRunLoop] addTimer: self];
	} else
		isValid = NO;
}

- (OFDate*)fireDate
{
	OF_GETTER(fireDate, YES)
}

- (double)timeInterval
{
	return interval;
}

- (void)invalidate
{
	isValid = NO;
}

- (BOOL)isValid
{
	return isValid;
}

- (void)waitUntilDone
{
	[condition lock];
	@try {
		if (done) {
			done = NO;
			return;
		}

		[condition wait];
	} @finally {
		[condition unlock];
	}
}
@end
