//
//  RACAction.h
//  ReactiveCocoa
//
//  Created by Justin Spahr-Summers on 2013-12-11.
//  Copyright (c) 2013 GitHub, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "RACSignal.h"
#import "RACSignalGenerator.h"

/// The domain for errors originating within `RACAction`.
extern NSString * const RACActionErrorDomain;

/// An attempt was made to execute a disabled action.
extern const NSInteger RACActionErrorNotEnabled;

/// Associated with the `RACAction` in which the error occurred.
extern NSString * const RACActionErrorKey;

/// Represents a UI action that will subscribe to a signal when executed.
///
/// To create an action, use -[RACSignalGenerator action] or -[RACSignal
/// action].
@interface RACAction : RACSignalGenerator

/// A signal of whether this action is able to execute.
///
/// This will send NO if the receiver has started executing, or if the receiver
/// was initialized with an `enabledSignal` and NO was received upon that
/// signal.
///
/// When the action is not executing (and `enabledSignal` has sent YES, if one
/// was provided), this will send YES.
///
/// This signal will send its current value immediately, and then all future
/// events on the main thread.
@property (nonatomic, strong, readonly) RACSignal *enabled;

/// A signal of whether this action is currently executing.
///
/// This signal will send its current value immediately, and then all future
/// events on the main thread.
@property (nonatomic, strong, readonly) RACSignal *executing;

/// Forwards values from the receiver's executions.
///
/// This signal includes all `next` events sent by the receiver's
/// `executionSignals`, without any execution's `error` or `completed` events.
///
/// This signal will send all events on the main thread.
@property (nonatomic, strong, readonly) RACSignal *results;

/// Forwards errors from the receiver's executions.
///
/// When an error occurs during an execution of the action, this signal will
/// send the associated NSError value as a `next` event (since an `error` event
/// would terminate the stream).
///
/// **Note:** This stream will not include any `RACActionErrorNotEnabled` errors
/// generated by trying to execute the action while it's disabled.
///
/// This signal will send all events on the main thread.
@property (nonatomic, strong, readonly) RACSignal *errors;

/// A signal of all executions of the receiver.
///
/// A new signal will be sent upon this signal when -execute: is invoked, or
/// the result of -signalWithValue: is subscribed to. Each inner signal will
/// replay the results of the execution, including any `error` or `completed`
/// event.
///
/// If a subscription to -signalWithValue: is disposed, the corresponding inner
/// signal from this signal will send `completed` (to prevent subscriptions from
/// living forever).
///
/// **Note:** No signal will be sent for any `RACActionErrorNotEnabled` errors
/// generated by trying to execute the action while it's disabled.
/// 
/// Only executions that begin _after_ subscription will be sent upon this
/// signal. All inner signals will arrive upon the main thread, though each may
/// deliver its results on any chosen thread(s).
@property (nonatomic, strong, readonly) RACSignal *executionSignals;

/// Asynchronously executes the receiver on the main thread.
///
/// If the action is not enabled when this method is invoked, nothing happens.
///
/// input - If the receiver was constructed from a signal generator, this is
///         a value to pass into -[RACSignalGenerator signalWithValue:] as part
///         of creating a signal for this execution.
///
///         If the receiver was constructed from a signal, this argument is
///         ignored.
///
///         In either case, this argument may be nil.
- (void)execute:(id)input;

/// Creates a signal that will execute the receiver (on the main thread) upon
/// each subscription.
///
/// If the receiver is already executing when the returned signal is subscribed
/// to, the subscription will error out.
///
/// input - If the receiver was constructed from a signal generator, this is
///         a value to pass into -[RACSignalGenerator signalWithValue:] as part
///         of creating a signal for execution.
///
///         If the receiver was constructed from a signal, this argument is
///         ignored.
///
///         In either case, this argument may be nil.
///
/// Returns a signal which will execute the receiver upon subscription, then
/// forward all events as they arrive. If the receiver is already executing,
/// the subscription will immediately error with code
/// `RACActionErrorNotEnabled`. Disposing of the execution subscription will
/// also dispose of the subscription to the underlying signal.
- (RACSignal *)signalWithValue:(id)input;

@end

@interface RACSignalGenerator (RACActionAdditions)

/// Creates an action that will generate signals using the receiver, and
/// subscribe to them when executed.
- (RACAction *)action;

/// Like -[RACSignalGenerator action], but automatically disables the
/// `RACAction` based on the given signal.
///
/// Note that actions are always disabled while executing, regardless of the
/// value sent by `enabledSignal`.
///
/// enabledSignal - A signal of BOOLs indicating whether the action should be
///                 enabled. Before any values are sent on this signal, the
///                 action will be enabled.
- (RACAction *)actionEnabledIf:(RACSignal *)enabledSignal;

@end

@interface RACSignal (RACActionAdditions)

/// Creates an action that will subscribe to the receiver when executed.
///
/// When a `RACAction` is constructed in this way, the `input` arguments to
/// -execute: and -signalWithValue: will be ignored.
- (RACAction *)action;

/// Like -[RACSignal action], but automatically disables the `RACAction` based
/// on the given signal.
///
/// Note that actions are always disabled while executing, regardless of the
/// value sent by `enabledSignal`.
///
/// enabledSignal - A signal of BOOLs indicating whether the action should be
///                 enabled. Before any values are sent on this signal, the
///                 action will be enabled.
- (RACAction *)actionEnabledIf:(RACSignal *)enabledSignal;

@end
