/* Copyright (c) 2011 Artjom Popov */

#import "NSString+Additions.h"
#import "NSData+Additions.h"
#import "NSDate+Additions.h"
#import <CommonCrypto/CommonHMAC.h>

typedef struct _EPCryptKey {
	const UInt8 *key; /* 128 bits */
	UInt32 delta;
	UInt32 rounds;
	UInt32 sum;
} EPCryptKey;

static EPCryptKey EPCryptKeyMake(const UInt8 *key)
{
	EPCryptKey ckey;
	
	ckey.key = key;
	ckey.delta = 0x9E3779B9;
	ckey.rounds = 32;
	ckey.sum = ckey.delta * ckey.rounds;
	
	return ckey;
}

static UInt8 *EPDecryptData(const UInt8 *data, UInt32 dataSize, EPCryptKey *key, UInt32 *size, UInt8 *buffer)
{
	if(!buffer) {
		*size = 0;
	}
	
	if(dataSize % sizeof(UInt64) == 0) {
		const UInt32 *d = (const UInt32 *)data, *e = (const UInt32 *)(data + dataSize), *k = (const UInt32 *)key->key;
		UInt32 i, rounds = key->rounds, sum = key->sum, delta = key->delta;
		UInt32 l = *size;
		
		while(d < e) {
			UInt32 *v;
			
			l = l + sizeof(UInt32) * 2;
			buffer = realloc(buffer, l);
			v = (UInt32 *)(buffer + l - sizeof(UInt32) * 2);
			v[0] = d[0];
			v[1] = d[1];
			
			for(i = 0; i < rounds; i++) {
				v[1] -= (((v[0] << 4) ^ (v[0] >> 5)) + v[0]) ^ (sum + k[(sum >> 11) & 3]);
				sum -= delta;
				v[0] -= (((v[1] << 4) ^ (v[1] >> 5)) + v[1]) ^ (sum + k[sum & 3]);
			}
			
			d += 2;
		}
		
		key->sum = sum;
		*size = l;
		
		return buffer;
	} else {
		NSLog(@"DecryptData: data size is invalid (%d)", dataSize);
	}
	
	return NULL;
}

#pragma mark -

@implementation NSString(Additions)

@dynamic javascriptString;

- (NSString *)javascriptString
{
	UniChar *buffer = malloc(sizeof(UniChar) * self.length);
	NSMutableString *str = [NSMutableString string];
	const UniChar *end = buffer + self.length;
	const UniChar *ptr = buffer;
	
	CFStringGetCharacters((CFStringRef)self, CFRangeMake(0, self.length), buffer);
	
	while(ptr < end) {
		if(*ptr <= 127 && !(*ptr == '\b' || *ptr == '"' || *ptr == '\'' || *ptr == '\n' || *ptr == '\\' || *ptr == '\f' || *ptr == '\t' || *ptr == '\r')) {
			CFStringAppendCharacters((CFMutableStringRef)str, ptr, 1);
		} else {
			CFStringAppendFormat((CFMutableStringRef)str, 0, CFSTR("\\u%04X"), *ptr);
		}
		
		ptr++;
	}
	
	free(buffer);
	
	return str;
}

- (NSString *)decodedStringUsingKey:(NSData *)key
{
	if(key.length == 16) {
		EPCryptKey ckey = EPCryptKeyMake(key.bytes);
		NSData *input = [NSData dataWithBase64EncodedString:self];
		UInt32 size = 0;
		UInt8 *bytes = EPDecryptData(input.bytes, input.length, &ckey, &size, NULL);
		NSString *output = (size > 0) ? [[[NSString alloc] initWithBytes:bytes length:size encoding:NSUTF8StringEncoding] autorelease] : nil;
		
		free(bytes);
		
		if(!output) {
			//NSLog(@"%@: Decryption was unsuccessful", self.class);
		}
		
		return output;
	} else if (self.length > 0 && [self characterAtIndex:0] == '@') {
		return self;
	} else if (self.length > 0 && [self characterAtIndex:0] == 'Q') {
		NSData *data = [NSData dataWithBase64EncodedString:self];
		return [[[NSString alloc] initWithBytes:data.bytes length:data.length encoding:NSUTF8StringEncoding] autorelease];
	} else {
		//NSLog(@"%@: Decryption key has invalid length (%d)", self.class, key.length);
	}
	
	return self;
}

@dynamic base64Value;

- (NSString *)base64Value
{
	return [self dataUsingEncoding:NSUTF8StringEncoding].base64Encoding;
}

@dynamic sha1String;

- (NSString *)sha1String
{
	NSMutableString *hexString = [NSMutableString string];
	const char *sourceString = self.UTF8String;
	unsigned char result[CC_SHA1_DIGEST_LENGTH];
	
	CC_SHA1(sourceString, strlen(sourceString), result);
	
	for(int i = 0; i < 20; i++) {
		[hexString appendFormat:@"%02X", result[i]];
	}
	
	return hexString.lowercaseString;
}

@dynamic sqlDateValue;

- (NSDate *)sqlDateValue
{
	return [NSGetSQLDateFormatter() dateFromString:self];
}

@dynamic sqlDateTimeValue;

- (NSDate *)sqlDateTimeValue
{
	NSDate *date = [NSGetSQLDateTimeFormatter() dateFromString:self];
	
	return (date) ? date : [NSGetSQLDateFormatter() dateFromString:self];
}

@dynamic soapDateTimeValue;

- (NSDate *)soapDateTimeValue
{
	return [NSGetDateTimeFormatter() dateFromString:self];
}

@dynamic trimmedString;

- (NSString *)trimmedString
{
	return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

@dynamic validEmailAddress;

- (BOOL)validEmailAddress
{
	NSString *regex =
	@"(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}"
	@"~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\"
	@"x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-"
	@"z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5"
	@"]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-"
	@"9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21"
	@"-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";

	return [[NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex] evaluateWithObject:self.lowercaseString];
}

- (NSString *)stringValue
{
	return self;
}

@end
