//
//  TIPURLStringCoding.m
//  TwitterImagePipeline
//
//  Created on 8/12/16.
//  Copyright © 2020 Twitter. All rights reserved.
//

#import "TIP_Project.h"
#import "TIPURLStringCoding.h"

NS_ASSUME_NONNULL_BEGIN

static const char kHexDigits[] = "0123456789ABCDEF";

NSString * __nullable TIPURLEncodeString(NSString * __nullable string)
{
    if (0 == string.length) {
        return @"";
    }

    const char *stringAsUTF8 = [string UTF8String];
    if (stringAsUTF8 == NULL) {
        NSMutableString *byteString = [[NSMutableString alloc] init];
        for (NSUInteger i = 0; i < string.length; i++) {
            unichar uchar = [string characterAtIndex:i];
            [byteString appendFormat:@"%02X ", uchar];
        }
        TIPLogError(@"No UTF8String for NSString:\n%@", byteString);
        return nil;
    }

    NSUInteger encodedLength = 0;
    BOOL needsEncoding = NO;
    for (const char *c = stringAsUTF8; *c; c++) {
        switch (*c) {
            case '0' ... '9':
            case 'A' ... 'Z':
            case 'a' ... 'z':
            case '-':
            case '.':
            case '_':
            case '~':
                encodedLength++;
                break;
            default:
                encodedLength += 3;
                needsEncoding = YES;
                break;
        }
    }
    if (!needsEncoding) {
        return string;
    }

    char *encodedBytes = malloc(encodedLength);
    if (NULL == encodedBytes) {
        TIPLogError(@"Out of memory");
        return nil;
    }

    char *outPtr = encodedBytes;
    for (const unsigned char *c = (const unsigned char *)stringAsUTF8; *c; c++) {
        switch (*c) {
            case '0' ... '9':
            case 'A' ... 'Z':
            case 'a' ... 'z':
            case '-':
            case '.':
            case '_':
            case '~':
                *outPtr++ = (char)*c;
                break;
            default:
                *outPtr++ = '%';
                *outPtr++ = kHexDigits[(*c>>4)&0xf];
                *outPtr++ = kHexDigits[*c&0xf];
                break;
        }
    }

    NSString *encodedString = [[NSString alloc] initWithBytesNoCopy:encodedBytes length:encodedLength encoding:NSASCIIStringEncoding freeWhenDone:YES];
    if (encodedString == nil) {
        TIPLogError(@"Can't create encodedString from '%@'", string);
        free(encodedBytes);
    }
    return encodedString;
}

NSString * __nullable TIPURLDecodeString(NSString * __nullable string, BOOL replacePlussesWithSpaces)
{
    NSString *s = string;
    // replace the '+' first since if the '+' is encoded we want to preserve its value
    if (replacePlussesWithSpaces) {
        s = [s stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    }

    // the deprecated [s stringByReplacingPercentExcapesUsingEncoding:NSUTF8StringEncoding]
    // used to return @"" for @"" .  the replacement method returns nil.
    //
    // by checking length, we preserve the old behavior for this caller in case anything depended upon it.
    return s.length ? [s stringByRemovingPercentEncoding] : s;
}

NS_ASSUME_NONNULL_END

