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

#import "RACTuple.h"
#import "EXTKeyPathCoding.h"
#import "RACTupleSequence.h"

@implementation RACTupleNil

+ (RACTupleNil *)tupleNil {
	static dispatch_once_t onceToken;
	static RACTupleNil *tupleNil = nil;
	dispatch_once(&onceToken, ^{
		tupleNil = [[self alloc] init];
	});
	
	return tupleNil;
}

#pragma mark NSCopying

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

#pragma mark NSCoding

- (id)initWithCoder:(NSCoder *)coder {
	// Always return the singleton.
	return self.class.tupleNil;
}

- (void)encodeWithCoder:(NSCoder *)coder {
}

@end


@interface RACTuple ()
@property (nonatomic, strong) NSArray *backingArray;
@end


@implementation RACTuple

- (instancetype)init {
	self = [super init];
	if (self == nil) return nil;
	
	self.backingArray = [NSArray array];
	
	return self;
}

- (NSString *)description {
	return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects];
}

- (BOOL)isEqual:(RACTuple *)object {
	if (object == self) return YES;
	if (![object isKindOfClass:self.class]) return NO;
	
	return [self.backingArray isEqual:object.backingArray];
}

- (NSUInteger)hash {
	return self.backingArray.hash;
}


#pragma mark NSFastEnumeration

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
	return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len];
}


#pragma mark NSCopying

- (instancetype)copyWithZone:(NSZone *)zone {
	// we're immutable, bitches!
	return self;
}


#pragma mark NSCoding

- (id)initWithCoder:(NSCoder *)coder {
	self = [self init];
	if (self == nil) return nil;
	
	self.backingArray = [coder decodeObjectForKey:@keypath(self.backingArray)];
	return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
	if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@keypath(self.backingArray)];
}


#pragma mark API

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
	return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
}

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert {
	RACTuple *tuple = [[self alloc] init];
	
	if (convert) {
		NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];
		for (id object in array) {
			[newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)];
		}
		
		tuple.backingArray = newArray;
	} else {
		tuple.backingArray = [array copy];
	}
	
	return tuple;
}

+ (instancetype)tupleWithObjects:(id)object, ... {
	RACTuple *tuple = [[self alloc] init];

	va_list args;
	va_start(args, object);

	NSUInteger count = 0;
	for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
		++count;
	}

	va_end(args);

	if (count == 0) {
		tuple.backingArray = @[];
		return tuple;
	}
	
	NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count];
	
	va_start(args, object);
	for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
		[objects addObject:currentObject];
	}

	va_end(args);
	
	tuple.backingArray = objects;
	return tuple;
}

- (id)objectAtIndex:(NSUInteger)index {
	if (index >= self.count) return nil;
	
	id object = [self.backingArray objectAtIndex:index];
	return (object == RACTupleNil.tupleNil ? nil : object);
}

- (NSArray *)allObjects {
	NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count];
	for (id object in self.backingArray) {
		[newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)];
	}
	
	return newArray;
}

- (instancetype)tupleByAddingObject:(id)obj {
	NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil];
	return [self.class tupleWithObjectsFromArray:newArray convertNullsToNils:NO];
}

- (NSUInteger)count {
	return self.backingArray.count;
}

- (id)first {
	return [self objectAtIndex:0];
}

- (id)second {
	return [self objectAtIndex:1];
}

- (id)third {
	return [self objectAtIndex:2];
}

- (id)fourth {
	return [self objectAtIndex:3];
}

- (id)fifth {
	return [self objectAtIndex:4];
}

- (id)last {
	return [self objectAtIndex:self.count - 1];
}

@end


@implementation RACTuple (RACSequenceAdditions)

- (RACSequence *)rac_sequence {
	return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0];
}

@end

@implementation RACTuple (ObjectSubscripting)

- (id)objectAtIndexedSubscript:(NSUInteger)idx {
	return [self objectAtIndex:idx];
}

@end


@implementation RACTupleUnpackingTrampoline

#pragma mark Lifecycle

+ (instancetype)trampoline {
	static dispatch_once_t onceToken;
	static id trampoline = nil;
	dispatch_once(&onceToken, ^{
		trampoline = [[self alloc] init];
	});
	
	return trampoline;
}

- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables {
	NSCParameterAssert(variables != nil);
	
	[variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) {
		__strong id *ptr = (__strong id *)value.pointerValue;
		*ptr = tuple[index];
	}];
}

@end
