//
//  DataSensor.m
//

#import "DataSensor.h"

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
_Pragma("clang diagnostic push")                                        \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
code;                                                                   \
_Pragma("clang diagnostic pop")                                         \

@implementation DataSensor {
	id _target;
	SEL _action;
	id _storedValue;
	id _previousValue;
	BOOL _isFiring;
	BOOL _didCheckBefore;
	NSNumber * _comparativeNumberValue;
}

- (void)setBoundDataValue:(id)value {
	
	if (!self.enabled) return;

	if (_sensorBehaviour == DataSensorBehaviourValueChanges && !_didCheckBefore && !_triggersInitially) {
		// Do nothing here.
	}
	else {
		
		if ([self shouldFireForValue:value]) {
			if (!_isFiring || _triggersRepeatedly) {
				if (_didCheckBefore || _triggersInitially) {
					_isFiring = YES;
					[self fire];
				}
			}
		}
		else _isFiring = NO;
		
		// It triggers again when the target has been set.
		if (!_target) _storedValue = value;

	}
	if (_sensorBehaviour == DataSensorBehaviourValueChanges) _previousValue = value;

	_didCheckBefore = YES;
}

- (void)addTarget:(id)target action:(SEL)selector forEvents:(NSString *)eventIdentifier {
	_target = target;
	_action = selector;
	
	if (_didCheckBefore) {
		[self setBoundDataValue:_storedValue];
		_storedValue = nil;
	}
}

- (void)removeTarget:(id)target {
	_target = nil;
}

- (BOOL)shouldFireForValue:(id)value {
	switch (self.sensorBehaviour) {
		case DataSensorBehaviourValueChanges:
			return (!_previousValue && _triggersInitially) || ![self previousValueIsEqualTo:value];
		case DataSensorBehaviourValueMatchesValue:
			return [self comparativeValueIsEqualTo:value];
		case DataSensorBehaviourValueMatchesConstraint:
			return [self valueMatchesConstraint:value];
		default:
			break;
	}
}

- (NSNumber *)numberFromValue:(id)value {
	if ([value isKindOfClass:[NSNumber class]]) return value;
	if ([value isKindOfClass:[NSString class]]) {
		NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
		[f setNumberStyle:NSNumberFormatterDecimalStyle];
		return [f numberFromString:value];
	}
	return [NSNumber numberWithBool:value ? YES : NO];
}

- (NSNumber *)comparativeNumberValue {
	if (!_comparativeNumberValue) {
		_comparativeNumberValue = [self numberFromValue:_comparativeValue];
	}
	return _comparativeNumberValue;
}

- (BOOL)comparativeValueIsEqualTo:(id)value {
	if (_interpretsComparativeValueAsNumber) {
		return [[self numberFromValue:value] isEqualToNumber:self.comparativeNumberValue];
	}
	else {
		if (![value isKindOfClass:[NSString class]]) return NO;
		return [self.comparativeValue isEqualToString:value];
	}
}

- (BOOL)previousValueIsEqualTo:(id)value {
	if (!_previousValue) return NO;
	if ([value isKindOfClass:NSString.class])
		return [value isEqualToString:_previousValue];
	if ([value isKindOfClass:NSNumber.class])
		return [value isEqualToNumber:_previousValue];
	if ([value isKindOfClass:NSArray.class])
		return [value isEqualToArray:_previousValue];
	return value && (value == _previousValue);
}

- (BOOL)valueMatchesConstraint:(id)value {
	switch (self.sensorConstraint) {
		case DataSensorConstraintValueEmpty:
			if (!value) return YES;
			if ([value isKindOfClass:[NSString class]]) return [value length] == 0;
			if ([value isKindOfClass:[NSArray class]]) return [value count] == 0;
			break;
		case DataSensorConstraintValueNotEmpty:
			if (!value) return NO;
			if ([value isKindOfClass:[NSString class]]) return [value length] > 0;
			if ([value isKindOfClass:[NSArray class]]) return [value count] > 0;
			return YES;
		default:
			break;
	}
	return NO;
}

- (void)fire {
#ifdef DEBUG
	//NSLog(@"Fire!");
#endif
	if (!_target) return;
  SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
	[_target performSelector:_action withObject:self afterDelay:0.0];
  );
}

- (void)addObserver:(id)observer {
}

- (void)removeObserver:(id)observer {
}

@end
