//
//  YSRACSequence.m
//  ReactiveObjC
//
//  Created by Justin Spahr-Summers on 2012-10-29.
//  Copyright (c) 2012 GitHub. All rights reserved.
//

#import "YSRACSequence.h"
#import "YSRACArraySequence.h"
#import "YSRACDynamicSequence.h"
#import "YSRACEagerSequence.h"
#import "YSRACEmptySequence.h"
#import "YSRACScheduler.h"
#import "YSRACSignal.h"
#import "YSRACSubscriber.h"
#import "YSRACTuple.h"
#import "YSRACUnarySequence.h"

// An enumerator over sequences.
@interface YSRACSequenceEnumerator : NSEnumerator

// The sequence the enumerator is enumerating.
//
// This will change as the enumerator is exhausted. This property should only be
// accessed while synchronized on self.
@property (nonatomic, strong) YSRACSequence *sequence;

@end

@interface YSRACSequence ()

// Performs one iteration of lazy binding, passing through values from `current`
// until the sequence is exhausted, then recursively binding the remaining
// values in the receiver.
//
// Returns a new sequence which contains `current`, followed by the combined
// result of all applications of `block` to the remaining values in the receiver.
- (YSRACSequence *)bind:(YSRACSequenceBindBlock)block passingThroughValuesFromSequence:(YSRACSequence *)current;

@end

@implementation YSRACSequenceEnumerator

- (id)nextObject {
	id object = nil;
	
	@synchronized (self) {
		object = self.sequence.head;
		self.sequence = self.sequence.tail;
	}
	
	return object;
}

@end

@implementation YSRACSequence

#pragma mark Lifecycle

+ (YSRACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(YSRACSequence<id> *(^)(void))tailBlock {
	return [[YSRACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"];
}

#pragma mark Class cluster primitives

- (id)head {
	NSCAssert(NO, @"%s must be overridden by subclasses", __func__);
	return nil;
}

- (YSRACSequence *)tail {
	NSCAssert(NO, @"%s must be overridden by subclasses", __func__);
	return nil;
}

#pragma mark YSRACStream

+ (YSRACSequence *)empty {
	return YSRACEmptySequence.empty;
}

+ (YSRACSequence *)return:(id)value {
	return [YSRACUnarySequence return:value];
}

- (YSRACSequence *)bind:(YSRACSequenceBindBlock (^)(void))block {
	YSRACSequenceBindBlock bindBlock = block();
	return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name];
}

- (YSRACSequence *)bind:(YSRACSequenceBindBlock)bindBlock passingThroughValuesFromSequence:(YSRACSequence *)passthroughSequence {
	// Store values calculated in the dependency here instead, avoiding any kind
	// of temporary collection and boxing.
	//
	// This relies on the implementation of YSRACDynamicSequence synchronizing
	// access to its head, tail, and dependency, and we're only doing it because
	// we really need the performance.
	__block YSRACSequence *valuesSeq = self;
	__block YSRACSequence *current = passthroughSequence;
	__block BOOL stop = NO;

	YSRACSequence *sequence = [YSRACDynamicSequence sequenceWithLazyDependency:^ id {
		while (current.head == nil) {
			if (stop) return nil;

			// We've exhausted the current sequence, create a sequence from the
			// next value.
			id value = valuesSeq.head;

			if (value == nil) {
				// We've exhausted all the sequences.
				stop = YES;
				return nil;
			}

			current = (id)bindBlock(value, &stop);
			if (current == nil) {
				stop = YES;
				return nil;
			}

			valuesSeq = valuesSeq.tail;
		}

		NSCAssert([current isKindOfClass:YSRACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current);
		return nil;
	} headBlock:^(id _) {
		return current.head;
	} tailBlock:^ id (id _) {
		if (stop) return nil;

		return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail];
	}];

	sequence.name = self.name;
	return sequence;
}

- (YSRACSequence *)concat:(YSRACSequence *)sequence {
	NSCParameterAssert(sequence != nil);

	return [[[YSRACArraySequence sequenceWithArray:@[ self, sequence ] offset:0]
		flatten]
		setNameWithFormat:@"[%@] -concat: %@", self.name, sequence];
}

- (YSRACSequence *)zipWith:(YSRACSequence *)sequence {
	NSCParameterAssert(sequence != nil);

	return [[YSRACSequence
		sequenceWithHeadBlock:^ id {
			if (self.head == nil || sequence.head == nil) return nil;
			return YSRACTuplePack(self.head, sequence.head);
		} tailBlock:^ id {
			if (self.tail == nil || [[YSRACSequence empty] isEqual:self.tail]) return nil;
			if (sequence.tail == nil || [[YSRACSequence empty] isEqual:sequence.tail]) return nil;

			return [self.tail zipWith:sequence.tail];
		}]
		setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence];
}

#pragma mark Extended methods

- (NSArray *)array {
	NSMutableArray *array = [NSMutableArray array];
	for (id obj in self) {
		[array addObject:obj];
	}

	return [array copy];
}

- (NSEnumerator *)objectEnumerator {
	YSRACSequenceEnumerator *enumerator = [[YSRACSequenceEnumerator alloc] init];
	enumerator.sequence = self;
	return enumerator;
}

- (YSRACSignal *)signal {
	return [[self signalWithScheduler:[YSRACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name];
}

- (YSRACSignal *)signalWithScheduler:(YSRACScheduler *)scheduler {
	return [[YSRACSignal createSignal:^(id<YSRACSubscriber> subscriber) {
		__block YSRACSequence *sequence = self;

		return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) {
			if (sequence.head == nil) {
				[subscriber sendCompleted];
				return;
			}

			[subscriber sendNext:sequence.head];

			sequence = sequence.tail;
			reschedule();
		}];
	}] setNameWithFormat:@"[%@] -signalWithScheduler: %@", self.name, scheduler];
}

- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce {
	NSCParameterAssert(reduce != NULL);

	if (self.head == nil) return start;
	
	for (id value in self) {
		start = reduce(start, value);
	}
	
	return start;
}

- (id)foldRightWithStart:(id)start reduce:(id (^)(id, YSRACSequence *))reduce {
	NSCParameterAssert(reduce != NULL);

	if (self.head == nil) return start;
	
	YSRACSequence *rest = [YSRACSequence sequenceWithHeadBlock:^{
		if (self.tail) {
			return [self.tail foldRightWithStart:start reduce:reduce];
		} else {
			return start;
		}
	} tailBlock:nil];
	
	return reduce(self.head, rest);
}

- (BOOL)any:(BOOL (^)(id))block {
	NSCParameterAssert(block != NULL);

	return [self objectPassingTest:block] != nil;
}

- (BOOL)all:(BOOL (^)(id))block {
	NSCParameterAssert(block != NULL);
	
	NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) {
		return @(accumulator.boolValue && block(value));
	}];
	
	return result.boolValue;
}

- (id)objectPassingTest:(BOOL (^)(id))block {
	NSCParameterAssert(block != NULL);

	return [self filter:block].head;
}

- (YSRACSequence *)eagerSequence {
	return [YSRACEagerSequence sequenceWithArray:self.array offset:0];
}

- (YSRACSequence *)lazySequence {
	return self;
}

#pragma mark NSCopying

- (id)copyWithZone:(NSZone *)zone {
	return self;
}

#pragma mark NSCoding

- (Class)classForCoder {
	// Most sequences should be archived as YSRACArraySequences.
	return YSRACArraySequence.class;
}

- (id)initWithCoder:(NSCoder *)coder {
	if (![self isKindOfClass:YSRACArraySequence.class]) return [[YSRACArraySequence alloc] initWithCoder:coder];

	// Decoding is handled in YSRACArraySequence.
	return [super init];
}

- (void)encodeWithCoder:(NSCoder *)coder {
	[coder encodeObject:self.array forKey:@"array"];
}

#pragma mark NSFastEnumeration

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
	if (state->state == ULONG_MAX) {
		// Enumeration has completed.
		return 0;
	}

	// We need to traverse the sequence itself on repeated calls to this
	// method, so use the 'state' field to track the current head.
	YSRACSequence *(^getSequence)(void) = ^{
		return (__bridge YSRACSequence *)(void *)state->state;
	};

	void (^setSequence)(YSRACSequence *) = ^(YSRACSequence *sequence) {
		// Release the old sequence and retain the new one.
		CFBridgingRelease((void *)state->state);

		state->state = (unsigned long)CFBridgingRetain(sequence);
	};

	void (^complete)(void) = ^{
		// Release any stored sequence.
		setSequence(nil);
		state->state = ULONG_MAX;
	};

	if (state->state == 0) {
		// Since a sequence doesn't mutate, this just needs to be set to
		// something non-NULL.
		state->mutationsPtr = state->extra;

		setSequence(self);
	}

	state->itemsPtr = stackbuf;

	NSUInteger enumeratedCount = 0;
	while (enumeratedCount < len) {
		YSRACSequence *seq = getSequence();

		// Because the objects in a sequence may be generated lazily, we want to
		// prevent them from being released until the enumerator's used them.
		__autoreleasing id obj = seq.head;
		if (obj == nil) {
			complete();
			break;
		}

		stackbuf[enumeratedCount++] = obj;

		if (seq.tail == nil) {
			complete();
			break;
		}

		setSequence(seq.tail);
	}

	return enumeratedCount;
}

#pragma mark NSObject

- (NSUInteger)hash {
	return [self.head hash];
}

- (BOOL)isEqual:(YSRACSequence *)seq {
	if (self == seq) return YES;
	if (![seq isKindOfClass:YSRACSequence.class]) return NO;

	for (id<NSObject> selfObj in self) {
		id<NSObject> seqObj = seq.head;

		// Handles the nil case too.
		if (![seqObj isEqual:selfObj]) return NO;

		seq = seq.tail;
	}

	// self is now depleted -- the argument should be too.
	return (seq.head == nil);
}

@end
