//
//  RACChannelExamples.m
//  ReactiveCocoa
//
//  Created by Uri Baghin on 30/12/2012.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import "RACChannelExamples.h"

#import "NSObject+RACDeallocating.h"
#import "NSObject+RACPropertySubscribing.h"
#import "RACChannel.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACSignal+Operations.h"

NSString * const RACChannelExamples = @"RACChannelExamples";
NSString * const RACChannelExampleCreateBlock = @"RACChannelExampleCreateBlock";

NSString * const RACViewChannelExamples = @"RACViewChannelExamples";
NSString * const RACViewChannelExampleCreateViewBlock = @"RACViewChannelExampleCreateViewBlock";
NSString * const RACViewChannelExampleCreateTerminalBlock = @"RACViewChannelExampleCreateTerminalBlock";
NSString * const RACViewChannelExampleKeyPath = @"RACViewChannelExampleKeyPath";
NSString * const RACViewChannelExampleSetViewValueBlock = @"RACViewChannelExampleSetViewValueBlock";

SharedExampleGroupsBegin(RACChannelExamples)

sharedExamplesFor(RACChannelExamples, ^(NSDictionary *data) {
	__block RACChannel * (^getChannel)(void);
	__block RACChannel *channel;

	id value1 = @"test value 1";
	id value2 = @"test value 2";
	id value3 = @"test value 3";
	NSArray *values = @[ value1, value2, value3 ];
	
	before(^{
		getChannel = data[RACChannelExampleCreateBlock];
		channel = getChannel();
	});
	
	it(@"should not send any leadingTerminal value on subscription", ^{
		__block id receivedValue = nil;

		[channel.followingTerminal sendNext:value1];
		[channel.leadingTerminal subscribeNext:^(id x) {
			receivedValue = x;
		}];

		expect(receivedValue).to.beNil();
		
		[channel.followingTerminal sendNext:value2];
		expect(receivedValue).to.equal(value2);
	});
	
	it(@"should send the latest followingTerminal value on subscription", ^{
		__block id receivedValue = nil;

		[channel.leadingTerminal sendNext:value1];
		[[channel.followingTerminal take:1] subscribeNext:^(id x) {
			receivedValue = x;
		}];

		expect(receivedValue).to.equal(value1);
		
		[channel.leadingTerminal sendNext:value2];
		[[channel.followingTerminal take:1] subscribeNext:^(id x) {
			receivedValue = x;
		}];

		expect(receivedValue).to.equal(value2);
	});
	
	it(@"should send leadingTerminal values as they change", ^{
		NSMutableArray *receivedValues = [NSMutableArray array];
		[channel.leadingTerminal subscribeNext:^(id x) {
			[receivedValues addObject:x];
		}];

		[channel.followingTerminal sendNext:value1];
		[channel.followingTerminal sendNext:value2];
		[channel.followingTerminal sendNext:value3];
		expect(receivedValues).to.equal(values);
	});
	
	it(@"should send followingTerminal values as they change", ^{
		[channel.leadingTerminal sendNext:value1];

		NSMutableArray *receivedValues = [NSMutableArray array];
		[channel.followingTerminal subscribeNext:^(id x) {
			[receivedValues addObject:x];
		}];

		[channel.leadingTerminal sendNext:value2];
		[channel.leadingTerminal sendNext:value3];
		expect(receivedValues).to.equal(values);
	});

	it(@"should complete both signals when the leadingTerminal is completed", ^{
		__block BOOL completedLeft = NO;
		[channel.leadingTerminal subscribeCompleted:^{
			completedLeft = YES;
		}];

		__block BOOL completedRight = NO;
		[channel.followingTerminal subscribeCompleted:^{
			completedRight = YES;
		}];

		[channel.leadingTerminal sendCompleted];
		expect(completedLeft).to.beTruthy();
		expect(completedRight).to.beTruthy();
	});

	it(@"should complete both signals when the followingTerminal is completed", ^{
		__block BOOL completedLeft = NO;
		[channel.leadingTerminal subscribeCompleted:^{
			completedLeft = YES;
		}];

		__block BOOL completedRight = NO;
		[channel.followingTerminal subscribeCompleted:^{
			completedRight = YES;
		}];

		[channel.followingTerminal sendCompleted];
		expect(completedLeft).to.beTruthy();
		expect(completedRight).to.beTruthy();
	});

	it(@"should replay completion to new subscribers", ^{
		[channel.leadingTerminal sendCompleted];

		__block BOOL completedLeft = NO;
		[channel.leadingTerminal subscribeCompleted:^{
			completedLeft = YES;
		}];

		__block BOOL completedRight = NO;
		[channel.followingTerminal subscribeCompleted:^{
			completedRight = YES;
		}];

		expect(completedLeft).to.beTruthy();
		expect(completedRight).to.beTruthy();
	});
});

SharedExampleGroupsEnd

SharedExampleGroupsBegin(RACViewChannelExamples)

sharedExamplesFor(RACViewChannelExamples, ^(NSDictionary *data) {
	__block NSString *keyPath;
	__block NSObject * (^getView)(void);
	__block RACChannelTerminal * (^getTerminal)(NSObject *);
	__block void (^setViewValue)(NSObject *view, NSNumber *value);

	__block NSObject *testView;
	__block RACChannelTerminal *endpoint;

	beforeEach(^{
		keyPath = data[RACViewChannelExampleKeyPath];
		getTerminal = data[RACViewChannelExampleCreateTerminalBlock];
		getView = data[RACViewChannelExampleCreateViewBlock];
		setViewValue = data[RACViewChannelExampleSetViewValueBlock];

		testView = getView();
		endpoint = getTerminal(testView);
	});

	it(@"should not send changes made by the channel itself", ^{
		__block BOOL receivedNext = NO;
		[endpoint subscribeNext:^(id x) {
			receivedNext = YES;
		}];

		expect(receivedNext).to.beFalsy();

		[endpoint sendNext:@0.1];
		expect(receivedNext).to.beFalsy();

		[endpoint sendNext:@0.2];
		expect(receivedNext).to.beFalsy();

		[endpoint sendCompleted];
		expect(receivedNext).to.beFalsy();
	});

	it(@"should not send progammatic changes made to the view", ^{
		__block BOOL receivedNext = NO;
		[endpoint subscribeNext:^(id x) {
			receivedNext = YES;
		}];

		expect(receivedNext).to.beFalsy();

		[testView setValue:@0.1 forKeyPath:keyPath];
		expect(receivedNext).to.beFalsy();

		[testView setValue:@0.2 forKeyPath:keyPath];
		expect(receivedNext).to.beFalsy();
	});

	it(@"should not have a starting value", ^{
		__block BOOL receivedNext = NO;
		[endpoint subscribeNext:^(id x) {
			receivedNext = YES;
		}];

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

	it(@"should send view changes", ^{
		__block NSString *received;
		[endpoint subscribeNext:^(id x) {
			received = x;
		}];

		setViewValue(testView, @0.1);
		expect(received).to.equal(@0.1);

		setViewValue(testView, @0.2);
		expect(received).to.equal(@0.2);
	});

	it(@"should set values on the view", ^{
		[endpoint sendNext:@0.1];
		expect([testView valueForKeyPath:keyPath]).to.equal(@0.1);

		[endpoint sendNext:@0.2];
		expect([testView valueForKeyPath:keyPath]).to.equal(@0.2);
	});

	it(@"should not echo changes back to the channel", ^{
		__block NSUInteger receivedCount = 0;
		[endpoint subscribeNext:^(id _) {
			receivedCount++;
		}];

		expect(receivedCount).to.equal(0);

		[endpoint sendNext:@0.1];
		expect(receivedCount).to.equal(0);

		setViewValue(testView, @0.2);
		expect(receivedCount).to.equal(1);
	});

	it(@"should complete when the view deallocates", ^{
		__block BOOL deallocated = NO;
		__block BOOL completed = NO;

		@autoreleasepool {
			NSObject *view __attribute__((objc_precise_lifetime)) = getView();
			[view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
				deallocated = YES;
			}]];

			RACChannelTerminal *terminal = getTerminal(view);
			[terminal subscribeCompleted:^{
				completed = YES;
			}];

			expect(deallocated).to.beFalsy();
			expect(completed).to.beFalsy();
		}

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

	it(@"should deallocate after the view deallocates", ^{
		__block BOOL viewDeallocated = NO;
		__block BOOL terminalDeallocated = NO;

		@autoreleasepool {
			NSObject *view __attribute__((objc_precise_lifetime)) = getView();
			[view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
				viewDeallocated = YES;
			}]];

			RACChannelTerminal *terminal = getTerminal(view);
			[terminal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
				terminalDeallocated = YES;
			}]];

			expect(viewDeallocated).to.beFalsy();
			expect(terminalDeallocated).to.beFalsy();
		}

		expect(viewDeallocated).to.beTruthy();
		expect(terminalDeallocated).will.beTruthy();
	});
});

SharedExampleGroupsEnd
