#import "DataUtil.h"
#import "Constraints.h"

@implementation NSData (Util)
-(const void*) bytesNotNull {
    // note: this storage location is static, not auto, so its lifetime does not end
    // (also, by virtue of being const, there are no threading/entrancy issues)
    static const int SafeNonNullPointerToStaticStorageLocation[1];

    if ([self length] == 0) {
        return SafeNonNullPointerToStaticStorageLocation;
    } else {
        require([self bytes] != nil);
        return [self bytes];
    }
}
+(NSData*) dataWithLength:(NSUInteger)length {
    return [NSMutableData dataWithLength:length];
}

+(NSData*) dataWithSingleByte:(uint8_t)value{
    return [NSData dataWithBytes:&value length:sizeof(value)];
}
-(NSNumber*) tryFindIndexOf:(NSData*)subData {
    require(subData != nil);
    if ([subData length] > [self length]) return nil;
    
    NSUInteger subDataLength = [subData length];
    NSUInteger excessLength = [self length] - subDataLength;
    
    const uint8_t* selfBytes = [self bytes];
    const uint8_t* subDataBytes = [subData bytes];
    for (NSUInteger i = 0; i <= excessLength; i++) {
        if (memcmp(selfBytes+i, subDataBytes, subDataLength) == 0) {
            return [NSNumber numberWithUnsignedInteger:i];
        }
    }
    return nil;
}
-(NSString*) encodedAsHexString {
    if (![self bytes]) return @"";
    
    NSMutableString* result = [NSMutableString string];
    for (NSUInteger i = 0; i < [self length]; ++i)
        [result appendString:[NSString stringWithFormat:@"%02x", [self uint8At:i]]];
    
    return result;
}
-(NSString*) decodedAsUtf8 {
    // workaround for empty data having nil bytes
    if ([self length] == 0) return @"";
    
    [NSString stringWithUTF8String:[self bytes]];
    NSString* result = [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
    checkOperationDescribe(result != nil, @"Invalid UTF8 data.");
    return result;
}
-(NSString*) decodedAsAscii {
    // workaround for empty data having nil bytes
    if ([self length] == 0) return @"";
    // workaround for initWithData not enforcing the fact that NSASCIIStringEncoding means strict 7-bit
    for (NSUInteger i = 0; i < [self length]; i++) {
        checkOperationDescribe(([self uint8At:i] & 0x80) == 0, @"Invalid ascii data.");
    }
    
    NSString* result = [[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding];
    checkOperationDescribe(result != nil, @"Invalid ascii data.");
    return result;
}
-(NSString*) decodedAsAsciiReplacingErrorsWithDots {
    const int MinPrintableChar = ' ';
    const int MaxPrintableChar = '~';
    
    NSMutableData* d = [NSMutableData dataWithLength:[self length]];
    for (NSUInteger i = 0; i < [self length]; i++) {
        uint8_t v = [self uint8At:i];
        if (v < MinPrintableChar || v > MaxPrintableChar) v = '.';
        [d setUint8At:i to:v];
    }
    return [d decodedAsAscii];
}

-(NSData*) skip:(NSUInteger)offset {
    require(offset <= [self length]);
    return [self subdataWithRange:NSMakeRange(offset, [self length] - offset)];
}
-(NSData*) take:(NSUInteger)takeCount {
    require(takeCount <= [self length]);
    return [self subdataWithRange:NSMakeRange(0, takeCount)];
}
-(NSData*) skipLast:(NSUInteger)skipLastCount {
    require(skipLastCount <= [self length]);
    return [self subdataWithRange:NSMakeRange(0, [self length] - skipLastCount)];
}
-(NSData*) takeLast:(NSUInteger)takeLastCount {
    require(takeLastCount <= [self length]);
    return [self subdataWithRange:NSMakeRange([self length] - takeLastCount, takeLastCount)];
}

-(NSData*) subdataVolatileWithRange:(NSRange)range {
    NSUInteger length = [self length];
    require(range.location <= length);
    require(range.length <= length);
    require(range.location + range.length <= length);
    
    return [NSData dataWithBytesNoCopy:(uint8_t*)[self bytes] + range.location length:range.length freeWhenDone:NO];
}
-(NSData*) skipVolatile:(NSUInteger)offset {
    require(offset <= [self length]);
    return [self subdataVolatileWithRange:NSMakeRange(offset, [self length] - offset)];
}
-(NSData*) takeVolatile:(NSUInteger)takeCount {
    require(takeCount <= [self length]);
    return [self subdataVolatileWithRange:NSMakeRange(0, takeCount)];
}
-(NSData*) skipLastVolatile:(NSUInteger)skipLastCount {
    require(skipLastCount <= [self length]);
    return [self subdataVolatileWithRange:NSMakeRange(0, [self length] - skipLastCount)];
}
-(NSData*) takeLastVolatile:(NSUInteger)takeLastCount {
    require(takeLastCount <= [self length]);
    return [self subdataVolatileWithRange:NSMakeRange([self length] - takeLastCount, takeLastCount)];
}

-(uint8_t) highUint4AtByteOffset:(NSUInteger)offset {
    return [self uint8At:offset] >> 4;
}
-(uint8_t) lowUint4AtByteOffset:(NSUInteger)offset {
    return [self uint8At:offset] & 0xF;
}
-(uint8_t) uint8At:(NSUInteger)offset {
    require(offset < [self length]);
    return ((const uint8_t*)[self bytes])[offset];
}
-(const uint8_t*) constPtrToUint8At:(NSUInteger)offset {
    return ((uint8_t*)[self bytes]) + offset;
}
-(NSString*) encodedAsBase64 {
    const NSUInteger BitsPerBase64Word = 6;
    const NSUInteger BitsPerByte = 8;
    const uint8_t Base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    NSUInteger byteCount = [self length];
    NSUInteger bitCount = byteCount*BitsPerByte;
    NSUInteger base64WordCount = bitCount / BitsPerBase64Word;
    if (base64WordCount * BitsPerBase64Word < bitCount) base64WordCount += 1;
    
    // base 256 to to base 2
    bool bits[bitCount];
    for (NSUInteger i = 0; i < byteCount; i++) {
        for (NSUInteger j = 0; j < BitsPerByte; j++) {
            bits[i*BitsPerByte + BitsPerByte - 1 - j] = (([self uint8At:i] >> j) & 1) != 0;
        }
    }
    
    // base 2 to base 64
    uint8_t base64Words[base64WordCount];
    for (NSUInteger i = 0; i < base64WordCount; i++) {
        base64Words[i] = 0;
        for (NSUInteger j = 0; j < BitsPerBase64Word; j++) {
            NSUInteger offset = i*BitsPerBase64Word + BitsPerBase64Word - 1 - j;
            if (offset >= bitCount) continue; // default to 0
            if (bits[offset]) base64Words[i] |= 1 << j;
        }
    }
    
    // base 64 to ASCII data
    NSUInteger paddingCount = bitCount % 3;
    NSMutableData* asciiData = [NSMutableData dataWithLength:base64WordCount+paddingCount];
    for (NSUInteger i = 0; i < base64WordCount; i++) {
        [asciiData setUint8At:i to:Base64Chars[base64Words[i]]];
    }
    for (NSUInteger i = 0; i < paddingCount; i++) {
        [asciiData setUint8At:i+base64WordCount to:'='];
    }
    
    return [asciiData decodedAsAscii];
}
@end

@implementation NSMutableData (Util)
-(void) setUint8At:(NSUInteger)offset to:(uint8_t)newValue {
    require(offset < [self length]);
    ((uint8_t*)[self mutableBytes])[offset] = newValue;
}
-(void) replaceBytesStartingAt:(NSUInteger)offset withData:(NSData*)data {
    require(data != nil);
    require(offset + [data length] <= [self length]);
    [self replaceBytesInRange:NSMakeRange(offset, [data length]) withBytes:[data bytes]];
}
@end
