//
//  NSObject+YSRACPropertySubscribing.m
//  ReactiveObjC
//
//  Created by Josh Abernathy on 3/2/12.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import "NSObject+YSRACPropertySubscribing.h"
#import "YSRACEXTScope.h"
#import "NSObject+YSRACDeallocating.h"
#import "NSObject+YSRACDescription.h"
#import "NSObject+YSRACKVOWrapper.h"
#import "YSRACCompoundDisposable.h"
#import "YSRACDisposable.h"
#import "YSRACKVOTrampoline.h"
#import "YSRACSubscriber.h"
#import "YSRACSignal+Operations.h"
#import "YSRACTuple.h"
#import <libkern/OSAtomic.h>

@implementation NSObject (YSRACPropertySubscribing)

- (YSRACSignal *)ysrac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer {
	return [[[self
		ysrac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer]
		map:^(YSRACTuple *value) {
			// -map: because it doesn't require the block trampoline that -reduceEach: uses
			return value[0];
		}]
		setNameWithFormat:@"YSRACObserve(%@, %@)", YSRACDescription(self), keyPath];
}

- (YSRACSignal *)ysrac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver {
	NSObject *strongObserver = weakObserver;
	keyPath = [keyPath copy];

	NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init];
	objectLock.name = @"org.reactivecocoa.ReactiveObjC.NSObjectYSRACPropertySubscribing";

	__weak NSObject *weakSelf = self;

	YSRACSignal *deallocSignal = [[YSRACSignal
		zip:@[
			self.ysrac_willDeallocSignal,
			strongObserver.ysrac_willDeallocSignal ?: [YSRACSignal never]
		]]
		doCompleted:^{
			// Forces deallocation to wait if the object variables are currently
			// being read on another thread.
			[objectLock lock];
			@onExit {
				[objectLock unlock];
			};
		}];

	return [[[YSRACSignal
		createSignal:^ YSRACDisposable * (id<YSRACSubscriber> subscriber) {
			// Hold onto the lock the whole time we're setting up the KVO
			// observation, because any resurrection that might be caused by our
			// retaining below must be balanced out by the time -dealloc returns
			// (if another thread is waiting on the lock above).
			[objectLock lock];
			@onExit {
				[objectLock unlock];
			};

			__strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver;
			__strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf;

			if (self == nil) {
				[subscriber sendCompleted];
				return nil;
			}

			return [self ysrac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
				[subscriber sendNext:YSRACTuplePack(value, change)];
			}];
		}]
		takeUntil:deallocSignal]
		setNameWithFormat:@"%@ -ysrac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", YSRACDescription(self), keyPath, (unsigned long)options, YSRACDescription(strongObserver)];
}

@end
