//
//  FormProcessor.m
//

#import "FormProcessor.h"
#import "NSStringPunycodeAdditions.h"

@protocol ConcreteDataSource <NSObject>
- (void)setDataString:(NSString *)dataString;
@end

@interface NSString (MIMEAdditions)
+ (NSString*)MIMEBoundary;
+ (NSString*)multipartMIMEStringWithDictionary:(NSDictionary*)dict keys:(NSArray *)keys;
@end

@implementation NSString (MIMEAdditions)
//this returns a unique boundary which is used in constructing the multipart MIME body of the POST request
+ (NSString*)MIMEBoundary
{
	static NSString* MIMEBoundary = nil;
	if(!MIMEBoundary)
		MIMEBoundary = [[NSString alloc] initWithFormat:@"----_=_FormProcessor_%@_=_----",[[NSProcessInfo processInfo] globallyUniqueString]];
	return MIMEBoundary;
}
//this create a correctly structured multipart MIME body for the POST request from a dictionary
+ (NSString*)multipartMIMEStringWithDictionary:(NSDictionary*)dict keys:(NSArray *)keys
{
	NSMutableString* result = [NSMutableString string];
	for (NSString* key in keys)
	{
		[result appendFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n",[NSString MIMEBoundary],key,[dict objectForKey:key]];
	}
	[result appendFormat:@"--%@--\r\n",[NSString MIMEBoundary]];
	return result;
}
@end

@implementation NSDictionary (QueryStringEncoding)

- (NSString *)encodedQueryStringWithKeyOrder:(NSArray *)keys {
	NSDictionary * params = self;
	/*
	 
	 Convert an NSDictionary to a query string
	 
	 */
	
	NSMutableArray* pairs = [NSMutableArray array];
	for (NSString* key in keys) {
		id value = [params objectForKey:key];
		if ([value isKindOfClass:[NSDictionary class]]) {
			for (NSString *subKey in value) {
				NSString * escaped_value = [[value objectForKey:subKey] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
				[pairs addObject:[NSString stringWithFormat:@"%@[%@]=%@", key, subKey, escaped_value]];
			}
		} else if ([value isKindOfClass:[NSArray class]]) {
			for (NSString *subValue in value) {
				NSString * escaped_value = [subValue stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
				[pairs addObject:[NSString stringWithFormat:@"%@[]=%@", key, escaped_value]];
			}
		} else {
			NSString * escaped_value = [[params objectForKey:key] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
			[pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
		}
	}
	return [pairs componentsJoinedByString:@"&"];
}

@end


@implementation FormProcessor {
	NSDictionary * _allCollectedData;
	NSMutableDictionary * _collectedKeys;
	NSDictionary * _formElementNamesWithViewTag;
	NSURL * _destinationURL;
	UIView * _blockedView;
	UIView * _progressView;
	id	_onSuccessTarget, _onErrorTarget;
	SEL _onSuccessAction, _onErrorAction;
	NSOperationQueue * downloadOperationQueue;
}

- (id)init {
	self = [super init];
	
	return self;
}

- (void)awakeFromNib {
	[super awakeFromNib];
	if (!_formControls) _formControls = @[].mutableCopy;
}

- (void)process {
	[self collectData];
	[self sendData];
}

- (void)collectData {
	NSMutableDictionary * dict = @{}.mutableCopy;
	_collectedKeys = @{}.mutableCopy;
	
	for (NSString * elementName in _formElementNamesWithViewTag) {
		NSInteger tag = [_formElementNamesWithViewTag[elementName] intValue];
		UIView * formElementView = nil;
		for (UIView * view in self.formControls) {
			if (view.tag == tag) {
				formElementView = view; break;
			}
		}
		[self collectDataInView:formElementView into:dict withKey:elementName];
	}
	
	_allCollectedData = dict;
}

- (void)collectDataInView:(UIView *)view into:(NSDictionary *)dict withKey:(NSString *)key {
	
	id value = nil;
	
	if ([view isKindOfClass:[UITextField class]]) {
		value = ((UITextField *)view).text;
	}
	else if ([view isKindOfClass:[UITextView class]]) {
		value = ((UITextView *)view).text;
	}
	else if ([view isKindOfClass:[UISegmentedControl class]]) {
		NSInteger segmentIndex = ((UISegmentedControl *)view).selectedSegmentIndex;
		if (segmentIndex >= 0)
			value = [((UISegmentedControl *)view) titleForSegmentAtIndex:segmentIndex];
	}
	else if ([view isKindOfClass:[UISwitch class]]) {
		value = [NSNumber numberWithBool:((UISwitch *)view).on];
	}
	else if ([view isKindOfClass:[UISlider class]]) {
		value = [NSNumber numberWithFloat:((UISlider *)view).value];
	}
	else if ([view isKindOfClass:[UIPickerView class]]) {
		NSInteger selectedRow = [((UIPickerView *)view) selectedRowInComponent:0];
		value = [((UIPickerView *)view).delegate pickerView:((UIPickerView *)view) titleForRow:selectedRow forComponent:0];
	}
	else if ([view isKindOfClass:[UIDatePicker class]]) {
		UIDatePickerMode datePickerMode = ((UIDatePicker *)view).datePickerMode;
		if (datePickerMode == UIDatePickerModeCountDownTimer) {
			value = [NSNumber numberWithInteger:((UIDatePicker *)view).countDownDuration/60];
		}
		else if (datePickerMode == UIDatePickerModeTime) {
			NSDate * date = ((UIDatePicker *)view).date;
			//			NSCalendar * calendar = [NSCalendar currentCalendar];
			//			NSDateComponents * components = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:date];
			//			value = [NSString stringWithFormat:@"%02d:%02d", components.hour, components.minute];
			NSDateFormatter * timeFormatter = [[NSDateFormatter alloc] init];
			[timeFormatter setDateFormat:@"HH:mm"];
			value = [timeFormatter stringFromDate:date];
		}
		else if (datePickerMode == UIDatePickerModeDate) {
			NSDate * date = ((UIDatePicker *)view).date;
			NSCalendar * calendar = [NSCalendar currentCalendar];
			NSDateComponents * components = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:date];
			value = [NSString stringWithFormat:@"%d-%02d-%02d", (int)components.year, (int)components.month, (int)components.day];
		}
		else if (datePickerMode == UIDatePickerModeDateAndTime) {
			NSDate * date = ((UIDatePicker *)view).date;
			value = [NSNumber numberWithDouble:date.timeIntervalSince1970];
		}
		
	}
	else if ([view isKindOfClass:[UISearchBar class]]) {
		value = ((UISearchBar *)view).text;
	}
	
	if (value) {
		[dict setValue:value forKey:key];
		[_collectedKeys setObject:key forKey:[NSNumber numberWithInteger:view.tag]];
	}
	
}

- (void)sendData {
	if ([_sendMethod isEqualToString:@"post"]) {
		[self sendDataWithPOST];
	}
	else {
		[self sendDataWithGET];
	}
}

- (NSDictionary *)dictionaryToSend {
    NSMutableDictionary *ret = [_allCollectedData mutableCopy];
    [ret addEntriesFromDictionary:self.extraContent];
    return ret;
}

- (void)sendDataWithPOST {
	NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:_destinationURL];
	[request setHTTPMethod:@"POST"];
	[request addValue:@"8bit" forHTTPHeaderField:@"Content-Transfer-Encoding"];
	[request addValue: [NSString stringWithFormat:@"multipart/form-data; boundary=%@", NSString.MIMEBoundary] forHTTPHeaderField: @"Content-Type"];
	[request setHTTPBody:[[NSString multipartMIMEStringWithDictionary:self.dictionaryToSend keys:self.sortedKeys] dataUsingEncoding:NSUTF8StringEncoding]];
	[self sendRequest:request];
}

- (void)sendDataWithGET {
	char separator = ([_destinationURL.absoluteString rangeOfString:@"?"].location == NSNotFound) ? '?' : '&';
	NSURL * url = [NSURL URLWithString:[_destinationURL.absoluteString stringByAppendingFormat:@"%c%@", separator, [self.dictionaryToSend encodedQueryStringWithKeyOrder:self.sortedKeys]]];
	NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
	[request setHTTPMethod:@"GET"];
	[self sendRequest:request];
}

- (void)sendRequest:(NSURLRequest *)request {
	if (!downloadOperationQueue) downloadOperationQueue = [[NSOperationQueue alloc] init];
	else [downloadOperationQueue cancelAllOperations];
	
	[self blockUserInterfaceIfRequired];
	
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
	[NSURLConnection sendAsynchronousRequest:request queue:downloadOperationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
		[self handleResponse:response data:data error:connectionError];
	}];
#pragma GCC diagnostic pop
}

- (NSArray *)sortedKeys {
	
	NSArray *sortedKeys = [[_collectedKeys allKeys] sortedArrayUsingSelector: @selector(compare:)];
	NSMutableArray *sortedValues = [NSMutableArray array];
	for (NSString *key in sortedKeys)
		[sortedValues addObject: [_collectedKeys objectForKey: key]];
	
	if (self.extraContent) for (NSString * key in self.extraContent) {
		[sortedValues addObject:key];
	}
	
	return sortedValues;

}

- (BOOL)responseStatusIsOK:(NSURLResponse *)response {
	if (![response isKindOfClass:[NSHTTPURLResponse class]]) return YES;
	NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
	if (statusCode < 200 || statusCode > 308) return NO;
	return YES;
}

- (void)handleResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *)connectionError {
	[self performSelectorOnMainThread:@selector(unblockUserInterface) withObject:nil waitUntilDone:NO];
	if (connectionError || ![self responseStatusIsOK:response]) {
		NSLog(@"Error: %@", connectionError);
		if (_onErrorAction && _onErrorTarget) {
			// suppress compiler warning
			_Pragma("clang diagnostic push")
			_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")
			[_onErrorTarget performSelectorOnMainThread:_onErrorAction withObject:self waitUntilDone:NO];
			_Pragma("clang diagnostic pop")
		}
		else {
			if (connectionError) {
				dispatch_async(dispatch_get_main_queue(), ^{
					NSError * underlyingError = [connectionError.userInfo objectForKey:NSUnderlyingErrorKey];
					NSString * title = [connectionError localizedDescription];
					NSString * message = @"";
					if (underlyingError) {
						message = [underlyingError localizedDescription];
						if ([message isEqualToString:title]) message = @"";
					}
					UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
					[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", nil) style:UIAlertActionStyleDefault handler:nil]];
					[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:alertController animated:YES completion:nil];
				});
			}
		}
		return;
	}
	if (self.dataSourceForResponse && [self.dataSourceForResponse respondsToSelector:@selector(setDataString:)]) {
		NSString * dataString = [NSString.alloc initWithData:data encoding:NSUTF8StringEncoding];
		[self.dataSourceForResponse performSelectorOnMainThread:@selector(setDataString:) withObject:dataString waitUntilDone:NO];
	}
	if (_onSuccessAction && _onSuccessTarget) {
		// suppress compiler warning
		_Pragma("clang diagnostic push")
		_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")
		[_onSuccessTarget performSelectorOnMainThread:_onSuccessAction withObject:self waitUntilDone:NO];
		_Pragma("clang diagnostic pop")
	}
}

- (void)blockUserInterfaceIfRequired {
	if (!_blocksUserInteractionWhileTransmitting) return;
	UIWindow * keyWindow = [UIApplication sharedApplication].keyWindow;
	[keyWindow endEditing:YES];
	keyWindow.userInteractionEnabled = NO;
	_blockedView = keyWindow;
	[self showProgressViewInView:_blockedView];
}

- (void)unblockUserInterface {
	if (!_blockedView) return;
	//_blockedView.layer.opacity = 1;
	_blockedView.userInteractionEnabled = YES;
	[self hideProgressView];
	_blockedView = nil;
}

- (void)showProgressViewInView:(UIView *)blockedView {
	if (_progressView) return;
	
	_progressView = [UIToolbar.alloc initWithFrame:CGRectMake(0,0,80,80)];
	((UIToolbar *)_progressView).barStyle = UIBarStyleBlackTranslucent;

	_progressView.layer.cornerRadius = 10;
	_progressView.opaque = NO;
	_progressView.clipsToBounds = YES;
	
	UIActivityIndicatorView * av = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
	av.center = CGPointMake(_progressView.frame.size.width/2, _progressView.frame.size.height/2);
	[_progressView addSubview:av];
	_progressView.alpha = 0;
	[av startAnimating];
	_progressView.center = blockedView.center;
	[UIView animateWithDuration:0.5 animations:^{
		_progressView.alpha = 1;
		blockedView.alpha = 0.75;
	} completion:^(BOOL finished) {
	}];
	
	
	[blockedView addSubview:_progressView];
}

- (void)hideProgressView {
	if (!_progressView) return;
	UIView * __block progressView = _progressView;
	UIView * blockedView = _progressView.superview;
	_progressView = nil;
	[UIView animateWithDuration:0.2 animations:^{
		progressView.alpha = 0;
		blockedView.alpha = 1.0;
	} completion:^(BOOL finished) {
		[progressView removeFromSuperview];
		progressView = nil;
	}];
}

- (void)setFormElementNamesWithViewTagJSONString:(NSString *)jsonString {
	_formElementNamesWithViewTag = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:0];
}

- (void)setDestinationURLString:(NSString *)urlString {
	_destinationURL = [NSURL URLWithUnicodeString:urlString];
}

- (void)addTarget:(id)target action:(SEL)action forEvents:(NSString *)eventIdentifier {
	if ([eventIdentifier isEqualToString:@"success"]) {
		_onSuccessTarget = target;
		_onSuccessAction = action;
	}
}

- (void)removeTarget:(id)target {
	if (target == _onSuccessTarget) {
		_onSuccessTarget = nil;
	}
}

@end
