#import "UtilTest.h"
#import "Util.h"
#import "TestUtil.h"

@implementation UtilTest
-(void) testFloorMultiple {
    test([NumberUtil largestIntegerThatIsAtMost:0 andIsAMultipleOf:20] == 0);
    test([NumberUtil largestIntegerThatIsAtMost:1 andIsAMultipleOf:20] == 0);
    test([NumberUtil largestIntegerThatIsAtMost:5 andIsAMultipleOf:20] == 0);
    test([NumberUtil largestIntegerThatIsAtMost:15 andIsAMultipleOf:20] == 0);
    test([NumberUtil largestIntegerThatIsAtMost:19 andIsAMultipleOf:20] == 0);
    test([NumberUtil largestIntegerThatIsAtMost:20 andIsAMultipleOf:20] == 20);
    test([NumberUtil largestIntegerThatIsAtMost:21 andIsAMultipleOf:20] == 20);
}
-(void) testCeilingMultiple {
    test([NumberUtil smallestIntegerThatIsAtLeast:0 andIsAMultipleOf:20] == 0);
    test([NumberUtil smallestIntegerThatIsAtLeast:1 andIsAMultipleOf:20] == 20);
    test([NumberUtil smallestIntegerThatIsAtLeast:5 andIsAMultipleOf:20] == 20);
    test([NumberUtil smallestIntegerThatIsAtLeast:15 andIsAMultipleOf:20] == 20);
    test([NumberUtil smallestIntegerThatIsAtLeast:19 andIsAMultipleOf:20] == 20);
    test([NumberUtil smallestIntegerThatIsAtLeast:20 andIsAMultipleOf:20] == 20);
    test([NumberUtil smallestIntegerThatIsAtLeast:21 andIsAMultipleOf:20] == 40);
}

-(void) testArrayToUint8Data {
    test([[(@[]) toUint8Data] length] == 0);

    NSData* d = [@[@0, @1] toUint8Data];
    test([d length] == 2);
    test(((uint8_t*)[d bytes])[0] == 0);
    test(((uint8_t*)[d bytes])[1] == 1);
}
-(void) testArrayConcatDatas {
    NSData* d1 = [@[@0, @1] toUint8Data];
    NSData* d2 = [@[@3, @4] toUint8Data];
    NSData* d3 = [@[@6, @7] toUint8Data];
    test([[@[] concatDatas] isEqualToData:[(@[]) toUint8Data]]);
    test([[@[d1] concatDatas] isEqualToData:d1]);
    test([[(@[d1, d2, d3]) concatDatas] isEqualToData:[(@[@0, @1, @3, @4, @6, @7]) toUint8Data]]);
}

-(void) testDatadDecodedAsUtf8 {
    testThrows([[(@[@0xC3, @0x28]) toUint8Data] decodedAsUtf8]);
    
    NSString* ab = [[(@[@97, @98]) toUint8Data] decodedAsUtf8];
    NSString* ab0 = [[(@[@97, @98, @0]) toUint8Data] decodedAsUtf8];
    test([ab isEqualToString:@"ab"]);
    test([ab0 isEqualToString:@"ab\0"]);
    test(![ab0  isEqualToString:ab]);
}
-(void) testTryFindFirstIndexOf {
    NSData* d = [@[@0, @1, @2, @3, @4, @5] toUint8Data];
    NSData* d34 = [@[@3, @4] toUint8Data];
    NSData* d67 = [@[@6, @7] toUint8Data];
    NSData* d01 = [@[@0, @1] toUint8Data];
    NSData* d02 = [@[@0, @2] toUint8Data];
    
    test([[d tryFindIndexOf:[NSData data]] intValue] == 0);
    test([[d tryFindIndexOf:d] intValue] == 0);
    
    test([[d tryFindIndexOf:d01] intValue] == 0);
    test([d tryFindIndexOf:d02] == nil);
    test([[d tryFindIndexOf:d34] intValue] == 3);
    test([d34 tryFindIndexOf:d] == nil);
    test([d tryFindIndexOf:d67] == nil);
}
-(void) testDatadDecodedAsAscii {
    testThrows([[(@[@97, @0xAA]) toUint8Data] decodedAsAscii]);

    NSString* ab = [[(@[@97, @98]) toUint8Data] decodedAsAscii];
    NSString* ab0 = [[(@[@97, @98, @0]) toUint8Data] decodedAsAscii];
    test([ab isEqualToString:@"ab"]);
    test([ab0 isEqualToString:@"ab\0"]);
    test(![ab0  isEqualToString:ab]);
}
-(void) testDatadDecodedAsAsciiReplacingErrorsWithDots {
    test([[[(@[@97, @98]) toUint8Data] decodedAsAsciiReplacingErrorsWithDots] isEqualToString:@"ab"]);
    test([[[(@[@97, @98, @0, @127, @250]) toUint8Data] decodedAsAsciiReplacingErrorsWithDots] isEqualToString:@"ab..."]);
}
-(void) testDataSkip {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d skip:0] isEqualToData:d]);
    test([[d skip:1] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]);
    test([[d skip:3] isEqualToData:[@[@3] toUint8Data]]);
    test([[d skip:4] length] == 0);
    testThrows([d skip:5]);

    // stable
    NSMutableData* m = [NSMutableData dataWithLength:2];
    NSData* b = [m skip:1];
    NSData* b2 = [m skip:0];
    [m setUint8At:0 to:1];
    [m setUint8At:1 to:1];
    test([b uint8At:0] == 0);
    test([b2 uint8At:0] == 0);
}
-(void) testDataTake {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d take:0] length] == 0);
    test([[d take:1] isEqualToData:[(@[@0]) toUint8Data]]);
    test([[d take:3] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]);
    test([[d take:4] isEqualToData:d]);
    testThrows([d take:5]);
    
    // stable
    NSMutableData* m = [NSMutableData dataWithLength:2];
    NSData* b = [m take:1];
    NSData* b2 = [m take:2];
    [m setUint8At:0 to:1];
    [m setUint8At:1 to:1];
    test([b uint8At:0] == 0);
    test([b2 uint8At:0] == 0);
}
-(void) testDataSkipLast {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d skipLast:0] isEqualToData:d]);
    test([[d skipLast:1] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]);
    test([[d skipLast:3] isEqualToData:[@[@0] toUint8Data]]);
    test([[d skipLast:4] length] == 0);
    testThrows([d skipLast:5]);

    // stable
    NSMutableData* m = [NSMutableData dataWithLength:2];
    NSData* b = [m skipLast:1];
    NSData* b2 = [m skipLast:0];
    [m setUint8At:0 to:1];
    [m setUint8At:1 to:1];
    test([b uint8At:0] == 0);
    test([b2 uint8At:0] == 0);
}
-(void) testDataTakeLast {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d takeLast:0] length] == 0);
    test([[d takeLast:1] isEqualToData:[(@[@3]) toUint8Data]]);
    test([[d takeLast:3] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]);
    test([[d takeLast:4] isEqualToData:d]);
    testThrows([d takeLast:5]);

    // stable
    NSMutableData* m = [NSMutableData dataWithLength:2];
    NSData* b = [m takeLast:1];
    NSData* b2 = [m takeLast:2];
    [m setUint8At:0 to:1];
    [m setUint8At:1 to:1];
    test([b uint8At:0] == 0);
    test([b2 uint8At:0] == 0);
}

-(void) testCongruentDifferenceMod2ToThe16 {
    test([NumberUtil congruentDifferenceMod2ToThe16From:1 to:0xFFFF] == -2);
    test([NumberUtil congruentDifferenceMod2ToThe16From:1 to:10] == 9);
    test([NumberUtil congruentDifferenceMod2ToThe16From:0xFFFF to:1] == 2);
    test([NumberUtil congruentDifferenceMod2ToThe16From:0 to:0x8000] == -0x8000);
    test([NumberUtil congruentDifferenceMod2ToThe16From:0x8000 to:0] == -0x8000);
    test([NumberUtil congruentDifferenceMod2ToThe16From:0 to:0] == 0);
}
-(void) testSubdataVolatileAgainstReference {
    NSData* d = increasingData(10);
    for (NSUInteger i = 0; i < 10; i++) {
        for (NSUInteger j = 0; j < 10-i; j++) {
            NSData* s1 = [d subdataVolatileWithRange:NSMakeRange(i, j)];
            NSData* s2 = [d subdataWithRange:NSMakeRange(i, j)];
            test([s1 isEqualToData:s2]);
        }
    }
}
-(void) testSubdataVolatileErrorCases {
    NSData* d = increasingData(10);

    [d subdataVolatileWithRange:NSMakeRange(0, 0)];
    [d subdataVolatileWithRange:NSMakeRange(0, 10)];
    [d subdataVolatileWithRange:NSMakeRange(10, 0)];
    testThrows([d subdataVolatileWithRange:NSMakeRange(0, 11)]);
    testThrows([d subdataVolatileWithRange:NSMakeRange(11, 0)]);
    testThrows([d subdataVolatileWithRange:NSMakeRange(1, 10)]);
    testThrows([d subdataVolatileWithRange:NSMakeRange(10, 1)]);
    
    // potential wraparound cases
    testThrows([d subdataVolatileWithRange:NSMakeRange(NSUIntegerMax, 1)]);
    testThrows([d subdataVolatileWithRange:NSMakeRange(1, NSUIntegerMax)]);
}
-(void) testDataSkipVolatile {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d skipVolatile:0] isEqualToData:d]);
    test([[d skipVolatile:1] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]);
    test([[d skipVolatile:3] isEqualToData:[@[@3] toUint8Data]]);
    test([[d skipVolatile:4] length] == 0);
    testThrows([d skipVolatile:5]);
}
-(void) testDataTakeVolatile {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d takeVolatile:0] length] == 0);
    test([[d takeVolatile:1] isEqualToData:[(@[@0]) toUint8Data]]);
    test([[d takeVolatile:3] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]);
    test([[d takeVolatile:4] isEqualToData:d]);
    testThrows([d takeVolatile:5]);
}
-(void) testDataSkipLastVolatile {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d skipLastVolatile:0] isEqualToData:d]);
    test([[d skipLastVolatile:1] isEqualToData:[(@[@0, @1, @2]) toUint8Data]]);
    test([[d skipLastVolatile:3] isEqualToData:[@[@0] toUint8Data]]);
    test([[d skipLastVolatile:4] length] == 0);
    testThrows([d skipLastVolatile:5]);
}
-(void) testDataTakeLastVolatile {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([[d takeLastVolatile:0] length] == 0);
    test([[d takeLastVolatile:1] isEqualToData:[(@[@3]) toUint8Data]]);
    test([[d takeLastVolatile:3] isEqualToData:[(@[@1, @2, @3]) toUint8Data]]);
    test([[d takeLastVolatile:4] isEqualToData:d]);
    testThrows([d takeLastVolatile:5]);
}

-(void) testDataUint8At {
    NSData* d = [@[@0, @1, @2, @3] toUint8Data];
    test([d uint8At:0] == 0);
    test([d uint8At:1] == 1);
    test([d uint8At:2] == 2);
    test([d uint8At:3] == 3);
    testThrows([d uint8At:4]);
}
-(void) testDataSetUint8At {
    NSMutableData* d = [NSMutableData dataWithLength:4];
    [d setUint8At:0 to:11];
    [d setUint8At:1 to:12];
    [d setUint8At:2 to:13];
    [d setUint8At:3 to:14];
    testThrows([d setUint8At:4 to:15]);
    test([d isEqualToData:[(@[@11, @12, @13, @14]) toUint8Data]]);
}
-(void) testMutableDataReplaceBytesStartingAt {
    NSMutableData* d = [NSMutableData dataWithLength:6];
    NSData* d2 = [@[@1, @2, @3] toUint8Data];
    testThrows([d replaceBytesStartingAt:0 withData:nil]);
    testThrows([d replaceBytesStartingAt:4 withData:d2]);
    
    [d replaceBytesStartingAt:0 withData:d2];
    test([d isEqualToData:[(@[@1, @2, @3, @0, @0, @0]) toUint8Data]]);
    [d replaceBytesStartingAt:2 withData:d2];
    test([d isEqualToData:[(@[@1, @2, @1, @2, @3, @0]) toUint8Data]]);
    [d replaceBytesStartingAt:3 withData:d2];
    test([d isEqualToData:[(@[@1, @2, @1, @1, @2, @3]) toUint8Data]]);
}
-(void) testStringEncodedAsUtf8 {
    test([[@"ab" encodedAsUtf8] isEqualToData:[(@[@97, @98]) toUint8Data]]);
}
-(void) testStringEncodedAsAscii {
    test([[@"ab" encodedAsAscii] isEqualToData:[(@[@97, @98]) toUint8Data]]);
    testThrows([@"√" encodedAsAscii]);
}
-(void) testBase64EncodeKnown {
    test([[[@"" encodedAsUtf8] encodedAsBase64] isEqualToString:@""]);
    test([[[@"f" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zg=="]);
    test([[[@"fo" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm8="]);
    test([[[@"foo" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9v"]);
    test([[[@"foob" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9vYg=="]);
    test([[[@"fooba" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9vYmE="]);
    test([[[@"foobar" encodedAsUtf8] encodedAsBase64] isEqualToString:@"Zm9vYmFy"]);
}
-(void) testBase64DecodeKnown {
    test([[@"" encodedAsUtf8] isEqualToData:[@"" decodedAsBase64Data]]);
    test([[@"f" encodedAsUtf8] isEqualToData:[@"Zg==" decodedAsBase64Data]]);
    test([[@"fo" encodedAsUtf8] isEqualToData:[@"Zm8=" decodedAsBase64Data]]);
    test([[@"foo" encodedAsUtf8] isEqualToData:[@"Zm9v" decodedAsBase64Data]]);
    test([[@"foob" encodedAsUtf8] isEqualToData:[@"Zm9vYg==" decodedAsBase64Data]]);
    test([[@"fooba" encodedAsUtf8] isEqualToData:[@"Zm9vYmE=" decodedAsBase64Data]]);
    test([[@"foobar" encodedAsUtf8] isEqualToData:[@"Zm9vYmFy" decodedAsBase64Data]]);
}
-(void) testBase64Perturbed {
    for (NSUInteger i = 0; i < 100; i++) {
        uint32_t n = arc4random_uniform(10) + 10;
        uint8_t data[n];
        arc4random_buf(data, sizeof(data));
        NSData* d = [NSData dataWithBytes:data length:sizeof(data)];
        NSString* b = [d encodedAsBase64];
        NSData* d2 = [b decodedAsBase64Data];
        if (![d isEqualToData:d2]) {
            STFail([d description]);
        }
    }
}
-(void) testToRegex {
    testThrows([@"(" toRegularExpression]);
    NSRegularExpression* r = [@"a+b" toRegularExpression];
    test([r numberOfMatchesInString:@"a" options:NSMatchingAnchored range:NSMakeRange(0, 1)] == 0);
    test([r numberOfMatchesInString:@"b" options:NSMatchingAnchored range:NSMakeRange(0, 1)] == 0);
    test([r numberOfMatchesInString:@"ba" options:NSMatchingAnchored range:NSMakeRange(0, 1)] == 0);
    test([r numberOfMatchesInString:@"ab" options:NSMatchingAnchored range:NSMakeRange(0, 2)] == 1);
    test([r numberOfMatchesInString:@"aab" options:NSMatchingAnchored range:NSMakeRange(0, 3)] == 1);
    test([r numberOfMatchesInString:@"aabXBNSAUI" options:NSMatchingAnchored range:NSMakeRange(0, 3)] == 1);
    test([r numberOfMatchesInString:@"aacb" options:NSMatchingAnchored range:NSMakeRange(0, 3)] == 0);
}
-(void) testWithMatchesAgainstReplacedBy {
    test([[@"(555)-555-5555" withMatchesAgainst:[@"[^0-9+]" toRegularExpression] replacedBy:@""] isEqualToString:@"5555555555"]);
    test([[@"aaaaaa" withMatchesAgainst:[@"a" toRegularExpression] replacedBy:@""] isEqualToString:@""]);
    test([[@"aabaabaa" withMatchesAgainst:[@"b" toRegularExpression] replacedBy:@"wonder"] isEqualToString:@"aawonderaawonderaa"]);
}
-(void) testContainsAnyMatches {
    NSRegularExpression* r = [@"^\\+[0-9]{10,}" toRegularExpression];
    test([@"+5555555555" containsAnyMatches:r]);
    test([@"+6555595555" containsAnyMatches:r]);
    test([@"+65555555557+/few,pf" containsAnyMatches:r]);
    test(![@" +5555555555" containsAnyMatches:r]);
    test(![@"+555KL55555" containsAnyMatches:r]);
    test(![@"+1-555-555-5555" containsAnyMatches:r]);
    test(![@"1-(555)-555-5555" containsAnyMatches:r]);
}
-(void) testWithPrefixRemovedElseNull {
    test([[@"test" withPrefixRemovedElseNull:@""] isEqualToString:@"test"]);
    test([[@"test" withPrefixRemovedElseNull:@"t"] isEqualToString:@"est"]);
    test([[@"test" withPrefixRemovedElseNull:@"te"] isEqualToString:@"st"]);
    test([[@"test" withPrefixRemovedElseNull:@"tes"] isEqualToString:@"t"]);
    test([[@"test" withPrefixRemovedElseNull:@"test"] isEqualToString:@""]);
    test([@"test" withPrefixRemovedElseNull:@"test2"] == nil);
    test([@"test" withPrefixRemovedElseNull:@"a"] == nil);
    testThrows([@"test" withPrefixRemovedElseNull:nil]);
}
-(void) testToJson {
    test([[@{} encodedAsJson] isEqualToString:@"{}"]);
    test([[@{@"a":@"b"} encodedAsJson] isEqualToString:@"{\"a\":\"b\"}"]);
    test([[@{@"c":@5} encodedAsJson] isEqualToString:@"{\"c\":5}"]);
    test([[(@{@"a":@5,@"b":@YES}) encodedAsJson] isEqualToString:@"{\"a\":5,\"b\":true}"]);
    
    testThrows([@{@"ev": [@"a+b" toRegularExpression]} encodedAsJson]);
}
-(void) testFromJson {
    test([[@"{}" decodedAsJsonIntoDictionary] isEqualToDictionary:@{}]);
    test([[@"{\"a\":\"b\"}" decodedAsJsonIntoDictionary] isEqualToDictionary:@{@"a":@"b"}]);
    test([[@"{\"c\":5}" decodedAsJsonIntoDictionary] isEqualToDictionary:@{@"c":@5}]);
    test([[@"{\"a\":5,\"b\":true}" decodedAsJsonIntoDictionary] isEqualToDictionary:(@{@"a":@5,@"b":@YES})]);
    
    testThrows([@"" decodedAsJsonIntoDictionary]);
    testThrows([@"}" decodedAsJsonIntoDictionary]);
    testThrows([@"{{}" decodedAsJsonIntoDictionary]);
}
-(void) testRepresentedAsHexString {
    test([[[NSData data] encodedAsHexString] isEqualToString:@""]);
    test([[increasingData(17) encodedAsHexString] isEqualToString:@"000102030405060708090a0b0c0d0e0f10"]);
    test([[increasingDataFrom(256-16,16) encodedAsHexString] isEqualToString:@"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"]);
}
-(void) testDecodedAsHexData {
    test([[@"" decodedAsHexString] isEqualToData:[NSData data]]);
    test([[@"000102030405060708090a0b0c0d0e0f10" decodedAsHexString] isEqualToData:increasingData(17)]);
    test([[@"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" decodedAsHexString] isEqualToData:increasingDataFrom(256-16,16)]);
    testThrows([@"gg" decodedAsHexString]);
    testThrows([@"-1" decodedAsHexString]);
    testThrows([@"a" decodedAsHexString]);
    testThrows([@"-" decodedAsHexString]);
    testThrows([@"0" decodedAsHexString]);
}
-(void) testHasUnsignedIntegerValue {
    test([@0 hasUnsignedIntegerValue]);
    test([@1 hasUnsignedIntegerValue]);
    test([@0xFFFFFFFF hasUnsignedIntegerValue]);
    test(![@0x100000000 hasUnsignedIntegerValue]);
    test([[NSNumber numberWithDouble:pow(2, 31)] hasUnsignedIntegerValue]);
    test(![[NSNumber numberWithDouble:pow(2, 32)] hasUnsignedIntegerValue]);
    test(![@-1 hasUnsignedIntegerValue]);
    test(![@0.5 hasUnsignedIntegerValue]);
}
-(void) testHasUnsignedLongLongValue {
    test([@0 hasUnsignedLongLongValue]);
    test([@1 hasUnsignedLongLongValue]);
    test([@0xFFFFFFFFFFFFFFFF hasUnsignedLongLongValue]);
    test([[NSNumber numberWithDouble:pow(2, 63)] hasUnsignedLongLongValue]);
    test(![[NSNumber numberWithDouble:pow(2, 64)] hasUnsignedLongLongValue]);
    test(![@-1 hasUnsignedLongLongValue]);
    test(![@0.5 hasUnsignedLongLongValue]);
}
-(void) testHasLongLongValue {
    test([@0 hasLongLongValue]);
    test([@1 hasLongLongValue]);
    test([@-11 hasLongLongValue]);
    test([@LONG_LONG_MAX hasLongLongValue]);
    test([@LONG_LONG_MIN hasLongLongValue]);
    test(![@ULONG_LONG_MAX hasLongLongValue]);
    test([[NSNumber numberWithDouble:pow(2, 62)] hasLongLongValue]);
    test(![[NSNumber numberWithDouble:pow(2, 63)] hasLongLongValue]);
    test(![[NSNumber numberWithDouble:-pow(2, 64)] hasLongLongValue]);
    test(![@0.5 hasLongLongValue]);
}
-(void) tryParseAsUnsignedInteger {
    test([@"" tryParseAsUnsignedInteger] == nil);
    test([@"88ffhih" tryParseAsUnsignedInteger] == nil);
    test([@"0xA" tryParseAsUnsignedInteger] == nil);
    test([@"A" tryParseAsUnsignedInteger] == nil);
    test([@"4294967297" tryParseAsUnsignedInteger] == nil);
    test([@"123456789123456789123456789123456789" tryParseAsUnsignedInteger] == nil);
    test([@"-1" tryParseAsUnsignedInteger] == nil);
    test([@"-" tryParseAsUnsignedInteger] == nil);

    test([[@"0" tryParseAsUnsignedInteger] isEqual:@0]);
    test([[@"1" tryParseAsUnsignedInteger] isEqual:@1]);
    test([[@"25" tryParseAsUnsignedInteger] isEqual:@25]);
    test([[@"4294967296" tryParseAsUnsignedInteger] isEqual:@4294967296]);
}
@end
