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

#import <Foundation/Foundation.h>
#import "YSRACEXTKeyPathCoding.h"
#import "YSRACmetamacros.h"

/// Creates a signal which observes `KEYPATH` on `TARGET` for changes.
///
/// In either case, the observation continues until `TARGET` _or self_ is
/// deallocated. If any intermediate object is deallocated instead, it will be
/// assumed to have been set to nil.
///
/// Make sure to `@strongify(self)` when using this macro within a block! The
/// macro will _always_ reference `self`, which can silently introduce a retain
/// cycle within a block. As a result, you should make sure that `self` is a weak
/// reference (e.g., created by `@weakify` and `@strongify`) before the
/// expression that uses `YSRACObserve`.
///
/// Examples
///
///    // Observes self, and doesn't stop until self is deallocated.
///    YSRACSignal *selfSignal = YSRACObserve(self, arrayController.items);
///
///    // Observes the array controller, and stops when self _or_ the array
///    // controller is deallocated.
///    YSRACSignal *arrayControllerSignal = YSRACObserve(self.arrayController, items);
///
///    // Observes obj.arrayController, and stops when self _or_ the array
///    // controller is deallocated.
///    YSRACSignal *signal2 = YSRACObserve(obj.arrayController, items);
///
///    @weakify(self);
///    YSRACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) {
///        // Avoids a retain cycle because of YSRACObserve implicitly referencing
///        // self.
///        @strongify(self);
///        return YSRACObserve(arrayController, items);
///    }];
///
/// Returns a signal which sends the current value of the key path on
/// subscription, then sends the new value every time it changes, and sends
/// completed if self or observer is deallocated.
#define _YSRACObserve(TARGET, KEYPATH) \
({ \
	__weak id target_ = (TARGET); \
	[target_ ysrac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})

#if __clang__ && (__clang_major__ >= 8)
#define YSRACObserve(TARGET, KEYPATH) _YSRACObserve(TARGET, KEYPATH)
#else
#define YSRACObserve(TARGET, KEYPATH) \
({ \
	_Pragma("clang diagnostic push") \
	_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
	_YSRACObserve(TARGET, KEYPATH) \
	_Pragma("clang diagnostic pop") \
})
#endif

@class YSRACDisposable;
@class YSRACTwoTuple<__covariant First, __covariant Second>;
@class YSRACSignal<__covariant ValueType>;

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (YSRACPropertySubscribing)

/// Creates a signal to observe the value at the given key path.
///
/// The initial value is sent on subscription, the subsequent values are sent
/// from whichever thread the change occured on, even if it doesn't have a valid
/// scheduler.
///
/// Returns a signal that immediately sends the receiver's current value at the
/// given keypath, then any changes thereafter.
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (YSRACSignal *)ysrac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
- (YSRACSignal *)ysrac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer;
#endif

/// Creates a signal to observe the changes of the given key path.
///
/// The initial value is sent on subscription if `NSKeyValueObservingOptionInitial` is set.
/// The subsequent values are sent from whichever thread the change occured on,
/// even if it doesn't have a valid scheduler.
///
/// Returns a signal that sends tuples containing the current value at the key
/// path and the change dictionary for each KVO callback.
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (YSRACSignal<YSRACTwoTuple<id, NSDictionary *> *> *)ysrac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer;
#else
- (YSRACSignal<YSRACTwoTuple<id, NSDictionary *> *> *)ysrac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer;
#endif

@end

NS_ASSUME_NONNULL_END

#define YSRACAble(...) \
	metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
		(_YSRACAbleObject(self, __VA_ARGS__)) \
		(_YSRACAbleObject(__VA_ARGS__))

#define _YSRACAbleObject(object, property) [object ysrac_signalForKeyPath:@keypath(object, property) observer:self]

#define YSRACAbleWithStart(...) \
	metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
		(_YSRACAbleWithStartObject(self, __VA_ARGS__)) \
		(_YSRACAbleWithStartObject(__VA_ARGS__))

#define _YSRACAbleWithStartObject(object, property) [object ysrac_signalWithStartingValueForKeyPath:@keypath(object, property) observer:self]
