//
//  TNLAttemptMetaData.m
//  TwitterNetworkLayer
//
//  Created on 1/16/15.
//  Copyright © 2020 Twitter. All rights reserved.
//

#import "TNLAttemptMetaData_Project.h"

NS_ASSUME_NONNULL_BEGIN

static NSString * const kMetaDataDictionaryKey = @"metaDataDictionary";
static NSString * const kFinalKey = @"final";

@interface TNLAttemptMetaData ()
{
    NSDictionary<NSString *, id> *_metaDataDictionary;
    BOOL _final;
}
@end

@implementation TNLAttemptMetaData

- (instancetype)initWithMetaDataDictionary:(nullable NSDictionary<NSString *, id> *)dictionary
{
    if (self = [super init]) {
        _final = NO;
        _metaDataDictionary = [dictionary mutableCopy] ?: [NSMutableDictionary dictionary];
    }
    return self;
}

- (instancetype)init
{
    return [self initWithMetaDataDictionary:nil];
}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        _final = [aDecoder decodeBoolForKey:kFinalKey];
        NSDictionary *metaDataDictionary = [aDecoder decodeObjectOfClass:[NSDictionary class]
                                                                  forKey:kMetaDataDictionaryKey];
        _metaDataDictionary = (_final) ? [metaDataDictionary copy] : [metaDataDictionary mutableCopy];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:[_metaDataDictionary copy] forKey:kMetaDataDictionaryKey];
    [aCoder encodeBool:_final forKey:kFinalKey];
}

+ (BOOL)supportsSecureCoding
{
    return YES;
}

- (NSUInteger)hash
{
    return _metaDataDictionary.hash;
}

- (BOOL)isEqual:(id)object
{
    if (self == object) {
        return YES;
    }

    if ([object isKindOfClass:[TNLAttemptMetaData class]]) {
        return [_metaDataDictionary isEqualToDictionary:[object metaDataDictionary]];
    }

    return NO;
}

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

- (NSDictionary *)metaDataDictionary
{
    return _metaDataDictionary;
}

- (void)finalizeMetaData
{
    if (_final) {
        return;
    }

    _final = YES;
    _metaDataDictionary = [_metaDataDictionary copy];
}

- (NSDictionary<NSString *, id> *)dictionaryDescription
{
    NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithCapacity:_metaDataDictionary.count];
    [_metaDataDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
        if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
            d[key] = obj;
            return;
        }
        if ([obj isKindOfClass:[NSData class]]) {
            const NSUInteger length = [(NSData *)obj length];
            if (length <= 256) {
                d[key] = [(NSData *)obj base64EncodedStringWithOptions:0];
            } else {
                d[key] = [NSString stringWithFormat:@"NSData: %lu bytes", (unsigned long)length];
            }
            return;
        }
        // other types, just skip
    }];
    return d;
}

@end

// Helper macros for coding & printing object; saves us from key & value name typos.

#define OBJECT_FIELD(field, fieldUpper, type) \
- (nullable type *)field \
{ \
    return _metaDataDictionary[@#field]; \
} \
- (void)set##fieldUpper:(nullable type *)field \
{ \
    if (field == nil) { \
        [(NSMutableDictionary *)_metaDataDictionary removeObjectForKey:@#field]; \
    } else { \
        ((NSMutableDictionary *)_metaDataDictionary)[@#field] = [field copy]; \
    } \
} \
- (BOOL)has##fieldUpper \
{ \
    return _metaDataDictionary[@#field] != nil; \
} \

#define PRIMITIVE_FIELD(field, fieldUpper, type, getter) \
- (type)field \
{ \
    return [_metaDataDictionary[@#field] getter]; \
} \
- (void)set##fieldUpper:(type)field \
{ \
    ((NSMutableDictionary *)_metaDataDictionary)[@#field] = @(field); \
} \
- (BOOL)has##fieldUpper \
{ \
    return _metaDataDictionary[@#field] != nil; \
} \

@implementation TNLAttemptMetaData (HTTP)
// See TNLAttemptMetadata_Project.h for list of fields.
HTTP_FIELDS()
@end

#undef OBJECT_FIELD
#undef PRIMITIVE_FIELD

NS_ASSUME_NONNULL_END
