#import "Utility.h"
#import "Lang.h"
#import "SDK.h"
#import "BeanUtils.h"
#import "CocoaUtils.h"
#import "ContactsManager.h"
#import "DrawUtils.h"
#import "LocationManager.h"
#import "NetUtils.h"
#import "PrivacyUtils.h"
#import "UserNotificationsManager.h"
#import "Validator.h"
#import "ComboBoxViewController.h"
#import "MFMailComposeViewControllerDelegateImpl.h"
#import "NavigationItemSheet.h"
#import "TextViewController.h"
#import "UIConstants.h"
#import "UIToolkit.h"
#import "CardIOPaymentViewControllerDelegateImpl.h"
#import "DZNEmptyDataSetDefault.h"
#import "IQPreviousNextScrollView.h"
#import "NSObject+RestKit.h"
#import "NotificationApi.h"
#import "AnswersManager.h"
#import <ADAppRater/ADAppRater.h>
#import <AFNetworking/AFURLRequestSerialization.h>
#import <BlocksKit/BlocksKit+UIKit.h>
#import <BlocksKit/A2DynamicDelegate.h>
#import <BlocksKit/NSObject+A2DynamicDelegate.h>
#import <CRToast/CRToast.h>
#import <DZNWebViewController/DZNWebViewController.h>
#import <EBBannerView/EBBannerView.h>
#import <IQKeyboardManager/IQKeyboardManager.h>
#import <libextobjc/EXTConcreteProtocol.h>
#import <libextobjc/EXTScope.h>
#import <libPhoneNumber_iOS/NBPhoneNumberUtil.h>
#import <PPTopMostController/UIViewController+PPTopMostController.h>
#import <XMLDictionary/XMLDictionary.h>
#import <Valet/Valet.h>
#import "CustomTextFieldView.h"
#import "CustomDualTextFieldView.h"
#import "BookingFinishViewController.h"
#import "MailViewController.h"
#import "Vars.h"
#import "AppLocalConfig.h"

@interface UINavigationController (StatusBarStyle)
@end

@implementation UINavigationController (StatusBarStyle)

- (UIStatusBarStyle)preferredStatusBarStyle {
	UIColor *barTintColor = self.navigationBar.barTintColor;
	if ([self.topViewController isKindOfClass:NSClassFromString(@"MWPhotoBrowser")]) {
		barTintColor = [UIColor blackColor];
	}
	return [CocoaUtils contrastingStatusBarStyleForColor:barTintColor];
}

@end

@concreteprotocol(DZNEmptyDataSetDefault)

- (NSAttributedString *)titleForEmptyDataSet: (UIScrollView *)scrollView {
	return [[NSAttributedString alloc] im_initWithString:IMLocalizedString(@"No Results") attributes:@{ NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor lightGrayColor] }];
}

- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView {
	return [DrawUtils imageWithFAKIcon:[FAKFontAwesome fileOIconWithSize:100] fillColor:[UIColor mercuryColor]];
}

@end

@implementation Utility

+ (void)load {
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		IQKeyboardManager *manager = [IQKeyboardManager sharedManager];
		manager.shouldShowToolbarPlaceholder = NO;
		[manager.toolbarPreviousNextAllowedClasses addObject:[IQPreviousNextScrollView class]];
		NSMutableArray *disabledClasses = [NSMutableArray array];
		[disabledClasses addObject:[TextViewController class]];
		if (NSClassFromString(@"PPAuthViewController")) {
			[disabledClasses addObject:NSClassFromString(@"PPAuthViewController")];
		}
		if (NSClassFromString(@"CardIODataEntryViewController")) {
			[disabledClasses addObject:NSClassFromString(@"CardIODataEntryViewController")];
		}
		[manager.disabledToolbarClasses addObjectsFromArray:disabledClasses];
		[manager.disabledDistanceHandlingClasses addObjectsFromArray:disabledClasses];
	});
}

+ (CLLocationManager *)sharedLocationManager {
	static dispatch_once_t onceToken;
	static CLLocationManager *locationManager;
	dispatch_once(&onceToken, ^{
		locationManager = [[CLLocationManager alloc] init];
		locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
	});
	return locationManager;
}

+ (CLGeocoder *)sharedGeocoder {
	static dispatch_once_t onceToken;
	static CLGeocoder *geocoder;
	dispatch_once(&onceToken, ^{
		geocoder = [[CLGeocoder alloc] init];
	});
	return geocoder;
}

+ (XMLDictionaryParser *)sharedXMLDictionaryParser {
	static dispatch_once_t onceToken;
	static XMLDictionaryParser *parser;
	dispatch_once(&onceToken, ^{
		parser = [[XMLDictionaryParser alloc] init];
		parser.nodeNameMode = XMLDictionaryNodeNameModeNever;
		parser.attributesMode = XMLDictionaryAttributesModeUnprefixed;
	});
	return parser;
}

#pragma mark - Public

+ (EKEventStore *)sharedEventStore {
    static dispatch_once_t onceToken;
    static EKEventStore *eventStore;
    dispatch_once(&onceToken, ^{
        eventStore = [[EKEventStore alloc] init];
    });
    return eventStore;
}

+ (NSString *)fullImageNameByLaunchSyntaxFromAsset:(NSString *)setName {
	NSString *launchImageName = [CocoaUtils launchImageName];
	if (launchImageName) {
		return [launchImageName stringByReplacingOccurrencesOfString:@"LaunchImage" withString:setName];
	}
	return nil;
}

#pragma mark - PERL

+ (NSString *)serverFileUrl:(NSString *)relativePath {
	if ([Vars isLiveServer]) {
		return [[Vars liveServer] im_stringByAppendingString:relativePath];
	} else if ([Vars is_testing_server]) {
		return [[Vars testingServer] im_stringByAppendingString:relativePath];
	} else if ([Vars is_develop]) {
		return [[Vars developServer] im_stringByAppendingString:relativePath];
	}
	return nil;
}

+ (NSString *)perlCgiBin:(NSString *)cgiName {
	if ([Vars isLiveServer]) {
		return [NSString stringWithFormat:@"%@/cgi-bin/%@.fcgi", [Vars liveServer], cgiName];
	} else if ([Vars is_testing_server]) {
		return [NSString stringWithFormat:@"%@/cgi-bin/%@.cgi", [Vars testingServer], cgiName];
	} else if ([Vars is_develop]) {
		return [NSString stringWithFormat:@"%@/cgi-bin/%@.cgi", [Vars developServer], cgiName];
	}
	return nil;
}

#pragma mark - AttributedString

+ (NSAttributedString *)attributedDefaultAppendingWithString:(NSString *)string {
	NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] im_initWithString:string];
	[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:@" "]];
	[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:[NSString stringWithFormat:@"[%@]", IMLocalizedString(@"Default")] attributes:@{ NSForegroundColorAttributeName : [Vars defaultTintColor] }]];
	return [[NSAttributedString alloc] initWithAttributedString:attributedString];
}

+ (NSAttributedString *)attributedPriceWithNumber:(NSNumber *)number currencySymbol:(NSString *)currencySymbol integerDigitsFont:(UIFont *)integerDigitsFont fractionDigitsFont:(UIFont *)fractionDigitsFont textColor:(UIColor *)color{

    CGFloat integerDigitsFontCapHeight =   integerDigitsFont.capHeight;
    CGFloat fractionDigitsFontCapHeight = fractionDigitsFont.capHeight;
    CGFloat offset = integerDigitsFontCapHeight - fractionDigitsFontCapHeight;
    
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    formatter.numberStyle = NSNumberFormatterCurrencyStyle;
    formatter.maximumFractionDigits = 2;
	formatter.currencySymbol = currencySymbol;
    
    NSString *numberStr = [formatter stringFromNumber:number];
    NSArray *stringArray = [numberStr componentsSeparatedByString:formatter.decimalSeparator];

    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
    [attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:stringArray.firstObject attributes:@{NSForegroundColorAttributeName : color,NSFontAttributeName : integerDigitsFont}]];
    [attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:[formatter.decimalSeparator im_stringByAppendingString:stringArray.lastObject] attributes:@{NSForegroundColorAttributeName : color,NSFontAttributeName : fractionDigitsFont,NSBaselineOffsetAttributeName:@(offset)}]];
    return [[NSAttributedString alloc] initWithAttributedString:attributedString];
}

#pragma mark - JSON

+ (id)JSONObjectWithXMLString:(NSString *)xmlString objectMapping:(RKObjectMapping *)objectMapping {
	NSMutableDictionary *dict = [[self sharedXMLDictionaryParser] dictionaryWithString:xmlString];
	if (![NSJSONSerialization isValidJSONObject:dict]) {
		return @{};
	}
	[self flattenJSONObject:dict objectMapping:objectMapping];
	return [NSJSONSerialization JSONObjectWithData:[NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:NULL] options:0 error:NULL];
}

+ (id)modelWithJSONObject:(id)object withClass:(Class)modelClass {
	WebServiceResponseJsonMapper *mapper = [[WebServiceResponseJsonMapper alloc] initWithClass:modelClass];
	if (![mapper parseWithJSONObject:object]) {
		return mapper.response;
	}
	return nil;
}

#pragma mark - XML

+ (NSString *)normalizeXmlString:(NSString *)xmlString {
	if ([StringUtils isEmpty:xmlString]) {
		return xmlString;
	}
	xmlString = [StringEscapeUtils unescapeXml:xmlString];
	// xmlString = [CocoaUtils noDeclarationXmlString:xmlString];
	xmlString = [xmlString stringByReplacingOccurrencesOfString:@", <DATA> line" withString:@", DATA line"];
	return xmlString;
}

+ (NSString *)javaSOAPEnvelope:(NSString *)string name:(NSString *)name namespace:(NSString *)namespace {
	NSString *body = [NSString stringWithFormat:@"<%@ xmlns=\"%@\">%@</%@>", name, namespace, string, name];
	return [NSString stringWithFormat:@"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>%@</soap:Body></soap:Envelope>", body];
}

+ (NSDictionary *)perlParamXml:(NSString *)string action:(NSString *)action {
	return [NSDictionary dictionaryWithObjectsAndKeys:[CocoaUtils xmlStringWithValue:string forElement:action], @"ws_param_xml", action, @"ws_action", nil];
}

#pragma mark - HTML

+ (NSAttributedString *)attributedStringWithHtmlString:(NSString *)htmlString fixAttributes:(NSDictionary *)attributes{
	if (!htmlString) {
		return nil;
	}
	NSString *pattern = @"<b>(.*?)</b>";
	NSRegularExpression *regular = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
	NSArray *result = [regular matchesInString:htmlString options:0 range:NSMakeRange(0, htmlString.length)];
	NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:htmlString];
	for (NSTextCheckingResult *checkingResult in result) {
		NSRange range = checkingResult.range;
		UIFont *font = attributes[NSFontAttributeName];
		[attributedString setAttributes:@{NSFontAttributeName : [CocoaUtils renderedFont:font withSymbolicTraits:UIFontDescriptorTraitBold]} range:range];
	}
	
	[attributedString.mutableString replaceOccurrencesOfString:@"<b>" withString:@"" options:0 range:NSMakeRange(0, attributedString.mutableString.length)];
	[attributedString.mutableString replaceOccurrencesOfString:@"</b>" withString:@"" options:0 range:NSMakeRange(0, attributedString.mutableString.length)];
	
	return [[NSAttributedString alloc] initWithAttributedString:attributedString];
}

+ (NSString *)securityHtmlString:(NSString *)htmlString {
	if ([StringUtils isEmpty:htmlString]) {
		return htmlString;
	}
	NSMutableString *string = [NSMutableString im_stringWithString:htmlString];
	[string replaceOccurrencesOfString:@"http://" withString:@"https://" options:0 range:NSMakeRange(0, string.length)];
	if (![Vars isLiveServer]) {
		NSArray *domains = [[NSBundle mainBundle].infoDictionary[@"NSAppTransportSecurity"][@"NSExceptionDomains"] allKeys];
		for (NSString *domain in domains) {
			[string replaceOccurrencesOfString:[@"https://" stringByAppendingString:domain] withString:[@"http://" stringByAppendingString:domain] options:0 range:NSMakeRange(0, string.length)];
		}
	}
	return [NSString stringWithString:string];
}

#pragma mark - URL

+ (BOOL)canBeConvertedToUrlEncodedFromString:(NSString *)string {
	return [CocoaUtils canBeConvertedToUrlEncodedFromString:string usingEncoding:[Vars charset]];
}

#pragma mark - Number

+ (NSString *)currencyStringFromNumber:(CGFloat)currencyNumber currencyCode:(NSString *)currencyCode  {
	return [self currencyStringFromNumber:currencyNumber currencyCode:currencyCode currencyRate:0];
}

+ (NSString *)currencyStringFromNumber:(CGFloat)currencyNumber currencyCode:(NSString *)currencyCode currencyRate:(CGFloat)currencyRate {
	NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
	formatter.numberStyle = NSNumberFormatterCurrencyStyle;
	if ([currencyCode isEqualToString:@"cny"]) {
		if (currencyRate > 0) {
			formatter.multiplier = @(currencyRate);
		}
		formatter.maximumFractionDigits = 0;
		formatter.minimumFractionDigits = 0;
		formatter.locale = [NSLocale localeWithLocaleIdentifier:@"zh-Hans_CN"];
	} else {
		formatter.maximumFractionDigits = 2;
		formatter.minimumFractionDigits = 0;
		formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
	}
	return [formatter stringFromNumber:@(currencyNumber)];
}

+ (BOOL)isValidPhoneNumber:(NSString *)phoneNumber {
	NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
	NSError *error = nil;
	NBPhoneNumber *myNumber = [phoneUtil parse:phoneNumber defaultRegion:@"US" error:&error];
	return [phoneUtil isValidNumber:myNumber];
}

+ (NSString *)formattedPhoneNumber:(NSString *)phoneNumber {
	NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
	NSError *error = nil;
	NBPhoneNumber *myNumber = [phoneUtil parse:phoneNumber defaultRegion:@"US" error:&error];
	return [phoneUtil format:myNumber numberFormat:NBEPhoneNumberFormatRFC3966 error:&error];
}

+ (void)callPhoneNumber:(NSString *)phoneNumber {
	[CocoaUtils callPhoneNumber:[self formattedPhoneNumber:phoneNumber]];
}

#pragma mark - Date

+ (NSString *)localizedStringWithDate:(NSDate *)date {
	NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
	formatter.locale = [CocoaUtils applicationLocale];
	formatter.dateFormat = [NSDateFormatter dateFormatFromTemplate:@"EEEMMMdyyyy" options:0 locale:[CocoaUtils applicationLocale]];
	return [formatter format:date];
}

+ (NSString *)localizedStringWithStandardDateString:(NSString *)string {
	if ([StringUtils isEmpty:string]) {
		return string;
	}
	return [self localizedStringWithDate:[[DateFormatUtils ISO_DATE_FORMAT] parse:string]];
}

+ (NSString *)localizedStringWithDatetime:(NSDate *)datetime {
	NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
	formatter.locale = [CocoaUtils applicationLocale];
	formatter.dateFormat = [NSDateFormatter dateFormatFromTemplate:@"hmmssaEEEMMMdyyyy" options:0 locale:[CocoaUtils applicationLocale]];
	return [formatter format:datetime];
}

+ (NSString *)localizedStringWithStandardDatetimeString:(NSString *)string {
	if ([StringUtils isEmpty:string]) {
		return string;
	}
	return [self localizedStringWithDatetime:[[DateFormatUtils ISO_DATETIME_FORMAT] parse:string]];
}

+ (NSString *)abbreviatedDurationStringFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
	NSString *str = [CocoaUtils durationStringWithUnitsStyle:NSDateComponentsFormatterUnitsStyleAbbreviated locale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"] fromDate:fromDate toDate:toDate];
	str = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
	return str;
}

+(CGFloat )abbreviatedDurationTimeFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate{
	NSTimeInterval timeInterval = [CocoaUtils durationTimeWithUnitsStyle:NSDateComponentsFormatterUnitsStyleAbbreviated locale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"] fromDate:fromDate toDate:toDate];
	CGFloat duration = timeInterval / 3600.0;
	return duration;

}

+ (NSDateFormatter *)RANGECALENDAR_DATE_FORMAT {
	static NSDateFormatter *dateFormatter;
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		dateFormatter = [[NSDateFormatter alloc] init];
		dateFormatter.dateFormat = @"EE , MMM d";
	});
	return dateFormatter;
}


#pragma mark - Graphics

+ (UIColor *)lowestPriceColor {
	return [UIColor im_colorWithRed:0.07 green:0.7 blue:0.18 alpha:1.0];
}

+ (UIColor *)defaultTitleColor{
	return [CocoaUtils colorWithRGBs:0x333333];
}

+ (UIColor *)defaultSubTitleColor{
	return [CocoaUtils colorWithRGBs:0x666666];
}

+(UIColor *)popoverViewBackGroundColor{
	return [[CocoaUtils colorWithRGBs:0x333333] colorWithAlphaComponent:0.8];
}

#pragma mark - Network

+ (id)getTextFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(NSString *text))success {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)getTextFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(NSString *text))success error:(void (^)(NSError *error))error {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:error complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)getTextFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(NSString *))success complete:(void (^)(void))complete {
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters beforeSend:beforeSend success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:complete];
}

+ (id)getTextFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(NSString *))success error:(void (^)(NSError *))error complete:(void (^)(void))complete {
	if (beforeSend) {
		beforeSend();
	}
	return [NetUtils performRequestWithMethod:method url:url parameters:parameters settings:@{ NetworkSettingRequestEncoding : @([Vars charset]), NetworkSettingResponseDecoding : @([Vars charset]), NetworkSettingResponseDataType : @"TEXT" } done:^(id responseObject) {
		if (responseObject) {
			if (success) {
				success(responseObject);
			}
		} else {
			if (error) {
				error([NSError errorWithDomain:NetworkSettingErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [Vars responseException] }]);
			}
		}
	} fail:error always:complete];
}

+ (id)getXMLFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(NSString *xml))success {
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters success:^(NSString *text) {
		success([self normalizeXmlString:text]);
	}];
}

+ (id)getXMLFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(NSString *xml))success error:(void (^)(NSError *error))error {
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters success:^(NSString *text) {
		success([self normalizeXmlString:text]);
	} error:error];
}

+ (id)getXMLFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(NSString *))success complete:(void (^)(void))complete {
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters beforeSend:beforeSend success:^(NSString *text) {
		success([self normalizeXmlString:text]);
	} complete:complete];
}

+ (id)getXMLFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(NSString *))success error:(void (^)(NSError *))error complete:(void (^)(void))complete {
	return [self getTextFromRequestWithMethod:method url:url parameters:parameters beforeSend:beforeSend success:^(NSString *text) {
		success([self normalizeXmlString:text]);
	} error:error complete:complete];
}

+ (id)getJSONFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(id jsonObject))success {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self getJSONFromRequestWithMethod:method url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)getJSONFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(NSString *xml))success error:(void (^)(NSError *error))error {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self getJSONFromRequestWithMethod:method url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:error complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)getJSONFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(id))success complete:(void (^)(void))complete {
	return [self getJSONFromRequestWithMethod:method url:url parameters:parameters beforeSend:beforeSend success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:complete];
}

+ (id)getJSONFromRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(id jsonObject))success error:(void (^)(NSError *error))error complete:(void (^)(void))complete {
	if (beforeSend) {
		beforeSend();
	}
	return [NetUtils performRequestWithMethod:method url:url parameters:parameters settings:@{ NetworkSettingRequestEncoding : @([Vars charset]), NetworkSettingResponseDecoding : @([Vars charset]), NetworkSettingResponseDataType : @"JSON" } done:^(id responseObject) {
		if (responseObject) {
			if (success) {
				success(responseObject);
			}
		} else {
			if (error) {
				error([NSError errorWithDomain:NetworkSettingErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [Vars responseException] }]);
			}
		}
	} fail:error always:complete];
}

+ (id)performSOAPRequestWithUrl:url namespace:(NSString *)namespace name:(NSString *)name parameters:(NSDictionary *)parameters success:(void (^)(NSString *xml))success {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self performSOAPRequestWithUrl:url namespace:namespace name:name parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)performSOAPRequestWithUrl:(NSString *)url namespace:(NSString *)namespace name:(NSString *)name parameters:(NSDictionary *)parameters success:(void (^)(NSString *xml))success error:(void (^)(NSError *error))error {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self performSOAPRequestWithUrl:url namespace:namespace name:name parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:error complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)performSOAPRequestWithUrl:(NSString *)url namespace:(NSString *)namespace name:(NSString *)name parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(NSString *xml))success complete:(void (^)(void))complete {
	return [self performSOAPRequestWithUrl:url namespace:namespace name:name parameters:parameters beforeSend:beforeSend success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:complete];
}

+ (id)performSOAPRequestWithUrl:(NSString *)url namespace:(NSString *)namespace name:(NSString *)name parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(NSString *xml))success error:(void (^)(NSError *error))error complete:(void (^)(void))complete {
	NSString *query = [self javaSOAPEnvelope:[CocoaUtils xmlStringWithProperties:parameters] name:name namespace:namespace];
#if DEBUG
	NSLog(@"SOAP %@ %@", url, query);
#endif
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
	request.HTTPMethod = @"POST";
	request.HTTPBody = [query dataUsingEncoding:NSUTF8StringEncoding];
	[request addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
	if (beforeSend) {
		beforeSend();
	}
	return [NetUtils performRequest:request settings:@{ NetworkSettingResponseDataType : @"TEXT" } done:^(id responseObject) {
		if (responseObject) {
			if (success) {
				success([StringEscapeUtils unescapeXml:responseObject]);
			}
		} else {
			if (error) {
				error([NSError errorWithDomain:NetworkSettingErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [Vars responseException] }]);
			}
		}
	} fail:error always:complete];
}

+ (id)performRESTRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(id jsonObject))success {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self performRESTRequestWithMethod:method url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)performRESTRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(id jsonObject))success error:(void (^)(NSError *error))error {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self performRESTRequestWithMethod:method url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:error complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)performRESTRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(id jsonObject))success complete:(void (^)(void))complete {
	return [self performRESTRequestWithMethod:method url:url parameters:parameters beforeSend:beforeSend success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:complete];
}

+ (id)performRESTRequestWithMethod:(NSString *)method url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(id jsonObject))success error:(void (^)(NSError *error))error complete:(void (^)(void))complete {
	if (beforeSend) {
		beforeSend();
	}
	return [NetUtils performRequestWithMethod:method url:url parameters:parameters settings:@{ NetworkSettingRequestDataType : @"JSON", NetworkSettingResponseDataType : @"JSON" } done:^(id responseObject) {
		if (responseObject) {
			if (success) {
				success(responseObject);
			}
		} else {
			if (error) {
				error([NSError errorWithDomain:NetworkSettingErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [Vars responseException] }]);
			}
		}
	} fail:error always:complete];
}

+ (id)performUploadMultipartRequestConstructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block progress:(void (^) (CGFloat uploadProgress))progressBlock url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self performUploadMultipartRequestConstructingBodyWithBlock:block progress:progressBlock url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)performUploadMultipartRequestConstructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block progress:(void (^) (CGFloat uploadProgress))progressBlock url:(NSString *)url parameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success error:(void (^)(NSError *error))error {
	UIViewController *this = [CocoaUtils topMostViewController];
	return [self performUploadMultipartRequestConstructingBodyWithBlock:block progress:progressBlock url:url parameters:parameters beforeSend:^{
		[CocoaUtils showBusyIndicator:this.view];
	} success:success complete:^{
		[CocoaUtils hideBusyIndicator:this.view];
	}];
}

+ (id)performUploadMultipartRequestConstructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block progress:(void (^) (CGFloat uploadProgress))progressBlock url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(id responseObject))success complete:(void (^)(void))complete {
	return [self performUploadMultipartRequestConstructingBodyWithBlock:block progress:progressBlock url:url parameters:parameters beforeSend:beforeSend success:success error:^(NSError *error) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	} complete:complete];
}

+ (id)performUploadMultipartRequestConstructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block progress:(void (^) (CGFloat uploadProgress))progressBlock url:(NSString *)url parameters:(NSDictionary *)parameters beforeSend:(void (^)(void))beforeSend success:(void (^)(id responseObject))success error:(void (^)(NSError *error))error complete:(void (^)(void))complete {
	if (beforeSend) {
		beforeSend();
	}
	return [NetUtils performUploadMultipartRequestConstructingBodyWithBlock:block progress:progressBlock url:url parameters:parameters settings:nil done:^(id responseObject) {
		if (responseObject) {
			if (success) {
				success(responseObject);
			}
		} else {
			if (error) {
				error([NSError errorWithDomain:NetworkSettingErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [Vars responseException] }]);
			}
		}
	} fail:error always:complete];
}

#pragma mark - View

+ (void)addRoundedBorderToButton:(UIView *)view {
	[DrawUtils addRoundedBorderToRect:view cornerRadius:kDefaultRoundedCornerRadius];
}

+ (void)addRoundedBorderToButtons:(NSArray *)views {
	for (UIView *view in views) {
		[self addRoundedBorderToButton:view];
	}
}

+ (void)addRoundedBorderToView:(UIView *)view {
	[DrawUtils addRoundedBorderToRect:view cornerRadius:kDefaultRoundedCornerRadius strokeColor:[Vars defaultTintColor]];
}

+ (void)addRoundedBorderToTextView:(UITextView *)textView {
	[DrawUtils addRoundedBorderToRect:textView cornerRadius:kDefaultRoundedCornerRadius strokeColor:[UIColor lightGrayColor] borderWidth:1 / [UIScreen mainScreen].scale];
}

+ (void)addInsetToFooterViewIfSafeArea:(UIView *)view {
	if ([CocoaUtils isIPhoneX]) {
		view.frame = CGRectMake(15, 5, CGRectGetWidth([UIScreen mainScreen].bounds) - 15 * 2, CGRectGetHeight(view.frame));
		[Utility addRoundedBorderToButton:view];
	}
}

#pragma mark - CoreLocation

+ (void)requestLocationWithCompletion:(void (^) (BOOL granted, CLLocation *location, NSError *error))completion {
	[CocoaUtils locationManager:[self sharedLocationManager] requestLocationWithCompletion:completion];
}

+ (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(void (^)(CLPlacemark *placemark, NSError *error))completionHandler {
	[CocoaUtils geocoder:[self sharedGeocoder] reverseGeocodeLocation:location completionHandler:completionHandler];
}

#pragma mark - EventKit

+ (BOOL)authorizeCalendarForEvents {
	return [PrivacyUtils authorizeCalendarForEventsWithStore:[self sharedEventStore]];
}

+ (void)saveCalendarEventsForUpcomingItinerariesWithOrders:(NSArray *)orders {
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	[userDefaults removeObjectForKey:@"lastCalendarEventSaveTimestamp"];
	if (![userDefaults boolForKey:UserDefaultsKeyCalendarEventsEnabled]) {
		return;
	}
	if (![self authorizeCalendarForEvents]) {
		return;
	}
	EKEventStore *eventStore = [self sharedEventStore];
	EKCalendar *calendar = [eventStore defaultCalendarForNewEvents];
	if ([userDefaults objectForKey:UserDefaultsKeyCalendarEventIdentifiersForUpcomingItineraries]) {
		for (NSString *eventIdentifier in [userDefaults objectForKey:UserDefaultsKeyCalendarEventIdentifiersForUpcomingItineraries]) {
			EKEvent *event = [eventStore eventWithIdentifier:eventIdentifier];
			if (event) {
				[eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:NULL];
			}
		}
	} else {
		NSArray *events = [eventStore eventsMatchingPredicate:[eventStore predicateForEventsWithStartDate:[DateUtils addYears:[NSDate date] amount:-3] endDate:[DateUtils addYears:[NSDate date] amount:1] calendars:nil]];
		for (EKEvent *event in events) {
			if ([event.title hasSuffix:[CocoaUtils bundleName]]) {
				[eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:NULL];
			}
		}
	}
	NSMutableArray *events = [NSMutableArray array];
	NSArray *entries = [Sales toEntries:orders];
	for (SalesEntry *entry in entries) {
		if (entry.confirmed) {
			if (entry.departureDate) {
				EKEvent *event = [EKEvent eventWithEventStore:eventStore];
				event.title = [CocoaUtils bundleName];
				event.startDate = entry.departureDate;
				event.endDate = entry.departureDate;
				event.allDay = YES;
				event.notes = entry.calendarNote;
				// NSLog(@"startDate=%@",event.startDate);
				// NSLog(@"endDate=%@",event.endDate);
				// NSLog(@"notes=%@",event.notes);
				[event addAlarm:[EKAlarm alarmWithAbsoluteDate:[DateUtils addHours:entry.departureDate amount:-15]]];
				[event addAlarm:[EKAlarm alarmWithAbsoluteDate:[DateUtils addHours:entry.departureDate amount:-3]]];
				[event setCalendar:calendar];
				[eventStore saveEvent:event span:EKSpanThisEvent commit:NO error:NULL];
				[events addObject:event];
			}
		}
	}
	NSError *error = nil;
	[eventStore commit:&error];
	if (!error) {
		NSMutableArray *eventIdentifiers = [NSMutableArray array];
		for (EKEvent *event in events) {
			if (event.eventIdentifier) {
				[eventIdentifiers addObject:event.eventIdentifier];
			}
		}
		[userDefaults setObject:[NSArray arrayWithArray:eventIdentifiers] forKey:UserDefaultsKeyCalendarEventIdentifiersForUpcomingItineraries];
	}
}

#pragma mark - MessageUI

+ (void)sendFeedback {
	UIViewController *this = [CocoaUtils topMostViewController];
	if (!TARGET_IPHONE_SIMULATOR && [MFMailComposeViewController canSendMail] && [Vars support_email]) {
		MFMailComposeViewController *viewController = [[MFMailComposeViewController alloc] init];
		viewController.mailComposeDelegate = [MFMailComposeViewControllerDelegateImpl sharedInstance];
		[viewController setToRecipients:@[[Vars support_email]]];
		[this presentViewController:viewController animated:YES completion:nil];
	} else {
		MailViewController *viewController = [[MailViewController alloc] init];
		[this.navigationController pushViewController:viewController animated:YES];
	}
}

#pragma mark - UserNotifications

+ (void)registerRemoteNotificationWithDeviceToken:(NSData *)deviceToken {
	if (![Vars appDelegate].loginUser) {
		return;
	}
	
	if ([AppLocalConfig sharedInstance].userNotificationEnable != [UserNotificationsManager authorizeUserNotifications] && [AppLocalConfig sharedInstance].userNotificationsRegister) {
		[self changeUserNotificationEnable:[UserNotificationsManager authorizeUserNotifications]];
	}
	
	if (![UserNotificationsManager authorizeUserNotifications]) {
		return;
	}
	
	NSString *deviceTokenString = [CocoaUtils stringWithDeviceToken:deviceToken];
	NSString *oldDeviceTokenString = [AppLocalConfig sharedInstance].userDeviceToken;
	if ([deviceTokenString isEqualToString:oldDeviceTokenString] && [AppLocalConfig sharedInstance].userNotificationsRegister) {
		return;
	}
	

	RegisterUserAppNotification *regist = [[RegisterUserAppNotification alloc] init];
	regist.userAppId = [Vars appDelegate].loginUser.user_app_id;
	regist.deviceToken = deviceTokenString;
	regist.apiType = 0;
	regist.apiPlatformApplicationId = [Vars AWSSNSPlatformApplicationArn];
	NSDictionary *requestDict = [BeanUtils describeAccessibleSimple:regist];

	[Utility performRESTRequestWithMethod:@"POST" url:regist.address parameters:requestDict beforeSend:nil success:^(id jsonObject) {
		RegisterUserAppNotificationResponse *response = [RegisterUserAppNotificationResponse yy_modelWithJSON:jsonObject];
		if (response.errorCode) {
			[CocoaUtils showMessageBoxWithTitle:@"Error" message:@" Notification subscription failed, please try again." cancelActionTitle:@"cancle" otherActionTitles:@[@"retry"] actionHandler:^(NSInteger buttonIndex) {
				if (buttonIndex != -1) {
					[self registerRemoteNotificationWithDeviceToken:deviceToken];
				}else{
					[AppLocalConfig sharedInstance].userNotificationsRegister = NO;
					[AnswersManager logRegisterNotification:@{@"success":@NO}];
				}
			}];
			return ;
		}else{
			[AppLocalConfig sharedInstance].userNotificationsRegister = YES;
			[AppLocalConfig sharedInstance].userDeviceToken = deviceTokenString;
			[AnswersManager logRegisterNotification:@{@"success":@YES}];
		}
	} error:^(NSError *error) {
		[AppLocalConfig sharedInstance].userNotificationsRegister = NO;
		[AnswersManager logRegisterNotification:@{@"success":@NO}];
	}  complete:^{}];
}


+ (void)changeUserNotificationEnable:(BOOL)enable{
	EnableUserAppNotification *changeUserNotificationEnable = [[EnableUserAppNotification alloc] init];
	changeUserNotificationEnable.enabled = enable;
	changeUserNotificationEnable.userAppId = [Vars appDelegate].loginUser.user_app_id;
	
	NSDictionary *requestDict = [BeanUtils describeAccessibleSimple:changeUserNotificationEnable];
	
	[Utility performRESTRequestWithMethod:@"POST" url:changeUserNotificationEnable.address parameters:requestDict beforeSend:nil success:^(id jsonObject) {
		EnableUserAppNotificationResponse *response = [EnableUserAppNotificationResponse yy_modelWithJSON:jsonObject];
		if (response.errorCode) {
		}else{
			[AppLocalConfig sharedInstance].userNotificationEnable = enable;
		}
	} complete:nil];
}


+ (void)presentRemoteNotificationInActiveWithPayload:(NSDictionary *)userInfo {
	if ([SystemUtils isOperatingSystemAtLeastVersion:NSOperatingSystemVersionMake(10, 0, 0)]) {
		return;
	}
	NSDictionary *dict = userInfo[@"aps"];
	if (!dict[@"alert"][@"body"]) {
		return;
	}
    EBBannerView *banner = [EBBannerView bannerWithBlock:^(EBBannerViewMaker *make) {
        make.title = [StringUtils isEmpty:dict[@"alert"][@"title"]] ? [Vars site_name] : dict[@"alert"][@"title"];
        make.content =  dict[@"alert"][@"body"];
        make.soundName = dict[@"sound"] ? dict[@"sound"] : UILocalNotificationDefaultSoundName;
    }];
    [banner show];
}

#pragma mark - <ADAppRater>

+ (void)configureRaterFlow {
	// [[ADAppRater sharedInstance] resetUsageHistory];
	[ADAppRater sharedInstance].currentVersionDaysUntilPrompt = 0;
	[ADAppRater sharedInstance].currentVersionLaunchesUntilPrompt = 0;
	ADAppRaterTexts *raterStrings = [ADAppRater sharedInstance].localStrings;
	if ([Vars is_bus_site]) {
		raterStrings.appRatingAlertMessage = @"It'd be really helpful if you rated us.";
	} else if ([Vars is_tours_site]) {
		raterStrings.appRatingAlertTitle = IMLocalizedString(@"localAppRatingAlertTitle");
		raterStrings.appRatingAlertMessage = IMLocalizedString(@"localAppRatingAlertMessageFormat");
		raterStrings.appRatingAlertAnswerRate = [NSString stringWithFormat:IMLocalizedString(@"localAppRatingAlertAnswerRateFormat"), [Vars site_name]];
		raterStrings.appRatingAlertAnswerRemindMe = IMLocalizedString(@"localAppRatingAlertAnswerRemindMe");
		raterStrings.appRatingAlertAnswerDontRate = IMLocalizedString(@"localAppRatingAlertAnswerDontRate");
	}
}

+ (void)promptRatingOnceFromViewController:(UIViewController *)viewController {
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		ADEventCriteria *criteria = [[ADEventCriteria alloc] initWithEventName:NSStringFromClass(viewController.class) eventCount:1];
		ADEventScenario *scenario = [[ADEventScenario alloc] initWithEventCriterias:@[criteria]];
		[ADAppRater sharedInstance].eventScenariosUntilPrompt = @[scenario];
	});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
	[[ADAppRater sharedInstance] performSelector:@selector(registerEvent:) withObject:NSStringFromClass(viewController.class)];
#pragma clang diagnostic pop
	if ([[ADAppRater sharedInstance] shouldPromptForRating]) {
		[[ADAppRater sharedInstance] promptDirectRatingFromViewController:viewController];
	}
}

#pragma mark - Custom

+ (NSString *)validationMessageWithContactInfo:(Address *)address {
	if ([StringUtils isEmpty:address.first_name]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"First Name").lowercaseString];
	}
	if (![Utility canBeConvertedToUrlEncodedFromString:address.first_name]) {
		return IMLocalizedString(@"Please enter alphabetic characters only");
	}
	if ([StringUtils isEmpty:address.last_name]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Last Name").lowercaseString];
	}
	if (![Utility canBeConvertedToUrlEncodedFromString:address.last_name]) {
		return IMLocalizedString(@"Please enter alphabetic characters only");
	}
	if ([StringUtils isEmpty:address.email]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Contact Email").lowercaseString];
	}
	if (![GenericValidator isEmail:address.email]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Contact Email").lowercaseString];
	}
	if ([StringUtils isEmpty:address.phone]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Contact Phone").lowercaseString];
	}
	NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
	if (![phoneUtil isViablePhoneNumber:address.phone]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Contact Phone").lowercaseString];
	}
	if (![StringUtils isEmpty:address.phone2] && ![phoneUtil isViablePhoneNumber:address.phone2]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Alternate Phone").lowercaseString];
	}
	return nil;
}

+ (NSArray *)collectTableViewCellDataArrayWithContactInfo:(Address *)address {
	NSMutableArray *array = [NSMutableArray array];
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"First Name");
		data.showsRequiredAsteriskDuringEditing = YES;
		data.editingType = TableViewCellEditingTypeTextField;
		data.autocapitalizationType = UITextAutocapitalizationTypeWords;
		data.textContentType = IMTextContentTypeGivenName;
		data.keyPath = @"first_name";
		data.observedObject = address;
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactGivenNameKey, CMContactFamilyNameKey] completion:^id (CMContact *contact) {
			address.first_name = contact.givenName;
			address.last_name = contact.familyName;
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.first_name];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"Last Name");
		data.showsRequiredAsteriskDuringEditing = YES;
		data.editingType = TableViewCellEditingTypeTextField;
		data.autocapitalizationType = UITextAutocapitalizationTypeWords;
		data.textContentType = IMTextContentTypeFamilyName;
		data.keyPath = @"last_name";
		data.observedObject = address;
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactGivenNameKey, CMContactFamilyNameKey] completion:^id (CMContact *contact) {
			address.first_name = contact.givenName;
			address.last_name = contact.familyName;
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.last_name];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"Contact Email");
		data.showsRequiredAsteriskDuringEditing = YES;
		data.editingType = TableViewCellEditingTypeTextField;
		data.keyboardType = UIKeyboardTypeEmailAddress;
		data.textContentType = IMTextContentTypeEmailAddress;
		data.keyPath = @"email";
		data.observedObject = address;
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactEmailAddressesKey] completion:^id (CMContactProperty *property) {
			address.email = property.value;
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [GenericValidator isEmail:address.email];
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"Contact Phone");
		data.showsRequiredAsteriskDuringEditing = YES;
		data.editingType = TableViewCellEditingTypeTextField;
		data.keyboardType = UIKeyboardTypePhonePad;
		data.textContentType = IMTextContentTypeTelephoneNumber;
		data.keyPath = @"phone";
		data.observedObject = address;
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactPhoneNumbersKey] completion:^id (CMContactProperty *property) {
			address.phone = property.value;
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
			return [phoneUtil isViablePhoneNumber:address.phone];
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"Alternate Phone");
		data.editingType = TableViewCellEditingTypeTextField;
		data.keyboardType = UIKeyboardTypePhonePad;
		data.textContentType = IMTextContentTypeTelephoneNumber;
		data.keyPath = @"phone2";
		data.observedObject = address;
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactPhoneNumbersKey] completion:^id (CMContactProperty *property) {
			address.phone2 = property.value;
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			if ([StringUtils isEmpty:address.phone2]) {
				return YES;
			}
			NBPhoneNumberUtil *phoneUtil = [NBPhoneNumberUtil sharedInstance];
			return [phoneUtil isViablePhoneNumber:address.phone];
		}];
		[array addObject:data];
	}
	return [NSArray arrayWithArray:array];
}

+ (NSString *)validationMessageWithBillingAddress:(Address *)address {
	if ([StringUtils isEmpty:address.street]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Street").lowercaseString];
	}
	if (![Utility canBeConvertedToUrlEncodedFromString:address.street]) {
		return IMLocalizedString(@"Please enter alphabetic characters only");
	}
	if ([StringUtils isEmpty:address.city]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"City").lowercaseString];
	}
	if (![Utility canBeConvertedToUrlEncodedFromString:address.city]) {
		return IMLocalizedString(@"Please enter alphabetic characters only");
	}
	if ([[Address preferredCountryCode] isEqualToString:address.country]) {
		if ([StringUtils isEmpty:address.state]) {
			return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"State").lowercaseString];
		}
	} else {
		if ([StringUtils isEmpty:address.other_state]) {
			return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Other State").lowercaseString];
		}
		if (![Utility canBeConvertedToUrlEncodedFromString:address.other_state]) {
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}
	}
	if ([StringUtils isEmpty:address.zipcode]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Zipcode").lowercaseString];
	}
	if (![Utility canBeConvertedToUrlEncodedFromString:address.zipcode]) {
		return IMLocalizedString(@"Please enter alphabetic characters only");
	}
	if ([StringUtils isEmpty:address.country]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Country/Region").lowercaseString];
	}
	return nil;
}

+ (NSArray *)collectTableViewCellDataArrayWithBillingAddress:(Address *)address {
	NSMutableArray *array = [NSMutableArray array];
	@weakify(self)
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.showsRequiredAsteriskDuringEditing = YES;
		data.text = IMLocalizedString(@"Street");
		data.editingType = TableViewCellEditingTypeTextField;
		data.autocapitalizationType = UITextAutocapitalizationTypeSentences;
		data.textContentType = IMTextContentTypeFullStreetAddress;
		data.observedObject = address;
		data.keyPath = @"street";
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactPostalAddressesKey] completion:^id (CMContactProperty *property) {
			[self configureBillingAddress:address postalAddress:property.value];
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.street];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (NO) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"Additional");
		data.editingType = TableViewCellEditingTypeTextField;
		data.autocapitalizationType = UITextAutocapitalizationTypeSentences;
		data.textContentType = IMTextContentTypeStreetAddressLine2;
		data.observedObject = address;
		data.keyPath = @"street2";
		data.validationMessageCaption = IMLocalizedString(@"Additional Street");
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.street2];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.showsRequiredAsteriskDuringEditing = YES;
		data.text = IMLocalizedString(@"City");
		data.editingType = TableViewCellEditingTypeTextField;
		data.autocapitalizationType = UITextAutocapitalizationTypeWords;
		data.textContentType = IMTextContentTypeAddressCity;
		data.observedObject = address;
		data.keyPath = @"city";
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactPostalAddressesKey] completion:^id (CMContactProperty *property) {
			[self configureBillingAddress:address postalAddress:property.value];
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.city];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"State");
		data.accessoryType = UITableViewCellAccessoryDisclosureIndicatorDown;
		[data setDetailTextBlock:^NSString *{
			return address.state;
		}];
		[data setRequiredValidationRuleBlock:^BOOL {
			if ([[Address preferredCountryCode] isEqualToString:address.country]) {
				return address.state;
			}
			return YES;
		}];
		[data setDidSelectBlock:^(UITableView *tableView, NSIndexPath *indexPath, TableViewCellData *data) {
			@strongify(self)
			[self performSegueForStateOfAddress: address onTableView: tableView atIndexPath: indexPath];
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.text = IMLocalizedString(@"Or");
		data.editingType = TableViewCellEditingTypeTextField;
		data.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
		data.textContentType = IMTextContentTypeAddressState;
		data.observedObject = address;
		data.keyPath = @"other_state";
		data.validationMessageCaption = IMLocalizedString(@"Other State");
		[data setRequiredValidationRuleBlock:^BOOL {
			if (![[Address preferredCountryCode] isEqualToString:address.country]) {
				return address.other_state;
			}
			return YES;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.other_state];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.showsRequiredAsteriskDuringEditing = YES;
		data.text = IMLocalizedString(@"Zipcode");
		[data setAccessoryViewBlock:^UIView *(UITableView *tableView, NSIndexPath *indexPath, TableViewCellData *data) {
			UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
			[button bk_addEventHandler:^(UIButton *sender) {
				UIViewController *viewController = [CocoaUtils topMostViewController];
				[CocoaUtils showTooltipWithMessage:IMLocalizedString(@"Some countries do not have zip codes. You can put '00000' instead of a number. And, you will be able to complete the booking without any problem.")  inView:viewController.view atFrame:[sender convertRect:sender.bounds toView:viewController.view]];
			} forControlEvents:UIControlEventTouchUpInside];
			return button;
		}];
		data.editingType = TableViewCellEditingTypeTextField;
		data.textContentType = IMTextContentTypePostalCode;
		data.observedObject = address;
		data.keyPath = @"zipcode";
		[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactPostalAddressesKey] completion:^id (CMContactProperty *property) {
			[self configureBillingAddress:address postalAddress:property.value];
			return nil;
		}];
		[data addIntegrityValidationRuleBlock:^BOOL {
			return [Utility canBeConvertedToUrlEncodedFromString:address.zipcode];
		} withMessageBlock:^NSString *{
			return IMLocalizedString(@"Please enter alphabetic characters only");
		}];
		[array addObject:data];
	}
	if (YES) {
		TableViewCellData *data = [[TableViewCellData alloc] init];
		data.style = UITableViewCellStyleValue2;
		data.showsRequiredAsteriskDuringEditing = YES;
		data.text = IMLocalizedString(@"Country/Region");
		[data setDetailTextBlock:^NSString * {
			if (![StringUtils isEmpty:address.country] && [[Address availableCountryCodes] containsObject:address.country]) {
				return [Address availableCountryNames][[[Address availableCountryCodes] indexOfObject:address.country]];
			}
			return nil;
		}];
		data.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
		[data setDidSelectBlock:^(UITableView *tableView, NSIndexPath *indexPath, TableViewCellData *data) {
			[self performSegueForCountryOfAddress:address onTableView:tableView atIndexPath:indexPath];
		}];
		[array addObject:data];
	}
	return [NSArray arrayWithArray:array];
}

+ (NSString *)validationMessageWithCreditCard:(CreditCard *)creditCard {
	if ([StringUtils isEmpty:creditCard.name_on_card]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Holder Name").lowercaseString];
	}
	if (![Utility canBeConvertedToUrlEncodedFromString:creditCard.name_on_card]) {
		return IMLocalizedString(@"Please enter alphabetic characters only");
	}
	if (creditCard.card_type < 1 || creditCard.card_type > 3) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Type").lowercaseString];
	}
	if ([StringUtils isEmpty:creditCard.card_number]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Card Number").lowercaseString];
	}
	if ((creditCard.card_type == 3 && creditCard.card_number.length != 15) || (creditCard.card_type != 3 && creditCard.card_number.length != 16)) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Card Number").lowercaseString];
	}
	if ([StringUtils isEmpty:creditCard.expire_year] || [StringUtils isEmpty:creditCard.expire_month]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"Expiration").lowercaseString];
	}
	NSInteger thisYear = [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitYear];
	NSInteger thisMonth = [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitMonth];
	if (creditCard.expire_year.intValue < thisYear) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Expiration").lowercaseString];
	}
	if (creditCard.expire_year.intValue == thisYear && creditCard.expire_month.intValue < thisMonth) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"Expiration").lowercaseString];
	}
	if ( [StringUtils isEmpty:creditCard.x_card_code]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please fill out %@"), IMLocalizedString(@"CVV")];
	}
	if (![GenericValidator isInRange:creditCard.x_card_code.length min:3 max:4]) {
		return [NSString stringWithFormat:IMLocalizedString(@"Please check against %@"), IMLocalizedString(@"CVV")];
	}
	return [Utility validationMessageWithBillingAddress:creditCard.address];
}

+ (NSArray *)collectTableViewCellDataArrayWithCreditCard:(CreditCard *)creditCard {
	NSMutableArray *array = [NSMutableArray array];
	if (YES) {
		if (YES) {
			TableViewCellData *data = [[TableViewCellData alloc] init];
			data.style = UITableViewCellStyleValue2;
			data.text = IMLocalizedString(@"Holder Name");
			data.showsRequiredAsteriskDuringEditing = YES;
			data.editingType = TableViewCellEditingTypeTextField;
			data.autocapitalizationType = UITextAutocapitalizationTypeWords;
			data.textContentType = IMTextContentTypeName;
			data.observedObject = creditCard;
			data.keyPath = @"name_on_card";
			[data setKeyboardToolbarActionForSelectionOfContactWithDisplayedPropertyKeys:@[CMContactGivenNameKey, CMContactFamilyNameKey] completion:^id (CMContact *contact) {
				creditCard.name_on_card = contact.fullName;
				return nil;
			}];
			[data addIntegrityValidationRuleBlock:^BOOL {
				return [Utility canBeConvertedToUrlEncodedFromString:creditCard.name_on_card];
			} withMessageBlock:^NSString *{
				return IMLocalizedString(@"Please enter alphabetic characters only");
			}];
			[array addObject:data];
		}
		if (YES) {
			TableViewCellData *data = [[TableViewCellData alloc] init];
			data.style = UITableViewCellStyleValue2;
			data.showsRequiredAsteriskDuringEditing = YES;
			data.text = IMLocalizedString(@"Type");
			data.editingType = TableViewCellEditingTypeSegmentedControl;
			data.observedObject = creditCard;
			data.keyPath = @"card_type";
			data.optionValues = @[@1, @2, @3];
			data.optionTexts = @[@"Master", @"Visa", @"AmEx"];
			[array addObject:data];
		}
		if (YES) {
			TableViewCellData *data = [[TableViewCellData alloc] init];
			data.style = UITableViewCellStyleValue2;
			data.showsRequiredAsteriskDuringEditing = YES;
			data.text = IMLocalizedString(@"Card Number");
			data.editingType = TableViewCellEditingTypeTextField;
			data.keyboardType = UIKeyboardTypeNumberPad;
			data.textContentType = IMTextContentTypeCreditCardNumber;
			data.observedObject = creditCard;
			data.keyPath = @"card_number";
			data.maxLength = 16;
			[data setKeyboardToolbarActionWithTitle:IMLocalizedString(@"Scan Credit Card") handler:^(UITableView *tableView, NSIndexPath *indexPath, TableViewCellData *data, id cell, UITextField *textField) {
				[self performSegueForCreditCard:creditCard onTableView:tableView atIndexPath:indexPath];
			}];
			[data addIntegrityValidationRuleBlock:^BOOL {
				if ((creditCard.card_type == 3 && creditCard.card_number.length != 15) || (creditCard.card_type != 3 && creditCard.card_number.length != 16)) {
					return NO;
				}
				return YES;
			}];
			[array addObject:data];
		}
		if (YES) {
			TableViewCellData *data = [[TableViewCellData alloc] init];
			data.style = UITableViewCellStyleValue2;
			data.showsRequiredAsteriskDuringEditing = YES;
			data.text = IMLocalizedString(@"Expiration");
			[data setDetailTextBlock:^NSString *{
				if (!([StringUtils isEmpty:creditCard.expire_month] || [StringUtils isEmpty:creditCard.expire_year])) {
					return [Vars is_cn_website] ? [NSString stringWithFormat:@"%@ / %@", creditCard.expire_year, creditCard.expire_month] : [NSString stringWithFormat:@"%@ / %@", creditCard.expire_month, creditCard.expire_year];
				}
				return [Vars is_cn_website] ? @"YYYY / MM" : @"MM / YYYY";
			}];
			[data setDetailTextColorBlock:^UIColor *{
				if (!([StringUtils isEmpty:creditCard.expire_month] || [StringUtils isEmpty:creditCard.expire_year])) {
					return [UIColor darkTextColor];
				}
				return [UIColor lightGrayColor];
			}];
			data.accessoryType = UITableViewCellAccessoryDisclosureIndicatorDown;
			[data setRequiredValidationRuleBlock:^BOOL {
				if ([StringUtils isEmpty:creditCard.expire_month] || [StringUtils isEmpty:creditCard.expire_year]) {
					return NO;
				}
				return YES;
			}];
			[data addIntegrityValidationRuleBlock:^BOOL {
				NSInteger thisYear = [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitYear];
				NSInteger thisMonth = [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitMonth];
				if (creditCard.expire_year.intValue < thisYear) {
					return NO;
				}
				if (creditCard.expire_year.intValue == thisYear && creditCard.expire_month.intValue < thisMonth) {
					return NO;
				}
				return YES;
			}];
			[data setDidSelectBlock:^(UITableView *tableView, NSIndexPath *indexPath, TableViewCellData *data) {
				[self performSegueForExpirationOfCreditCard:creditCard onTableView:tableView atIndexPath:indexPath];
			}];
			[array addObject:data];
		}
		if (YES) {
			TableViewCellData *data = [[TableViewCellData alloc] init];
			data.style = UITableViewCellStyleValue2;
			data.showsRequiredAsteriskDuringEditing = YES;
			data.text = IMLocalizedString(@"CVV");
			data.editingType = TableViewCellEditingTypeTextField;
			data.keyboardType = UIKeyboardTypeNumberPad;
			data.observedObject = creditCard;
			data.keyPath = @"x_card_code";
			data.maxLength = 4;
			data.validationMessageCaption = IMLocalizedString(@"CVV");
			[data addIntegrityValidationRuleBlock:^BOOL {
				if (![GenericValidator isInRange:creditCard.x_card_code.length min:3 max:4]) {
					return NO;
				}
				return YES;
			}];
			[array addObject:data];
		}
	}
	if (YES) {
		[array addObjectsFromArray:[self collectTableViewCellDataArrayWithBillingAddress:creditCard.address]];
	}
	return [NSArray arrayWithArray:array];
}

#pragma mark - Private

+ (void)flattenJSONObject:(id)obj objectMapping:(RKObjectMapping *)objectMapping {
	if ([obj isKindOfClass:[NSArray class]]) {
		NSMutableArray *array = obj;
		for (int i = 0; i < array.count; i++) {
			[self flattenJSONObject:array[i] objectMapping:objectMapping];
		}
	} else if ([obj isKindOfClass:[NSDictionary class]]) {
		NSMutableDictionary *dict = obj;
		for (NSString *aKey in dict.allKeys) {
			RKPropertyMapping *propertyMapping = objectMapping.propertyMappingsBySourceKeyPath[aKey];
			if (!propertyMapping) {
				// { status : 1, products:[], user: {} } => { status : 1, body: { products:[], user: {} } }
				RKRelationshipMapping *relationshipMapping = [objectMapping.propertyMappingsBySourceKeyPath bk_match:^BOOL (id key, id obj) {
					return key == [NSNull null] && [obj isKindOfClass:[RKRelationshipMapping class]];
				}];
				if (relationshipMapping) {
					propertyMapping = ((RKObjectMapping *)relationshipMapping.mapping).propertyMappingsBySourceKeyPath[aKey];
				}
			}
			if (propertyMapping) {
				if ([propertyMapping.propertyValueClass isSubclassOfClass:[NSArray class]] && [dict[aKey] isKindOfClass:[NSDictionary class]] && [dict[aKey] count] == 1) {
					if ([[dict[aKey] allValues][0] isKindOfClass:[NSArray class]]) {
						dict[aKey] = [dict[aKey] allValues][0];
					} else {
						dict[aKey] = @[[dict[aKey] allValues][0]];
					}
				}
				if ([propertyMapping isKindOfClass:[RKRelationshipMapping class]]) {
					[self flattenJSONObject:dict[aKey] objectMapping:((RKRelationshipMapping *)propertyMapping).mapping];
				}
			} else {
				RKPropertyMapping *componentPropertyMapping = [objectMapping.propertyMappingsBySourceKeyPath bk_match:^BOOL (id key, id obj) {
					return key != [NSNull null] && [key hasPrefix:[aKey stringByAppendingString:@"."]];
				}];
				if (componentPropertyMapping) {
					RKObjectMapping *aObjectMapping = [RKObjectMapping mappingForClass:objectMapping.objectClass];
					NSString *lastKeyPath = [componentPropertyMapping.sourceKeyPath componentsSeparatedByString:@"."].lastObject;
					if ([componentPropertyMapping isKindOfClass:[RKRelationshipMapping class]]) {
						// { isDynamic : 0, productPrice: { priceEntries: [] } } => { isDynamic : 0, priceEntries: [] }
						[aObjectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:lastKeyPath toKeyPath:componentPropertyMapping.destinationKeyPath withMapping:((RKRelationshipMapping *)componentPropertyMapping).mapping]];
					} else if ([componentPropertyMapping isKindOfClass:[RKAttributeMapping class]]) {
						// { labels : { label : { options: [] } } } => { options: [] }
						[aObjectMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:lastKeyPath toKeyPath:componentPropertyMapping.destinationKeyPath]];
					}
					NSString *aKeyPath = [componentPropertyMapping.sourceKeyPath substringToIndex:componentPropertyMapping.sourceKeyPath.length - lastKeyPath.length - 1];
					[self flattenJSONObject:[dict valueForKeyPath:aKeyPath] objectMapping:aObjectMapping];
				}
			}
		}
	}
}

// + (void)fillJSONObject:(id)obj objectMapping:(RKObjectMapping *)objectMapping {
// if ([obj isKindOfClass:[NSArray class]]) {
// NSMutableArray *array = obj;
// for (int i = 0; i < array.count; i++) {
// [self flattenJSONObject:array[i] objectMapping:objectMapping];
// }
// }
// else if ([obj isKindOfClass:[NSDictionary class]]) {
// NSMutableDictionary *dict = obj;
// for (id key in dict.allKeys) {
// RKPropertyMapping *propertyMapping = objectMapping.propertyMappingsBySourceKeyPath[key];
// if (propertyMapping) {
// if ([propertyMapping.propertyValueClass isSubclassOfClass:[NSArray class]] && [dict[key] isKindOfClass:[NSArray class]] && propertyMapping.itemName) {
// NSDictionary *itemDict = [NSDictionary dictionaryWithObject:dict[key] forKey:propertyMapping.itemName];
// [dict setObject:itemDict forKey:key];
// }
// if ([propertyMapping isKindOfClass:[RKRelationshipMapping class]]) {
// [self flattenJSONObject:dict[key] objectMapping:(RKObjectMapping *)((RKRelationshipMapping *)propertyMapping).mapping];
// }
// }
// }
// }
// }


+ (NSArray *)getCreditCardExpirationMonths:(BOOL)notThisYear {
	NSMutableArray *array = [NSMutableArray array];
	NSInteger thisMonth = 1;
	if (!notThisYear) {
		thisMonth = [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitMonth];
	}
	for (NSInteger i = thisMonth; i <= 12; i++) {
		[array addObject:[NSString stringWithFormat:@"%02d", i]];
	}
	return [NSArray arrayWithArray:array];
}

+ (NSArray *)getCreditCardExpirationYears {
	NSMutableArray *array = [NSMutableArray array];
	NSInteger thisYear = [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitYear];
	for (int i = 0; i < 15; i++) {
		[array addObject:[NSString stringWithFormat:@"%d", thisYear + i]];
	}
	return [NSArray arrayWithArray:array];
}

+ (void)performSegueForExpirationOfCreditCard:(CreditCard *)creditCard onTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
	NSInteger componentY = [Vars is_cn_website] ? 0 : 1;
	NSInteger componentM = [Vars is_cn_website] ? 1 : 0;
	__block BOOL notThisYear = !([StringUtils isEmpty:creditCard.expire_year] || creditCard.expire_year.intValue == [DateUtils getFragment:[NSDate date] unit:NSCalendarUnitYear]);
	UIPickerView *pickerView = [[UIPickerView alloc] init];
	[pickerView.bk_dynamicDataSource implementMethod:@selector(numberOfComponentsInPickerView:) withBlock:^NSInteger (UIPickerView *pickerView) {
		return 2;
	}];
	[pickerView.bk_dynamicDataSource implementMethod:@selector(pickerView:numberOfRowsInComponent:) withBlock:^NSInteger (UIPickerView *pickerView, NSInteger component) {
		if (component == componentM) {
			return [self getCreditCardExpirationMonths:notThisYear].count;
		} else if (component == componentY) {
			return [self getCreditCardExpirationYears].count;
		}
		return 0;
	}];
	pickerView.dataSource = pickerView.bk_dynamicDataSource;
	[pickerView.bk_dynamicDelegate implementMethod:@selector(pickerView:titleForRow:forComponent:) withBlock:^NSString *(UIPickerView *pickerView, NSInteger row, NSInteger component) {
		if (component == componentM) {
			return [self getCreditCardExpirationMonths:notThisYear][row];
		} else if (component == componentY) {
			return [self getCreditCardExpirationYears][row];
		}
		return nil;
	}];
	[pickerView.bk_dynamicDelegate implementMethod:@selector(pickerView:didSelectRow:inComponent:) withBlock:^(UIPickerView *pickerView, NSInteger row, NSInteger component) {
		if (component == componentY) {
			BOOL flag = row != 0;
			if (notThisYear != flag) {
				NSString *expirationMonth = [self getCreditCardExpirationMonths:notThisYear][[pickerView selectedRowInComponent:componentM]];
				notThisYear = flag;
				[pickerView reloadComponent:componentM];
				if ([[self getCreditCardExpirationMonths:notThisYear] containsObject:expirationMonth]) {
					[pickerView selectRow:[[self getCreditCardExpirationMonths:notThisYear] indexOfObject:expirationMonth] inComponent:componentM animated:NO];
				}
			}
		}
	}];
	pickerView.delegate = pickerView.bk_dynamicDelegate;
	if (!([StringUtils isEmpty:creditCard.expire_month] || [StringUtils isEmpty:creditCard.expire_year]) && [[self getCreditCardExpirationMonths:notThisYear] containsObject:creditCard.expire_month] && [[self getCreditCardExpirationYears] containsObject:creditCard.expire_year]) {
		[pickerView selectRow:[[self getCreditCardExpirationMonths:notThisYear] indexOfObject:creditCard.expire_month] inComponent:componentM animated:NO];
		[pickerView selectRow:[[self getCreditCardExpirationYears] indexOfObject:creditCard.expire_year] inComponent:componentY animated:NO];
	}
	NavigationItemSheet *sheet = [[NavigationItemSheet alloc] initWithViews:@[pickerView] handler:^{
		creditCard.expire_month = [self getCreditCardExpirationMonths:notThisYear][[pickerView selectedRowInComponent:componentM]];
		creditCard.expire_year = [self getCreditCardExpirationYears][[pickerView selectedRowInComponent:componentY]];
		[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
	}];
	[sheet show];
}

+ (void)performSegueForStateOfAddress:(Address *)address onTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
	UIPickerView *pickerView = [[UIPickerView alloc] init];
	[pickerView.bk_dynamicDataSource implementMethod:@selector(numberOfComponentsInPickerView:) withBlock:^NSInteger (UIPickerView *pickerView) {
		return 1;
	}];
	[pickerView.bk_dynamicDataSource implementMethod:@selector(pickerView:numberOfRowsInComponent:) withBlock:^NSInteger (UIPickerView *pickerView, NSInteger component) {
		return [Address knownStates].count + 1;
	}];
	pickerView.dataSource = pickerView.bk_dynamicDataSource;
	[pickerView.bk_dynamicDelegate implementMethod:@selector(pickerView:titleForRow:forComponent:) withBlock:^NSString *(UIPickerView *pickerView, NSInteger row, NSInteger component) {
		return row == 0 ? IMLocalizedString(@"No preference") : [Address knownStates][row - 1];
	}];
	pickerView.delegate = pickerView.bk_dynamicDelegate;
	if (![StringUtils isEmpty:address.state] && [[Address knownStates] containsObject:address.state]) {
		[pickerView selectRow:[[Address knownStates] indexOfObject:address.state] + 1 inComponent:0 animated:YES];
	}
	NavigationItemSheet *sheet = [[NavigationItemSheet alloc] initWithViews:@[pickerView] handler:^{
		address.state = [pickerView selectedRowInComponent:0] == 0 ? nil : [Address knownStates][[pickerView selectedRowInComponent:0] - 1];
		[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
	}];
	[sheet show];
}

+ (void)performSegueForCountryOfAddress:(Address *)address onTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
	UIViewController *this = [CocoaUtils topMostViewController];
	ComboBoxViewController *viewController = [[ComboBoxViewController alloc] init];
	viewController.title = IMLocalizedString(@"Country/Region");
	[[Vars appDelegate] applyDefaultConfigurationForViewController:viewController];
	[viewController setItemsWithDictionary:[NSDictionary dictionaryWithObjects:[Address availableCountryNames] forKeys:[Address availableCountryCodes]]];
	[viewController setDidSelectItemBlock:^(ComboBoxItem *item) {
		[this.navigationController popViewControllerAnimated:YES];
		address.country = item.value;
		[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
	}];
	if (![StringUtils isEmpty:address.country]) {
		[viewController selectValue:address.country];
	}
	[this.navigationController pushViewController:viewController animated:YES];
}

+ (void)performSegueForCreditCard:(CreditCard *)creditCard onTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
	if (!TARGET_IPHONE_SIMULATOR && ![CardIOUtilities canReadCardWithCamera]) {
		[CocoaUtils showConfirmWithTitle:IMLocalizedString(@"Information") message:[NSString stringWithFormat:IMLocalizedString(@"Please allow \"%@\" to access your camera"), [CocoaUtils bundleName]] otherActionTitle:IMLocalizedString(@"Open Settings") otherActionHandler:^{
			[CocoaUtils openSettings];
		}];
		return;
	}
	CardIOPaymentViewControllerDelegateImpl *impl = [CardIOPaymentViewControllerDelegateImpl sharedInstance];
	[impl setUserDidProvideCreditCardInfoBlock:^(CardIOCreditCardInfo *cardInfo) {
		if (cardInfo.cardType != CardIOCreditCardTypeVisa && cardInfo.cardType != CardIOCreditCardTypeMastercard && cardInfo.cardType != CardIOCreditCardTypeAmex) {
			[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Information") message:IMLocalizedString(@"A debit card with VISA, MasterCard or American Express logo can be accepted as a credit card")];
		}
		[creditCard setValuesForKeysWithCreditCardInfo:cardInfo];
		[tableView reloadData];
	}];
	CardIOPaymentViewController *viewController = [[CardIOPaymentViewController alloc] initWithPaymentDelegate:impl scanningEnabled:TARGET_IPHONE_SIMULATOR ? NO : YES];
	viewController.languageOrLocale = [CocoaUtils applicationLocale].localeIdentifier;
	viewController.guideColor = [Vars defaultTintColor];
	viewController.hideCardIOLogo = YES;
#if !DEBUG
	viewController.disableManualEntryButtons = YES;
#endif
	viewController.collectCVV = NO;
	UIViewController *this = [CocoaUtils topMostViewController];
	[this presentViewController:viewController animated:YES completion:nil];
}

+ (void)configureBillingAddress:(Address *)address postalAddress:(CMPostalAddress *)postalAddress {
	address.street = postalAddress.street;
	address.city = postalAddress.city;
	address.zipcode = postalAddress.postalCode;
	if ([[Address preferredCountryCode] isEqualToString:postalAddress.ISOCountryCode.lowercaseString]) {
		if ([[Destination stateNameCodeHashForUSA] objectForKey:postalAddress.state]) {
			address.state = [Destination stateNameCodeHashForUSA][postalAddress.state];
		} else if ([[Destination stateNameCodeHashForUSA].allValues containsObject:postalAddress.state.uppercaseString]) {
			address.state = postalAddress.state.uppercaseString;
		}
		address.other_state = nil;
	} else {
		address.state = nil;
		address.other_state = postalAddress.state;
	}
	if ([[Address availableCountryCodes] containsObject:postalAddress.ISOCountryCode.lowercaseString]) {
		address.country = postalAddress.ISOCountryCode.lowercaseString;
	}
}

+ (NSString *)deviceIdentifier{
	NSString *bundleID = @"com.GoToBus.app";
	VALValet *myValet = [[VALValet alloc] initWithIdentifier:bundleID accessibility:VALAccessibilityWhenUnlocked];
	NSString *oldIdentifier = [myValet stringForKey:NSStringFromSelector(@selector(identifierForVendor))];
	
	VALValet *shareValet = [[VALValet alloc] initWithSharedAccessGroupIdentifier:bundleID accessibility:VALAccessibilityWhenUnlocked];
	NSString *newIdentifier = [shareValet stringForKey:NSStringFromSelector(@selector(identifierForVendor))];
	if (newIdentifier) {
		return newIdentifier;
	}
	if (oldIdentifier) {
		newIdentifier = oldIdentifier;
	}else{
		newIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
	}
	[shareValet setString:newIdentifier forKey:NSStringFromSelector(@selector(identifierForVendor))];
	return newIdentifier;
}
@end
