//
//  RACCommandSpec.m
//  ReactiveCocoa
//
//  Created by Josh Abernathy on 8/31/12.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import "NSArray+RACSequenceAdditions.h"
#import "NSObject+RACDeallocating.h"
#import "NSObject+RACPropertySubscribing.h"
#import "RACCommand.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACEvent.h"
#import "RACScheduler.h"
#import "RACSequence.h"
#import "RACSignal+Operations.h"
#import "RACSubject.h"
#import "RACUnit.h"

SpecBegin(RACCommand)

RACSignal * (^emptySignalBlock)(id) = ^(id _) {
	return [RACSignal empty];
};

describe(@"with a simple signal block", ^{
	__block RACCommand *command;

	beforeEach(^{
		command = [[RACCommand alloc] initWithSignalBlock:^(id value) {
			return [RACSignal return:value];
		}];

		expect(command).notTo.beNil();
		expect(command.allowsConcurrentExecution).to.beFalsy();
	});

	it(@"should be enabled by default", ^{
		expect([command.enabled first]).to.equal(@YES);
	});

	it(@"should not be executing by default", ^{
		expect([command.executing first]).to.equal(@NO);
	});

	it(@"should create an execution signal", ^{
		__block NSUInteger signalsReceived = 0;
		__block BOOL completed = NO;

		id value = NSNull.null;
		[command.executionSignals subscribeNext:^(RACSignal *signal) {
			signalsReceived++;

			[signal subscribeNext:^(id x) {
				expect(x).to.equal(value);
			} completed:^{
				completed = YES;
			}];
		}];

		expect(signalsReceived).to.equal(0);
		
		[command execute:value];
		expect(signalsReceived).will.equal(1);
		expect(completed).to.beTruthy();
	});

	it(@"should return the execution signal from -execute:", ^{
		__block BOOL completed = NO;

		id value = NSNull.null;
		[[command
			execute:value]
			subscribeNext:^(id x) {
				expect(x).to.equal(value);
			} completed:^{
				completed = YES;
			}];

		expect(completed).will.beTruthy();
	});

	it(@"should always send executionSignals on the main thread", ^{
		__block RACScheduler *receivedScheduler = nil;
		[command.executionSignals subscribeNext:^(id _) {
			receivedScheduler = RACScheduler.currentScheduler;
		}];

		[[RACScheduler scheduler] schedule:^{
			expect([[command execute:nil] waitUntilCompleted:NULL]).to.beTruthy();
		}];

		expect(receivedScheduler).to.beNil();
		expect(receivedScheduler).will.equal(RACScheduler.mainThreadScheduler);
	});

	it(@"should not send anything on 'errors' by default", ^{
		__block BOOL receivedError = NO;
		[command.errors subscribeNext:^(id _) {
			receivedError = YES;
		}];
		
		expect([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy();
		expect(receivedError).to.beFalsy();
	});

	it(@"should be executing while an execution signal is running", ^{
		[command.executionSignals subscribeNext:^(RACSignal *signal) {
			[signal subscribeNext:^(id x) {
				expect([command.executing first]).to.equal(@YES);
			}];
		}];

		expect([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy();
		expect([command.executing first]).to.equal(@NO);
	});

	it(@"should always update executing on the main thread", ^{
		__block RACScheduler *updatedScheduler = nil;
		[[command.executing skip:1] subscribeNext:^(NSNumber *executing) {
			if (!executing.boolValue) return;

			updatedScheduler = RACScheduler.currentScheduler;
		}];

		[[RACScheduler scheduler] schedule:^{
			expect([[command execute:nil] waitUntilCompleted:NULL]).to.beTruthy();
		}];

		expect([command.executing first]).to.equal(@NO);
		expect(updatedScheduler).will.equal(RACScheduler.mainThreadScheduler);
	});

	it(@"should dealloc without subscribers", ^{
		__block BOOL disposed = NO;

		@autoreleasepool {
			RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock];
			[command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
				disposed = YES;
			}]];
		}

		expect(disposed).will.beTruthy();
	});

	it(@"should complete signals on the main thread when deallocated", ^{
		__block RACScheduler *executionSignalsScheduler = nil;
		__block RACScheduler *executingScheduler = nil;
		__block RACScheduler *enabledScheduler = nil;
		__block RACScheduler *errorsScheduler = nil;

		[[RACScheduler scheduler] schedule:^{
			@autoreleasepool {
				RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock];

				[command.executionSignals subscribeCompleted:^{
					executionSignalsScheduler = RACScheduler.currentScheduler;
				}];

				[command.executing subscribeCompleted:^{
					executingScheduler = RACScheduler.currentScheduler;
				}];

				[command.enabled subscribeCompleted:^{
					enabledScheduler = RACScheduler.currentScheduler;
				}];

				[command.errors subscribeCompleted:^{
					errorsScheduler = RACScheduler.currentScheduler;
				}];
			}
		}];

		expect(executionSignalsScheduler).will.equal(RACScheduler.mainThreadScheduler);
		expect(executingScheduler).will.equal(RACScheduler.mainThreadScheduler);
		expect(enabledScheduler).will.equal(RACScheduler.mainThreadScheduler);
		expect(errorsScheduler).will.equal(RACScheduler.mainThreadScheduler);
	});
});

it(@"should invoke the signalBlock once per execution", ^{
	NSMutableArray *valuesReceived = [NSMutableArray array];
	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id x) {
		[valuesReceived addObject:x];
		return [RACSignal empty];
	}];

	expect([[command execute:@"foo"] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy();
	expect(valuesReceived).to.equal((@[ @"foo" ]));

	expect([[command execute:@"bar"] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy();
	expect(valuesReceived).to.equal((@[ @"foo", @"bar" ]));
});

it(@"should send on executionSignals in order of execution", ^{
	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSequence *seq) {
		return [seq signalWithScheduler:RACScheduler.immediateScheduler];
	}];

	NSMutableArray *valuesReceived = [NSMutableArray array];
	[[command.executionSignals
		concat]
		subscribeNext:^(id x) {
			[valuesReceived addObject:x];
		}];

	RACSequence *first = @[ @"foo", @"bar" ].rac_sequence;
	expect([[command execute:first] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy();

	RACSequence *second = @[ @"buzz", @"baz" ].rac_sequence;
	expect([[command execute:second] asynchronouslyWaitUntilCompleted:NULL]).will.beTruthy();

	NSArray *expectedValues = @[ @"foo", @"bar", @"buzz", @"baz" ];
	expect(valuesReceived).to.equal(expectedValues);
});

it(@"should wait for all signals to complete or error before executing sends NO", ^{
	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
		return signal;
	}];

	command.allowsConcurrentExecution = YES;
	
	RACSubject *firstSubject = [RACSubject subject];
	expect([command execute:firstSubject]).notTo.beNil();

	RACSubject *secondSubject = [RACSubject subject];
	expect([command execute:secondSubject]).notTo.beNil();

	expect([command.executing first]).will.equal(@YES);

	[firstSubject sendError:nil];
	expect([command.executing first]).to.equal(@YES);

	[secondSubject sendNext:nil];
	expect([command.executing first]).to.equal(@YES);

	[secondSubject sendCompleted];
	expect([command.executing first]).will.equal(@NO);
});

it(@"should not deliver errors from executionSignals", ^{
	RACSubject *subject = [RACSubject subject];
	NSMutableArray *receivedEvents = [NSMutableArray array];

	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
		return subject;
	}];

	[[[command.executionSignals
		flatten]
		materialize]
		subscribeNext:^(RACEvent *event) {
			[receivedEvents addObject:event];
		}];

	expect([command execute:nil]).notTo.beNil();
	expect([command.executing first]).will.equal(@YES);

	[subject sendNext:RACUnit.defaultUnit];

	NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ];
	expect(receivedEvents).will.equal(expectedEvents);
	expect([command.executing first]).to.equal(@YES);

	[subject sendNext:@"foo"];

	expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ];
	expect(receivedEvents).will.equal(expectedEvents);
	expect([command.executing first]).to.equal(@YES);

	NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil];
	[subject sendError:error];

	expect([command.executing first]).will.equal(@NO);
	expect(receivedEvents).to.equal(expectedEvents);
});

it(@"should deliver errors from -execute:", ^{
	RACSubject *subject = [RACSubject subject];
	NSMutableArray *receivedEvents = [NSMutableArray array];

	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
		return subject;
	}];

	[[[command
		execute:nil]
		materialize]
		subscribeNext:^(RACEvent *event) {
			[receivedEvents addObject:event];
		}];

	expect([command.executing first]).will.equal(@YES);

	[subject sendNext:RACUnit.defaultUnit];

	NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ];
	expect(receivedEvents).will.equal(expectedEvents);
	expect([command.executing first]).to.equal(@YES);

	[subject sendNext:@"foo"];

	expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ];
	expect(receivedEvents).will.equal(expectedEvents);
	expect([command.executing first]).to.equal(@YES);

	NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil];
	[subject sendError:error];

	expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"], [RACEvent eventWithError:error] ];
	expect(receivedEvents).will.equal(expectedEvents);
	expect([command.executing first]).will.equal(@NO);
});

it(@"should deliver errors onto 'errors'", ^{
	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
		return signal;
	}];

	command.allowsConcurrentExecution = YES;
	
	RACSubject *firstSubject = [RACSubject subject];
	expect([command execute:firstSubject]).notTo.beNil();

	RACSubject *secondSubject = [RACSubject subject];
	expect([command execute:secondSubject]).notTo.beNil();

	NSError *firstError = [NSError errorWithDomain:@"" code:1 userInfo:nil];
	NSError *secondError = [NSError errorWithDomain:@"" code:2 userInfo:nil];
	
	// We should receive errors from our previously-started executions.
	NSMutableArray *receivedErrors = [NSMutableArray array];
	[command.errors subscribeNext:^(NSError *error) {
		[receivedErrors addObject:error];
	}];

	expect([command.executing first]).will.equal(@YES);

	[firstSubject sendError:firstError];
	expect([command.executing first]).will.equal(@YES);

	NSArray *expected = @[ firstError ];
	expect(receivedErrors).will.equal(expected);

	[secondSubject sendError:secondError];
	expect([command.executing first]).will.equal(@NO);

	expected = @[ firstError, secondError ];
	expect(receivedErrors).will.equal(expected);
});

it(@"should not deliver non-error events onto 'errors'", ^{
	RACSubject *subject = [RACSubject subject];
	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
		return subject;
	}];

	__block BOOL receivedEvent = NO;
	[command.errors subscribeNext:^(id _) {
		receivedEvent = YES;
	}];

	expect([command execute:nil]).notTo.beNil();
	expect([command.executing first]).will.equal(@YES);

	[subject sendNext:RACUnit.defaultUnit];
	[subject sendCompleted];

	expect([command.executing first]).will.equal(@NO);
	expect(receivedEvent).to.beFalsy();
});

it(@"should send errors on the main thread", ^{
	RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
		return signal;
	}];

	NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil];

	__block RACScheduler *receivedScheduler = nil;
	[command.errors subscribeNext:^(NSError *e) {
		expect(e).to.equal(error);
		receivedScheduler = RACScheduler.currentScheduler;
	}];

	RACSignal *errorSignal = [RACSignal error:error];

	[[RACScheduler scheduler] schedule:^{
		expect([[command execute:errorSignal] waitUntilCompleted:NULL]).to.beTruthy();
	}];

	expect(receivedScheduler).to.beNil();
	expect(receivedScheduler).will.equal(RACScheduler.mainThreadScheduler);
});

describe(@"enabled signal", ^{
	__block RACSubject *enabledSubject;
	__block RACCommand *command;

	beforeEach(^{
		enabledSubject = [RACSubject subject];
		command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id _) {
			return [RACSignal return:RACUnit.defaultUnit];
		}];
	});

	it(@"should send YES by default", ^{
		expect([command.enabled first]).to.equal(@YES);
	});

	it(@"should send whatever the enabledSignal has sent most recently", ^{
		[enabledSubject sendNext:@NO];
		expect([command.enabled first]).will.equal(@NO);

		[enabledSubject sendNext:@YES];
		expect([command.enabled first]).will.equal(@YES);

		[enabledSubject sendNext:@NO];
		expect([command.enabled first]).will.equal(@NO);
	});
	
	it(@"should sample enabledSignal synchronously at initialization time", ^{
		RACCommand *command = [[RACCommand alloc] initWithEnabled:[RACSignal return:@NO] signalBlock:^(id _) {
			return [RACSignal empty];
		}];
		expect([command.enabled first]).to.equal(@NO);
	});

	it(@"should send NO while executing is YES and allowsConcurrentExecution is NO", ^{
		[[command.executionSignals flatten] subscribeNext:^(id _) {
			expect([command.executing first]).to.equal(@YES);
			expect([command.enabled first]).to.equal(@NO);
		}];

		expect([command.enabled first]).to.equal(@YES);
		expect([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy();
		expect([command.enabled first]).to.equal(@YES);
	});

	it(@"should send YES while executing is YES and allowsConcurrentExecution is YES", ^{
		command.allowsConcurrentExecution = YES;

		__block BOOL outerExecuted = NO;
		__block BOOL innerExecuted = NO;

		// Prevent infinite recursion by only responding to the first value.
		[[[command.executionSignals
			take:1]
			flatten]
			subscribeNext:^(id _) {
				outerExecuted = YES;

				expect([command.executing first]).to.equal(@YES);
				expect([command.enabled first]).to.equal(@YES);

				[[command execute:nil] subscribeCompleted:^{
					innerExecuted = YES;
				}];
			}];

		expect([command.enabled first]).to.equal(@YES);

		expect([command execute:nil]).notTo.beNil();
		expect(outerExecuted).will.beTruthy();
		expect(innerExecuted).will.beTruthy();

		expect([command.enabled first]).to.equal(@YES);
	});

	it(@"should send an error from -execute: when NO", ^{
		[enabledSubject sendNext:@NO];

		RACSignal *signal = [command execute:nil];
		expect(signal).notTo.beNil();
		
		__block BOOL success = NO;
		__block NSError *error = nil;
		expect([signal firstOrDefault:nil success:&success error:&error]).to.beNil();
		expect(success).to.beFalsy();

		expect(error).notTo.beNil();
		expect(error.domain).to.equal(RACCommandErrorDomain);
		expect(error.code).to.equal(RACCommandErrorNotEnabled);
		expect(error.userInfo[RACUnderlyingCommandErrorKey]).to.beIdenticalTo(command);
	});

	it(@"should always update on the main thread", ^{
		__block RACScheduler *updatedScheduler = nil;
		[[command.enabled skip:1] subscribeNext:^(id _) {
			updatedScheduler = RACScheduler.currentScheduler;
		}];

		[[RACScheduler scheduler] schedule:^{
			[enabledSubject sendNext:@NO];
		}];

		expect([command.enabled first]).to.equal(@YES);
		expect([command.enabled first]).will.equal(@NO);
		expect(updatedScheduler).to.equal(RACScheduler.mainThreadScheduler);
	});

	it(@"should complete when the command is deallocated even if the input signal hasn't", ^{
		__block BOOL deallocated = NO;
		__block BOOL completed = NO;

		@autoreleasepool {
			RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:emptySignalBlock];
			[command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
				deallocated = YES;
			}]];

			[command.enabled subscribeCompleted:^{
				completed = YES;
			}];
		}

		expect(deallocated).will.beTruthy();
		expect(completed).will.beTruthy();
	});
});

SpecEnd
