//
//  Timer.m
//

#import "Timer.h"

@interface Timer ()
- (void)fire;
@property (strong, nonatomic) NSTimer * internalTimer;
@end


// Storing a weak reference for the NSTimer to avoid a strong reference cycle
@interface TimerCallback : NSObject
@property (nonatomic, weak) Timer * timer;
- (void)callback;
@end

@implementation TimerCallback
- (void)callback { [self.timer fire]; }
@end


// Storing a weak reference to the target
@interface TimerTargetWeakStorage : NSObject
@property (nonatomic, weak) id object;
@end
@implementation TimerTargetWeakStorage
@end


@implementation Timer {
	NSMutableSet * _targetInfos;
}

- (void)addTarget:(id)target action:(SEL)action forEvents:(NSString *)eventType {

	if (!target || !action || !eventType ||
		![target conformsToProtocol:NSProtocolFromString(@"NSObject")] ||
		![eventType isEqualToString:@"fire"])
		return;

	
	if (!_targetInfos) _targetInfos = NSMutableSet.set;
	
	
	TimerTargetWeakStorage * storage = [TimerTargetWeakStorage new];
	storage.object = target;
	
	[_targetInfos addObject:@{
							  @"target": storage,
							  @"action": NSStringFromSelector(action),
							  }];
	
}

- (void)removeTarget:(id)target {
	for (NSDictionary * targetInfo in _targetInfos.copy) {
		TimerTargetWeakStorage * storage = [targetInfo objectForKey:@"target"];
		if (storage.object == target) {
			[_targetInfos removeObject:targetInfo];
		}
	}
}


- (void)dealloc {
	[_targetInfos removeAllObjects];
	[self stop];
}

- (void)fire {
	for (NSDictionary * targetInfo in _targetInfos) {

		TimerTargetWeakStorage * storage = targetInfo[@"target"];
		SEL action = NSSelectorFromString(targetInfo[@"action"]);

		// suppress compiler warning
		_Pragma("clang diagnostic push")
		_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")
		[storage.object performSelector:action withObject:self];
		_Pragma("clang diagnostic pop")
		
	}
	
	if (!_shouldRepeat) {
		[self stop];
	}
}

- (void)awakeFromNib {
	[super awakeFromNib];
	if (self.shouldStartImmediately) {
		[self start];
	}
}

- (void)setInterval:(CGFloat)interval {
	_interval = interval;
	if (_internalTimer) [self start];
}

- (void)start {
	if (_internalTimer) [self stop];
	TimerCallback * cb = [TimerCallback new];
	cb.timer = self;
	_internalTimer = [NSTimer timerWithTimeInterval:_interval target:cb selector:@selector(callback) userInfo:nil repeats:self.shouldRepeat];
	[[NSRunLoop mainRunLoop] addTimer:_internalTimer forMode:NSDefaultRunLoopMode];
}

- (void)stop {
	[_internalTimer invalidate], _internalTimer = nil;
}

@end
