//
//  YSRACTuple.h
//  ReactiveObjC
//
//  Created by Josh Abernathy on 4/12/12.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "YSRACmetamacros.h"

@class YSRACSequence;

/// Creates a new tuple with the given values. At least one value must be given.
/// Values can be nil.
#define YSRACTuplePack(...) \
    YSRACTuplePack_(__VA_ARGS__)

/// Declares new object variables and unpacks a YSRACTuple into them.
///
/// This macro should be used on the left side of an assignment, with the
/// tuple on the right side. Nothing else should appear on the same line, and the
/// macro should not be the only statement in a conditional or loop body.
///
/// If the tuple has more values than there are variables listed, the excess
/// values are ignored.
///
/// If the tuple has fewer values than there are variables listed, the excess
/// variables are initialized to nil.
///
/// Examples
///
///   YSRACTupleUnpack(NSString *string, NSNumber *num) = [YSRACTuple tupleWithObjects:@"foo", @5, nil];
///   NSLog(@"string: %@", string);
///   NSLog(@"num: %@", num);
///
///   /* The above is equivalent to: */
///   YSRACTuple *t = [YSRACTuple tupleWithObjects:@"foo", @5, nil];
///   NSString *string = t[0];
///   NSNumber *num = t[1];
///   NSLog(@"string: %@", string);
///   NSLog(@"num: %@", num);
#define YSRACTupleUnpack(...) \
        YSRACTupleUnpack_(__VA_ARGS__)

@class YSRACTwoTuple<__covariant First, __covariant Second>;
@class YSRACThreeTuple<__covariant First, __covariant Second, __covariant Third>;
@class YSRACFourTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth>;
@class YSRACFiveTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth, __covariant Fifth>;

NS_ASSUME_NONNULL_BEGIN

/// A sentinel object that represents nils in the tuple.
///
/// It should never be necessary to create a tuple nil yourself. Just use
/// +tupleNil.
@interface YSRACTupleNil : NSObject <NSCopying, NSCoding>
/// A singleton instance.
+ (YSRACTupleNil *)tupleNil;
@end


/// A tuple is an ordered collection of objects. It may contain nils, represented
/// by YSRACTupleNil.
@interface YSRACTuple : NSObject <NSCoding, NSCopying, NSFastEnumeration>

@property (nonatomic, readonly) NSUInteger count;

/// These properties all return the object at that index or nil if the number of 
/// objects is less than the index.
@property (nonatomic, readonly, nullable) id first;
@property (nonatomic, readonly, nullable) id second;
@property (nonatomic, readonly, nullable) id third;
@property (nonatomic, readonly, nullable) id fourth;
@property (nonatomic, readonly, nullable) id fifth;
@property (nonatomic, readonly, nullable) id last;

/// Creates a new tuple out of the array. Does not convert nulls to nils.
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;

/// Creates a new tuple out of the array. If `convert` is YES, it also converts
/// every NSNull to YSRACTupleNil.
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert;

/// Creates a new tuple with the given objects. Use YSRACTupleNil to represent
/// nils.
+ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;

/// Returns the object at `index` or nil if the object is a YSRACTupleNil. Unlike
/// NSArray and friends, it's perfectly fine to ask for the object at an index
/// past the tuple's count - 1. It will simply return nil.
- (nullable id)objectAtIndex:(NSUInteger)index;

/// Returns an array of all the objects. YSRACTupleNils are converted to NSNulls.
- (NSArray *)allObjects;

/// Appends `obj` to the receiver.
///
/// obj - The object to add to the tuple. This argument may be nil.
///
/// Returns a new tuple.
- (__kindof YSRACTuple *)tupleByAddingObject:(nullable id)obj;

@end

@interface YSRACTuple (YSRACSequenceAdditions)

/// Returns a sequence of all the objects. YSRACTupleNils are converted to NSNulls.
@property (nonatomic, copy, readonly) YSRACSequence *ysrac_sequence;

@end

@interface YSRACTuple (ObjectSubscripting)
/// Returns the object at that index or nil if the number of objects is less
/// than the index.
- (nullable id)objectAtIndexedSubscript:(NSUInteger)idx;
@end

/// A tuple with exactly one generic value.
@interface YSRACOneTuple<__covariant First> : YSRACTuple

+ (instancetype)tupleWithObjects:(id)object, ... __attribute((unavailable("Use pack: instead.")));

- (YSRACTwoTuple<First, id> *)tupleByAddingObject:(nullable id)obj;

/// Creates a new tuple with the given values.
+ (YSRACOneTuple<First> *)pack:(nullable First)first;

@property (nonatomic, readonly, nullable) First first;

@end

/// A tuple with exactly two generic values.
@interface YSRACTwoTuple<__covariant First, __covariant Second> : YSRACTuple

+ (instancetype)tupleWithObjects:(id)object, ... __attribute((unavailable("Use pack:: instead.")));

- (YSRACThreeTuple<First, Second, id> *)tupleByAddingObject:(nullable id)obj;

/// Creates a new tuple with the given value.
+ (YSRACTwoTuple<First, Second> *)pack:(nullable First)first :(nullable Second)second;

@property (nonatomic, readonly, nullable) First first;
@property (nonatomic, readonly, nullable) Second second;

@end

/// A tuple with exactly three generic values.
@interface YSRACThreeTuple<__covariant First, __covariant Second, __covariant Third> : YSRACTuple

+ (instancetype)tupleWithObjects:(id)object, ... __attribute((unavailable("Use pack::: instead.")));

- (YSRACFourTuple<First, Second, Third, id> *)tupleByAddingObject:(nullable id)obj;

/// Creates a new tuple with the given values.
+ (instancetype)pack:(nullable First)first :(nullable Second)second :(nullable Third)third;

@property (nonatomic, readonly, nullable) First first;
@property (nonatomic, readonly, nullable) Second second;
@property (nonatomic, readonly, nullable) Third third;

@end

/// A tuple with exactly four generic values.
@interface YSRACFourTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth> : YSRACTuple

+ (instancetype)tupleWithObjects:(id)object, ... __attribute((unavailable("Use pack:::: instead.")));

- (YSRACFiveTuple<First, Second, Third, Fourth, id> *)tupleByAddingObject:(nullable id)obj;

/// Creates a new tuple with the given values.
+ (instancetype)pack:(nullable First)first :(nullable Second)second :(nullable Third)third :(nullable Fourth)fourth;

@property (nonatomic, readonly, nullable) First first;
@property (nonatomic, readonly, nullable) Second second;
@property (nonatomic, readonly, nullable) Third third;
@property (nonatomic, readonly, nullable) Fourth fourth;

@end

/// A tuple with exactly five generic values.
@interface YSRACFiveTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth, __covariant Fifth> : YSRACTuple

+ (instancetype)tupleWithObjects:(id)object, ... __attribute((unavailable("Use pack::::: instead.")));

/// Creates a new tuple with the given values.
+ (instancetype)pack:(nullable First)first :(nullable Second)second :(nullable Third)third :(nullable Fourth)fourth :(nullable Fifth)fifth;

@property (nonatomic, readonly, nullable) First first;
@property (nonatomic, readonly, nullable) Second second;
@property (nonatomic, readonly, nullable) Third third;
@property (nonatomic, readonly, nullable) Fourth fourth;
@property (nonatomic, readonly, nullable) Fifth fifth;

@end

/// This and everything below is for internal use only.
///
/// See YSRACTuplePack() and YSRACTupleUnpack() instead.
#define YSRACTuplePack_(...) \
    ([YSRACTuplePack_class_name(__VA_ARGS__) tupleWithObjectsFromArray:@[ metamacro_foreach(YSRACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])

#define YSRACTuplePack_object_or_ractuplenil(INDEX, ARG) \
    (ARG) ?: YSRACTupleNil.tupleNil,

/// Returns the class that should be used to create a tuple with the provided
/// variadic arguments to YSRACTuplePack_(). Supports up to 20 arguments.
#define YSRACTuplePack_class_name(...) \
        metamacro_at(20, __VA_ARGS__, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACTuple, YSRACFiveTuple, YSRACFourTuple, YSRACThreeTuple, YSRACTwoTuple, YSRACOneTuple)

#define YSRACTupleUnpack_(...) \
    metamacro_foreach(YSRACTupleUnpack_decl,, __VA_ARGS__) \
    \
    int YSRACTupleUnpack_state = 0; \
    \
    YSRACTupleUnpack_after: \
        ; \
        metamacro_foreach(YSRACTupleUnpack_assign,, __VA_ARGS__) \
        if (YSRACTupleUnpack_state != 0) YSRACTupleUnpack_state = 2; \
        \
        while (YSRACTupleUnpack_state != 2) \
            if (YSRACTupleUnpack_state == 1) { \
                goto YSRACTupleUnpack_after; \
            } else \
                for (; YSRACTupleUnpack_state != 1; YSRACTupleUnpack_state = 1) \
                    [YSRACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(YSRACTupleUnpack_value,, __VA_ARGS__) ] ]

#define YSRACTupleUnpack_state metamacro_concat(YSRACTupleUnpack_state, __LINE__)
#define YSRACTupleUnpack_after metamacro_concat(YSRACTupleUnpack_after, __LINE__)
#define YSRACTupleUnpack_loop metamacro_concat(YSRACTupleUnpack_loop, __LINE__)

#define YSRACTupleUnpack_decl_name(INDEX) \
    metamacro_concat(metamacro_concat(YSRACTupleUnpack, __LINE__), metamacro_concat(_var, INDEX))

#define YSRACTupleUnpack_decl(INDEX, ARG) \
    __strong id YSRACTupleUnpack_decl_name(INDEX);

#define YSRACTupleUnpack_assign(INDEX, ARG) \
    __strong ARG = YSRACTupleUnpack_decl_name(INDEX);

#define YSRACTupleUnpack_value(INDEX, ARG) \
    [NSValue valueWithPointer:&YSRACTupleUnpack_decl_name(INDEX)],

@interface YSRACTupleUnpackingTrampoline : NSObject

+ (instancetype)trampoline;
- (void)setObject:(nullable YSRACTuple *)tuple forKeyedSubscript:(NSArray *)variables;

@end

NS_ASSUME_NONNULL_END
