#import "CPSUIWebViewScriptObject.h"

static NSString * decodeURIComponent(NSString * _self)
{
	NSString *result = [_self stringByReplacingOccurrencesOfString:@"+" withString:@" "];
	result = [result stringByRemovingPercentEncoding];
	return result;
}


@interface CPSUIWebViewScriptObject () <UIWebViewDelegate>
@end

@implementation CPSUIWebViewScriptObject {
	UIWebView * _webView;
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
	if ([request.URL.scheme isEqualToString:@"call"]) {
		NSArray * pieces = [[[request.URL absoluteString] substringFromIndex:request.URL.scheme.length+1] componentsSeparatedByString:@","];
		[self handleCall:decodeURIComponent(pieces[0]) :decodeURIComponent(pieces[1]) :decodeURIComponent(pieces[2])];
		[webView stringByEvaluatingJavaScriptFromString:@"object.canCall = true; object.checkCallQueue();"];
	}
	return NO;
}

- (UIWebView *)webView {
	if (_webView == nil) {
		_webView = [[UIWebView alloc] init];
		_webView.delegate = self;
		[_webView stringByEvaluatingJavaScriptFromString:@"\
		 window.ScriptObject = function () { this.callQueue = []; this.canCall = true; };\
		 ScriptObject.prototype.setValueForKey = function (value, key) { this[key] = value; };\
		 ScriptObject.prototype.valueForKey = function (key) { return this[key]; };\
		 ScriptObject.prototype.call = function (receiver, message, arg) {\
			this.callQueue.push([receiver, message, arg]);\
		 	this.checkCallQueue();\
		 };\
		 ScriptObject.prototype.checkCallQueue = function () {\
			if (this.canCall !== true || this.callQueue.length === 0) return;\
			this.canCall = false; var front = this.callQueue.shift();  \
			window.location = 'call:'+front[0]+','+front[1]+','+front[2];\
		 };\
		 "];
		[self instantiateScriptObject];
	}
	return _webView;
}

- (void)instantiateScriptObject {
	NSString * prototypeName = self.prototypeName;
	if (prototypeName.length > 0) {
		[self.webView stringByEvaluatingJavaScriptFromString:self.script];
		[self.webView stringByEvaluatingJavaScriptFromString:[@"window.object = new " stringByAppendingString:prototypeName]];
	}
}


- (void)awakeFromNib {
	[super awakeFromNib];
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
	NSString * enquotedKey = [NSString stringWithFormat:@"\"%@\"", key];
	NSString * valueString;
	
	if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
		NSError * error = nil;
		NSData * jsonData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error];
		if (error) {
			NSLog(@"Error setting %@: The value is not JSON compliant.", key);
			return;
		}
		valueString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
	}
	else if ([value isKindOfClass:[NSString class]]) {
		
		// NSJSONSerialization doesn't accept strings as root objects.
		// We encapsulate the string.
		
		NSData * jsonData = [NSJSONSerialization dataWithJSONObject:@{@"value":value} options:0 error:nil];
		valueString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] stringByAppendingString:@".value"];
		
	}
	else if ([value isKindOfClass:[NSNumber class]]) {
		valueString = [NSString stringWithFormat:@"%f", [value doubleValue]];
	}
	else {
		NSLog(@"Warning: Setting %@: The value is not a string, number, array or dictionary.", key);
		valueString = @"null";
	}
	
	NSString * code = [NSString stringWithFormat:@"object.setValueForKey(%@, %@)", valueString, enquotedKey];
	[self.webView stringByEvaluatingJavaScriptFromString:code];
}

- (id)evaluate:(NSString *)c {
	NSString * code = [NSString stringWithFormat:@"JSON.stringify({result: (%@)})", c];
	NSString * string = [self.webView stringByEvaluatingJavaScriptFromString:code];
	NSError * error = nil;
	id result = [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];
	if ([result isKindOfClass:[NSDictionary class]]) {
		return [result objectForKey:@"result"];
	}
	return nil;
}

- (id)valueForUndefinedKey:(NSString *)key {
	NSString * enquotedKey = [NSString stringWithFormat:@"\"%@\"", key];
	return [self evaluate:[NSString stringWithFormat:@"object.valueForKey(%@)", enquotedKey]];
}

- (id)invoke:(NSString *)methodName {
	return [self evaluate:[NSString stringWithFormat:@"object.%@()", methodName]];
}

#pragma mark - Data source

- (NSInteger)numberOfSections {
	return [[self invoke:@"numberOfSections"] integerValue];
}

- (NSInteger)numberOfItemsInSection:(NSInteger)section {
	return [[self invoke:@"numberOfItemsInSection"] integerValue];
}

- (NSDictionary *)datasetForItemAtIndexPath:(NSIndexPath *)indexPath {
	return [self invoke:@"datasetForItemAtIndexPath"];
}


#pragma mark - For subclassing

// These methods are expected to be overwritten by subclasses

- (void)handleCall:(NSString *)receiver 
				  :(NSString *)message
				  :(NSString *)arg {}

- (NSString *)prototypeName { return @""; }

- (NSString *)script { return @""; }


@end
