//
//  JSONKeyMapper.m
//  JSONModel
//

#import "JSONKeyMapper.h"
#import <libkern/OSAtomic.h>

@interface JSONKeyMapper()
@property (nonatomic, strong) NSMutableDictionary *toJSONMap;
@property (nonatomic, assign) OSSpinLock lock;
@end

@implementation JSONKeyMapper

- (instancetype)init
{
    if (!(self = [super init]))
        return nil;

    _toJSONMap  = [NSMutableDictionary new];

    return self;
}

- (instancetype)initWithJSONToModelBlock:(JSONModelKeyMapBlock)toModel modelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
    return [self initWithModelToJSONBlock:toJSON];
}

- (instancetype)initWithModelToJSONBlock:(JSONModelKeyMapBlock)toJSON
{
    if (!(self = [self init]))
        return nil;

    __weak JSONKeyMapper *weakSelf = self;

    _modelToJSONKeyBlock = ^NSString *(NSString *keyName)
    {
        __strong JSONKeyMapper *strongSelf = weakSelf;

        id cached = strongSelf.toJSONMap[keyName];

        if (cached == [NSNull null])
            return nil;

        if (cached)
            return strongSelf.toJSONMap[keyName];

        NSString *result = toJSON(keyName);

        OSSpinLockLock(&strongSelf->_lock);
        strongSelf.toJSONMap[keyName] = result ? result : [NSNull null];
        OSSpinLockUnlock(&strongSelf->_lock);

        return result;
    };

    return self;
}

- (instancetype)initWithDictionary:(NSDictionary *)map
{
    NSDictionary *toJSON  = [JSONKeyMapper swapKeysAndValuesInDictionary:map];

    return [self initWithModelToJSONDictionary:toJSON];
}

- (instancetype)initWithModelToJSONDictionary:(NSDictionary *)toJSON
{
    if (!(self = [super init]))
        return nil;

    _modelToJSONKeyBlock = ^NSString *(NSString *keyName)
    {
        return [toJSON valueForKeyPath:keyName] ?: keyName;
    };

    return self;
}

+ (NSDictionary *)swapKeysAndValuesInDictionary:(NSDictionary *)dictionary
{
    NSArray *keys = dictionary.allKeys;
    NSArray *values = [dictionary objectsForKeys:keys notFoundMarker:[NSNull null]];

    return [NSDictionary dictionaryWithObjects:keys forKeys:values];
}

- (NSString *)convertValue:(NSString *)value isImportingToModel:(BOOL)importing
{
    return [self convertValue:value];
}

- (NSString *)convertValue:(NSString *)value
{
    return _modelToJSONKeyBlock(value);
}

+ (instancetype)mapperFromUnderscoreCaseToCamelCase
{
    return [self mapperForSnakeCase];
}

+ (instancetype)mapperForSnakeCase
{
    return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
    {
        NSMutableString *result = [NSMutableString stringWithString:keyName];
        NSRange range;

        // handle upper case chars
        range = [result rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]];
        while (range.location != NSNotFound)
        {
            NSString *lower = [result substringWithRange:range].lowercaseString;
            [result replaceCharactersInRange:range withString:[NSString stringWithFormat:@"_%@", lower]];
            range = [result rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]];
        }

        // handle numbers
        range = [result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
        while (range.location != NSNotFound)
        {
            NSRange end = [result rangeOfString:@"\\D" options:NSRegularExpressionSearch range:NSMakeRange(range.location, result.length - range.location)];

            // spans to the end of the key name
            if (end.location == NSNotFound)
                end = NSMakeRange(result.length, 1);

            NSRange replaceRange = NSMakeRange(range.location, end.location - range.location);
            NSString *digits = [result substringWithRange:replaceRange];
            [result replaceCharactersInRange:replaceRange withString:[NSString stringWithFormat:@"_%@", digits]];
            range = [result rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet] options:0 range:NSMakeRange(end.location + 1, result.length - end.location - 1)];
        }

        return result;
    }];
}

+ (instancetype)mapperForTitleCase
{
    return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
    {
        return [keyName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[keyName substringToIndex:1].uppercaseString];
    }];
}

+ (instancetype)mapperFromUpperCaseToLowerCase
{
    return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
    {
        return keyName.uppercaseString;
    }];
}

+ (instancetype)mapper:(JSONKeyMapper *)baseKeyMapper withExceptions:(NSDictionary *)exceptions
{
    NSDictionary *toJSON = [JSONKeyMapper swapKeysAndValuesInDictionary:exceptions];

    return [self baseMapper:baseKeyMapper withModelToJSONExceptions:toJSON];
}

+ (instancetype)baseMapper:(JSONKeyMapper *)baseKeyMapper withModelToJSONExceptions:(NSDictionary *)toJSON
{
    return [[self alloc] initWithModelToJSONBlock:^NSString *(NSString *keyName)
    {
        if (!keyName)
            return nil;

        if (toJSON[keyName])
            return toJSON[keyName];

        return baseKeyMapper.modelToJSONKeyBlock(keyName);
    }];
}

@end
