//
//  RACSubjectSpec.m
//  ReactiveCocoa
//
//  Created by Josh Abernathy on 6/24/12.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import "RACSubscriberExamples.h"

#import <libkern/OSAtomic.h>
#import "EXTScope.h"
#import "RACBehaviorSubject.h"
#import "RACDisposable.h"
#import "RACReplaySubject.h"
#import "RACScheduler.h"
#import "RACSignal+Operations.h"
#import "RACSubject.h"
#import "RACUnit.h"

SpecBegin(RACSubject)

describe(@"RACSubject", ^{
	__block RACSubject *subject;
	__block NSMutableArray *values;

	__block BOOL success;
	__block NSError *error;

	beforeEach(^{
		values = [NSMutableArray array];

		subject = [RACSubject subject];
		success = YES;
		error = nil;

		[subject subscribeNext:^(id value) {
			[values addObject:value];
		} error:^(NSError *e) {
			error = e;
			success = NO;
		} completed:^{
			success = YES;
		}];
	});

	itShouldBehaveLike(RACSubscriberExamples, ^{
		return @{
			RACSubscriberExampleSubscriber: subject,
			RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy],
			RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy],
			RACSubscriberExampleSuccessBlock: [^{ return success; } copy]
		};
	});
});

describe(@"RACReplaySubject", ^{
	__block RACReplaySubject *subject = nil;

	describe(@"with a capacity of 1", ^{
		beforeEach(^{
			subject = [RACReplaySubject replaySubjectWithCapacity:1];
		});
		
		it(@"should send the last value", ^{
			id firstValue = @"blah";
			id secondValue = @"more blah";
			
			[subject sendNext:firstValue];
			[subject sendNext:secondValue];
			
			__block id valueReceived = nil;
			[subject subscribeNext:^(id x) {
				valueReceived = x;
			}];
			
			expect(valueReceived).to.equal(secondValue);
		});
		
		it(@"should send the last value to new subscribers after completion", ^{
			id firstValue = @"blah";
			id secondValue = @"more blah";
			
			__block id valueReceived = nil;
			__block NSUInteger nextsReceived = 0;
			
			[subject sendNext:firstValue];
			[subject sendNext:secondValue];
			
			expect(nextsReceived).to.equal(0);
			expect(valueReceived).to.beNil();
			
			[subject sendCompleted];
			
			[subject subscribeNext:^(id x) {
				valueReceived = x;
				nextsReceived++;
			}];
			
			expect(nextsReceived).to.equal(1);
			expect(valueReceived).to.equal(secondValue);
		});

		it(@"should not send any values to new subscribers if none were sent originally", ^{
			[subject sendCompleted];

			__block BOOL nextInvoked = NO;
			[subject subscribeNext:^(id x) {
				nextInvoked = YES;
			}];

			expect(nextInvoked).to.beFalsy();
		});

		it(@"should resend errors", ^{
			NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
			[subject sendError:error];

			__block BOOL errorSent = NO;
			[subject subscribeError:^(NSError *sentError) {
				expect(sentError).to.equal(error);
				errorSent = YES;
			}];

			expect(errorSent).to.beTruthy();
		});

		it(@"should resend nil errors", ^{
			[subject sendError:nil];

			__block BOOL errorSent = NO;
			[subject subscribeError:^(NSError *sentError) {
				expect(sentError).to.beNil();
				errorSent = YES;
			}];

			expect(errorSent).to.beTruthy();
		});
	});

	describe(@"with an unlimited capacity", ^{
		beforeEach(^{
			subject = [RACReplaySubject subject];
		});

		itShouldBehaveLike(RACSubscriberExamples, ^{
			return @{
				RACSubscriberExampleSubscriber: subject,
				RACSubscriberExampleValuesReceivedBlock: [^{
					NSMutableArray *values = [NSMutableArray array];

					// This subscription should synchronously dump all values already
					// received into 'values'.
					[subject subscribeNext:^(id value) {
						[values addObject:value];
					}];

					return values;
				} copy],
				RACSubscriberExampleErrorReceivedBlock: [^{
					__block NSError *error = nil;

					[subject subscribeError:^(NSError *x) {
						error = x;
					}];

					return error;
				} copy],
				RACSubscriberExampleSuccessBlock: [^{
					__block BOOL success = YES;

					[subject subscribeError:^(NSError *x) {
						success = NO;
					}];

					return success;
				} copy]
			};
		});
		
		it(@"should send both values to new subscribers after completion", ^{
			id firstValue = @"blah";
			id secondValue = @"more blah";
			
			[subject sendNext:firstValue];
			[subject sendNext:secondValue];
			[subject sendCompleted];
			
			__block BOOL completed = NO;
			NSMutableArray *valuesReceived = [NSMutableArray array];
			[subject subscribeNext:^(id x) {
				[valuesReceived addObject:x];
			} completed:^{
				completed = YES;
			}];
			
			expect(valuesReceived.count).to.equal(2);
			NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil];
			expect(valuesReceived).to.equal(expected);
			expect(completed).to.beTruthy();
		});

		it(@"should send values in the same order live as when replaying", ^{
			NSUInteger count = 49317;

			// Just leak it, ain't no thang.
			__unsafe_unretained volatile id *values = (__unsafe_unretained id *)calloc(count, sizeof(*values));
			__block volatile int32_t nextIndex = 0;

			[subject subscribeNext:^(NSNumber *value) {
				int32_t indexPlusOne = OSAtomicIncrement32(&nextIndex);
				values[indexPlusOne - 1] = value;
			}];

			dispatch_queue_t queue = dispatch_queue_create("com.github.ReactiveCocoa.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT);
			@onExit {
				dispatch_release(queue);
			};

			dispatch_suspend(queue);
			
			for (NSUInteger i = 0; i < count; i++) {
				dispatch_async(queue, ^{
					[subject sendNext:@(i)];
				});
			}

			dispatch_resume(queue);
			dispatch_barrier_sync(queue, ^{
				[subject sendCompleted];
			});

			OSMemoryBarrier();

			NSArray *liveValues = [NSArray arrayWithObjects:(id *)values count:(NSUInteger)nextIndex];
			expect(liveValues.count).to.equal(count);
			
			NSArray *replayedValues = subject.toArray;
			expect(replayedValues.count).to.equal(count);

			// It should return the same ordering for multiple invocations too.
			expect(replayedValues).to.equal(subject.toArray);

			[replayedValues enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) {
				expect(liveValues[index]).to.equal(value);
			}];
		});

		it(@"should have a current scheduler when replaying", ^{
			[subject sendNext:RACUnit.defaultUnit];

			__block RACScheduler *currentScheduler;
			[subject subscribeNext:^(id x) {
				currentScheduler = RACScheduler.currentScheduler;
			}];

			expect(currentScheduler).notTo.beNil();

			currentScheduler = nil;
			dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
				[subject subscribeNext:^(id x) {
					currentScheduler = RACScheduler.currentScheduler;
				}];
			});

			expect(currentScheduler).willNot.beNil();
		});
		
		it(@"should stop replaying when the subscription is disposed", ^{
			NSMutableArray *values = [NSMutableArray array];

			[subject sendNext:@0];
			[subject sendNext:@1];

			dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
				__block RACDisposable *disposable = [subject subscribeNext:^(id x) {
					expect(disposable).notTo.beNil();

					[values addObject:x];
					[disposable dispose];
				}];
			});

			expect(values).will.equal(@[ @0 ]);
		});

		it(@"should finish replaying before completing", ^{
			[subject sendNext:@1];

			__block id received;
			dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
				[subject subscribeNext:^(id x) {
					received = x;
				}];

				[subject sendCompleted];
			});

			expect(received).will.equal(@1);
		});

		it(@"should finish replaying before erroring", ^{
			[subject sendNext:@1];

			__block id received;
			dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
				[subject subscribeNext:^(id x) {
					received = x;
				}];

				[subject sendError:[NSError errorWithDomain:@"blah" code:-99 userInfo:nil]];
			});

			expect(received).will.equal(@1);
		});

		it(@"should finish replaying before sending new values", ^{
			[subject sendNext:@1];

			NSMutableArray *received = [NSMutableArray array];
			dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
				[subject subscribeNext:^(id x) {
					[received addObject:x];
				}];

				[subject sendNext:@2];
			});

			NSArray *expected = @[ @1, @2 ];
			expect(received).will.equal(expected);
		});
	});
});

SpecEnd
