//
//  YSRACKVOChannel.h
//  ReactiveObjC
//
//  Created by Uri Baghin on 27/12/2012.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import "YSRACChannel.h"
#import "YSRACEXTKeyPathCoding.h"
#import "YSRACmetamacros.h"

/// Creates a YSRACKVOChannel to the given key path. When the targeted object
/// deallocates, the channel will complete.
///
/// If YSRACChannelTo() is used as an expression, it returns a YSRACChannelTerminal that
/// can be used to watch the specified property for changes, and set new values
/// for it. The terminal will start with the property's current value upon
/// subscription.
///
/// If YSRACChannelTo() is used on the left-hand side of an assignment, there must a
/// YSRACChannelTerminal on the right-hand side of the assignment. The two will be
/// subscribed to one another: the property's value is immediately set to the
/// value of the channel terminal on the right-hand side, and subsequent changes
/// to either terminal will be reflected on the other.
///
/// There are two different versions of this macro:
///
///  - YSRACChannelTo(TARGET, KEYPATH, NILVALUE) will create a channel to the `KEYPATH`
///    of `TARGET`. If the terminal is ever sent a `nil` value, the property will
///    be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object
///    properties, but an NSValue should be used for primitive properties, to
///    avoid an exception if `nil` is sent (which might occur if an intermediate
///    object is set to `nil`).
///  - YSRACChannelTo(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to
///    `nil`.
///
/// Examples
///
///  YSRACChannelTerminal *integerChannel = YSRACChannelTo(self, integerProperty, @42);
///
///  // Sets self.integerProperty to 5.
///  [integerChannel sendNext:@5];
///
///  // Logs the current value of self.integerProperty, and all future changes.
///  [integerChannel subscribeNext:^(id value) {
///      NSLog(@"value: %@", value);
///  }];
///
///  // Binds properties to each other, taking the initial value from the right
///  side.
///  YSRACChannelTo(view, objectProperty) = YSRACChannelTo(model, objectProperty);
///  YSRACChannelTo(view, integerProperty, @2) = YSRACChannelTo(model, integerProperty, @10);
#define YSRACChannelTo(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (YSRACChannelTo_(TARGET, __VA_ARGS__, nil)) \
        (YSRACChannelTo_(TARGET, __VA_ARGS__))

/// Do not use this directly. Use the YSRACChannelTo macro above.
#define YSRACChannelTo_(TARGET, KEYPATH, NILVALUE) \
    [[YSRACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(YSRACKVOChannel.new, followingTerminal)]

NS_ASSUME_NONNULL_BEGIN

/// A YSRACChannel that observes a KVO-compliant key path for changes.
@interface YSRACKVOChannel<ValueType> : YSRACChannel<ValueType>

/// Initializes a channel that will observe the given object and key path.
///
/// The current value of the key path, and future KVO notifications for the given
/// key path, will be sent to subscribers of the channel's `followingTerminal`.
/// Values sent to the `followingTerminal` will be set at the given key path using
/// key-value coding.
///
/// When the target object deallocates, the channel will complete. Signal errors
/// are considered undefined behavior.
///
/// This is the designated initializer for this class.
///
/// target   - The object to bind to.
/// keyPath  - The key path to observe and set the value of.
/// nilValue - The value to set at the key path whenever a `nil` value is
///            received. This may be nil when connecting to object properties, but
///            an NSValue should be used for primitive properties, to avoid an
///            exception if `nil` is received (which might occur if an intermediate
///            object is set to `nil`).
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (instancetype)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(nullable ValueType)nilValue;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
- (instancetype)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(nullable ValueType)nilValue;
#endif

- (instancetype)init __attribute__((unavailable("Use -initWithTarget:keyPath:nilValue: instead")));

@end

/// Methods needed for the convenience macro. Do not call explicitly.
@interface YSRACKVOChannel (YSRACChannelTo)

- (YSRACChannelTerminal *)objectForKeyedSubscript:(NSString *)key;
- (void)setObject:(YSRACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key;

@end

NS_ASSUME_NONNULL_END

