//
// NSUtils.m
// IvyMedia
//
// Created by iphone development on 8/19/14.
//
//

#import "CocoaUtils.h"
#import <StoreKit/StoreKit.h>
#import "Lang.h"
#import "SDK.h"
#import "AllConfig.h"
#import "ContactsManager.h"
#import "DrawUtils.h"
#import "LocationManager.h"
#import "UserNotificationsManager.h"
#import "CustomNavigationController.h"
#import "UIConstants.h"
#import <BlocksKit/BlocksKit.h>
#import <BlocksKit/BlocksKit+UIKit.h>
#import <BlocksKit/A2DynamicDelegate.h>
#import <BlocksKit/NSObject+A2DynamicDelegate.h>
#import <AMPopTip/AMPopTip.h>
#import <CRToast/CRToast.h>
#import <IQKeyboardManager/IQKeyboardManager.h>
#import <IQKeyboardManager/IQTitleBarButtonItem.h>
#import <IQKeyboardManager/IQToolbar.h>
#import <IQKeyboardManager/IQUIView+IQKeyboardToolbar.h>
#import <MBProgressHUD/MBProgressHUD.h>
#import <PPTopMostController/PPTopMostControllerProtocol.h>
#import <PPTopMostController/UIViewController+PPTopMostController.h>
#import <PureLayout/PureLayout.h>
#import <RestKit/RKURLEncodedSerialization.h>
#import <XMLDictionary/XMLDictionary.h>

@interface UIAlertController (TopMostController) <PPTopMostControllerProtocol>
@end

@implementation UIAlertController (TopMostController)

#pragma mark - <PPTopMostControllerProtocol>

- (UIViewController *)visibleViewController {
	return self.presentingViewController;
}

@end

@interface TooltipManager : NSObject
@property(nonatomic, strong) AMPopTip *popTip;
@end

@implementation TooltipManager

#pragma mark - Private

+ (instancetype)sharedManager {
	static dispatch_once_t onceToken;
	static TooltipManager *manager;
	dispatch_once(&onceToken, ^{
		manager = [[self alloc] init];
	});
	return manager;
}


- (void)config:(AMPopTip *)tooltip {
	tooltip.popoverColor = [UIColor whiteColor];
	tooltip.textColor = [UIColor blackColor];
	tooltip.borderColor = [UIColor lightGrayColor];
	tooltip.padding = 5;
	tooltip.borderWidth = 1;
	tooltip.shouldDismissOnTap = YES;
	self.popTip = tooltip;
}

#pragma mark - Public
- (void)showTooltipWithMessage:(id)message inView:(UIView*)inView atFrame:(CGRect )frame {
	[self.popTip hide];
	AMPopTip *popTip = [[AMPopTip alloc] init];
	[self config:popTip];
	CGFloat maxWidth = CGRectGetWidth([UIScreen mainScreen].bounds) - 15 * 2;
	if ([message isKindOfClass:[NSAttributedString class]]) {
		[popTip showAttributedText:message direction:AMPopTipDirectionDown maxWidth:maxWidth inView:inView fromFrame:frame];
	}else{
		[popTip showText:message direction:AMPopTipDirectionNone maxWidth:maxWidth inView:inView fromFrame:frame];
	}
}


- (void)showTooltipWithCustomView:(id)customView inView:(UIView *)inView atFrame:(CGRect )frame {
	[self.popTip hide];
	AMPopTip *popTip = [[AMPopTip alloc] init];
	[self config:popTip];
	[popTip showCustomView:customView direction:AMPopTipDirectionDown inView:inView fromFrame:frame];
}


@end

@implementation CocoaUtils

static UIViewController *topMostViewController;
static BOOL alertControllerAnimated = YES;

+ (void)load {
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		[CRToastManager setDefaultOptions:@{
			kCRToastAnimationInDirectionKey : @(CRToastAnimationDirectionTop),
			kCRToastAnimationOutDirectionKey : @(CRToastAnimationDirectionTop),
			kCRToastBackgroundColorKey : [UIColor redColor],
			kCRToastImageKey : [DrawUtils imageWithFAKIcon:[FAKFontAwesome exclamationTriangleIconWithSize:24] fillColor:[UIColor whiteColor]],
			kCRToastNotificationTypeKey : @(CRToastTypeNavigationBar),
			kCRToastNotificationPresentationTypeKey : @(CRToastPresentationTypeCover),
			kCRToastTextAlignmentKey : @(NSTextAlignmentLeft)
		}];
	});
}

+ (NSCache *)sharedHtmlStringCache {
	static dispatch_once_t onceToken;
	static NSCache *cache;
	dispatch_once(&onceToken, ^{
		cache = [[NSCache alloc] init];
		cache.countLimit = 100;
	});
	return cache;
}

#pragma mark - Public

+ (NSString *)userName {
	return NSHomeDirectory().pathComponents[2];
}

+ (NSString *)bundleName {
	return [NSBundle mainBundle].infoDictionary[@"CFBundleName"];
}

+ (NSString *)bundleVersion {
	return [NSBundle mainBundle].infoDictionary[@"CFBundleVersion"];
}

+ (BOOL)isIPhoneX {
	return [[IMDeviceHardware platformStringSimple] hasPrefix:@"iPhone X"];
}

+ (NSLocale *)applicationLocale {
	return [NSLocale localeWithLocaleIdentifier:[IMLocalization sharedInstance].language];
}

+ (void)callPhoneNumber:(NSString *)phoneNumber {
	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[@"tel:" im_stringByAppendingString:phoneNumber]]];
}

+ (void)openSettings {
	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}

+ (NSString *)launchImageName {
	NSArray *launchImages = [NSBundle mainBundle].infoDictionary[@"UILaunchImages"];
	for (NSDictionary *launchImage in launchImages) {
		if ([NSStringFromCGSize([UIScreen mainScreen].bounds.size) isEqualToString:launchImage[@"UILaunchImageSize"]]) {
			return launchImage[@"UILaunchImageName"];
		}
	}
	return nil;
}

+ (BOOL)allowsBackgroundLocationUpdates {
	NSArray *backgroundModes = [NSBundle mainBundle].infoDictionary[@"UIBackgroundModes"];
	return backgroundModes && [backgroundModes containsObject:@"location"];
}

+ (CGFloat)deltaRetinaDisplayScreenWidth {
	return CGRectGetWidth([UIScreen mainScreen].bounds) - 320;
}

#pragma mark - Collection

+ (NSMutableArray *)mutableArray:(NSArray *)array {
	return array ? [NSMutableArray arrayWithArray:array] : [NSMutableArray array];
}

+ (void)removeObjectsInArray:(NSMutableArray *)array fromIndex:(NSUInteger)anIndex {
	if (array.count <= anIndex) {
		return;
	}
	[array removeObjectsInRange:NSMakeRange(anIndex, array.count - anIndex)];
}

+ (NSArray *)unshiftObject:(id)obj toArray:(NSMutableArray *)array count:(NSInteger)count {
	if ([array containsObject:obj]) {
		[array removeObject:obj];
	}
	[array insertObject:obj atIndex:0];
	if (array.count > count) {
		[array removeObject:array.lastObject];
	}
	return array;
}

+ (id)objectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate fromArray:(NSArray *)array {
	NSInteger idx = [array indexOfObjectPassingTest:predicate];
	if (idx != NSNotFound) {
		return array[idx];
	}
	return nil;
}

+ (id)objectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate fromArrays:(NSArray *)arrays {
	return [self objectPassingTest:predicate fromArray:[arrays valueForKeyPath:@"@unionOfArrays.self"]];
}

+ (id)objectFilteredByClass:(Class)klass fromArray:(NSArray *)array {
	return [array bk_match:^BOOL (id obj) {
		return [obj isKindOfClass:klass];
	}];
}

+ (NSDictionary *)nonNullDictionary:(NSDictionary *)dictionary {
	return [dictionary bk_select:^BOOL (id key, id obj) {
		if (!key || key == [NSNull null]) {
			return NO;
		}
		if (!obj || obj == [NSNull null]) {
			return NO;
		}
		return YES;
	}];
}

+ (NSDictionary *)nonEmptyDictionary:(NSDictionary *)dictionary {
	return [[self nonNullDictionary:dictionary] bk_select:^BOOL (id key, id obj) {
		if ([obj isKindOfClass:[NSString class]] && [obj length] == 0) {
			return NO;
		}
		return YES;
	}];
}

+ (NSDictionary *)nonZeroDictionary:(NSDictionary *)dictionary {
	return [[self nonEmptyDictionary:dictionary] bk_select:^BOOL (id key, id obj) {
		if ([obj isKindOfClass:[NSNumber class]] && ![obj boolValue]) {
			return NO;
		}
		return YES;
	}];
}

+ (NSDictionary *)dictionaryForKeys:(NSArray *)keys retainEntries:(NSDictionary *)entries {
	NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjects:keys forKeys:keys];
	[dict removeObjectsForKeys:entries.allValues];
	[dict addEntriesFromDictionary:entries];
	return [NSDictionary dictionaryWithDictionary:dict];
}

+ (NSArray *)powerSet:(NSArray *)array {
	NSInteger length = array.count;
	if (length == 0) {
		return [NSArray arrayWithObject:[NSArray array]];
	}

	// get an object from the array and the array without that object
	id lastObject = [array lastObject];
	NSArray *arrayLessOne = [array subarrayWithRange:NSMakeRange(0, length - 1)];

	// compute the powerset of the array without that object
	// recursion makes me happy
	NSArray *powerSetLessOne = [self powerSet:arrayLessOne];

	// powerset is the union of the powerSetLessOne and powerSetLessOne where
	// each element is unioned with the removed element
	NSMutableArray *powerset = [NSMutableArray arrayWithArray:powerSetLessOne];

	// add the removed object to every element of the recursive power set
	for (NSArray *lessOneElement in powerSetLessOne) {
		[powerset addObject:[lessOneElement arrayByAddingObject:lastObject]];
	}
	return [NSArray arrayWithArray:powerset];
}

#pragma mark - String

+ (NSString *)jsonStringWithDictionary:(NSDictionary *)dictionary {
	return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:NULL] encoding:NSUTF8StringEncoding];
}

+ (NSString *)stringWithDeviceToken:(NSData *)deviceToken {
	const char *data = deviceToken.bytes;
	NSMutableString *string = [NSMutableString string];
	for (int i = 0; i < deviceToken.length; i++) {
		[string appendFormat:@"%02.2hhX", data[i]];
	}
	return [NSString stringWithString:string];
}

+ (NSString *)normalizeUserGeneratedContentString:(NSString *)string {
	NSMutableString *mutableString = [NSMutableString stringWithString:string];
	CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformToLatin, false);
	CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformStripCombiningMarks, false);
	return [NSString stringWithString:mutableString];
}

#pragma mark - AttributedString

+ (NSAttributedString *)attributedStringPrependingAsteriskToString:(NSString *)string {
	NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
	[attributedString appendAttributedString:[self attributedStringForAsteriskWithFont:nil]];
	[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:@" "]];
	[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:string]];
	return [[NSAttributedString alloc] initWithAttributedString:attributedString];
}

+ (NSAttributedString *)attributedStringForAsteriskWithFont:(UIFont *)font {
	NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] im_initWithString:@"*" attributes:@{ NSForegroundColorAttributeName : [UIColor redColor] }];
	if (font) {
		[attributedString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, 1)];
	}
	return [[NSAttributedString alloc] initWithAttributedString:attributedString];
}

+ (NSAttributedString *)attributedStringForImage:(UIImage *)image {
	NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
	textAttachment.image = image;
	return [NSAttributedString attributedStringWithAttachment:textAttachment];
}

+ (NSAttributedString *)attributedStringForImage:(UIImage *)image verticalAlignWithFont:(UIFont *)font {
	NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
	textAttachment.image = image;
	if (font) {
		textAttachment.bounds = CGRectMake(0, (font.ascender + font.descender - image.size.height) / 2, image.size.width, image.size.height);
	}
	return [NSAttributedString attributedStringWithAttachment:textAttachment];
}

#pragma mark - XML

+ (NSString *)noDeclarationXmlString:(NSString *)xmlString {
	if ([StringUtils isEmpty:xmlString]) {
		return nil;
	}
	NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"<\\?xml.*?\\?>" options:NSRegularExpressionCaseInsensitive error:NULL];
	NSRange range = [regex rangeOfFirstMatchInString:xmlString options:0 range:NSMakeRange(0, xmlString.length)];
	if (range.location != NSNotFound) {
		return [xmlString stringByReplacingCharactersInRange:range withString:@""];
	}
	return xmlString;
}

+ (NSString *)xmlStringWithValue:(id)value forElement:(NSString *)elementName {
	return [NSString stringWithFormat:@"<%@>%@</%@>", elementName, value, elementName];
}

+ (NSString *)xmlStringWithProperties:(NSDictionary *)dictionary {
	NSMutableString *string = [NSMutableString string];
	for (id key in [self nonNullDictionary:dictionary].allKeys) {
		[string im_appendString:[self xmlStringWithValue:dictionary[key] forElement:key]];
	}
	return [NSString stringWithString:string];
}

+ (NSString *)xmlStringWithNonZeroProperties:(NSDictionary *)dictionary {
	NSMutableString *string = [NSMutableString string];
	for (id key in [self nonZeroDictionary:dictionary].allKeys) {
		[string im_appendString:[self xmlStringWithValue:dictionary[key] forElement:key]];
	}
	return [NSString stringWithString:string];
}

+ (NSString *)firstValueMatchedInString:(NSString *)xmlString forElement:(NSString *)elementName {
	NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"<%@>(.*)</%@>", elementName, elementName] options:NSRegularExpressionDotMatchesLineSeparators error:NULL];
	NSTextCheckingResult *match = [regex firstMatchInString:xmlString options:0 range:NSMakeRange(0, xmlString.length)];
	if (match && match.numberOfRanges > 1) {
		return [xmlString substringWithRange:[match rangeAtIndex:1]];
	}
	return nil;
}

+ (NSArray *)allValuesMatchedInString:(NSString *)xmlString forElement:(NSString *)elementName {
	NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"<%@>(.*?)</%@>", elementName, elementName] options:NSRegularExpressionDotMatchesLineSeparators error:NULL];
	NSArray *matchs = [regex matchesInString:xmlString options:NSMatchingReportProgress range:NSMakeRange(0, [xmlString length])];
	return [matchs bk_map:^NSString *(NSTextCheckingResult *match) {
		return [xmlString substringWithRange:[match rangeAtIndex:1]];
	}];
}

+ (NSDictionary *)dictionaryWithXmlString:(NSString *)xmlString forElement:(NSString *)elementName {
	NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"<%@>.*</%@>", elementName, elementName] options:NSRegularExpressionDotMatchesLineSeparators error:NULL];
	NSRange range = [regex rangeOfFirstMatchInString:xmlString options:0 range:NSMakeRange(0, xmlString.length)];
	if (range.location != NSNotFound) {
		return [NSDictionary dictionaryWithXMLString:[xmlString substringWithRange:range]];
	}
	return nil;
}

#pragma mark - HTML

+ (NSAttributedString *)attributedStringWithHtmlString:(NSString *)htmlString {
	if ([StringUtils isEmpty:htmlString]) {
		return nil;
	}
	if (![[self sharedHtmlStringCache] objectForKey:@(htmlString.hash)]) {
		NSAttributedString *attributedString = [[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding] options:@{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding) } documentAttributes:NULL error:NULL];
		BOOL isHtml = attributedString.string.hash != htmlString.hash;
		[[self sharedHtmlStringCache] setObject:isHtml ? attributedString : [NSNull null] forKey:@(htmlString.hash)];
	}
	if ([[self sharedHtmlStringCache] objectForKey:@(htmlString.hash)] == [NSNull null]) {
		return nil;
	}
	return [[self sharedHtmlStringCache] objectForKey:@(htmlString.hash)];
}

+ (NSAttributedString *)attributedStringWithHtmlString:(NSString *)htmlString fixAttributes:(NSDictionary *)attributes {
	NSAttributedString *originalAttributedString = [CocoaUtils attributedStringWithHtmlString:htmlString];
	if (!originalAttributedString) {
		return originalAttributedString;
	}
	NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:originalAttributedString];
	[attributedString beginEditing];
	[attributedString removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, attributedString.length)];
	if (attributes[NSFontAttributeName]) {
		UIFont *sepcifiedFont = attributes[NSFontAttributeName];
		NSMutableDictionary *dict = [NSMutableDictionary dictionary];
		[attributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedString.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(UIFont *font, NSRange range, BOOL *stop) {
			dict[[NSValue valueWithRange:range]] = font.fontDescriptor;
		}];
		for (NSValue *key in dict.allKeys) {
			NSRange range = key.rangeValue;
			UIFontDescriptor *fontDescriptor = dict[key];
			[attributedString addAttribute:NSFontAttributeName value:[UIFont fontWithDescriptor:[sepcifiedFont.fontDescriptor fontDescriptorWithSymbolicTraits:fontDescriptor.symbolicTraits] size:0] range:range];
		}
	}
	if (attributes[NSForegroundColorAttributeName]) {
		[attributedString addAttribute:NSForegroundColorAttributeName value:attributes[NSForegroundColorAttributeName] range:NSMakeRange(0, attributedString.length)];
	}
	[attributedString endEditing];
	return [[NSAttributedString alloc] initWithAttributedString:attributedString];
}

+ (BOOL)isHtmlString:(NSString *)htmlString {
	NSAttributedString *attributedString = [self attributedStringWithHtmlString:htmlString];
	return attributedString ? YES : NO;
}

+ (NSString *)stripHtmlString:(NSString *)htmlString {
	NSAttributedString *attributedString = [self attributedStringWithHtmlString:htmlString];
	if (!attributedString) {
		return htmlString;
	}
	NSString *string = attributedString.string;
	// string = [[string componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\uFFFC"]] componentsJoinedByString:@""];
	return string;
}

#pragma mark - URL

+ (NSString *)securityUrlString:(NSString *)urlString {
	if ([StringUtils isEmpty:urlString]) {
		return nil;
	}
	NSURLComponents *urlComponents = [NSURLComponents componentsWithString:urlString];
	if ([urlComponents.scheme isEqualToString:@"http"]) {
		urlComponents.scheme = @"https";
	}
	if ([urlComponents respondsToSelector:@selector(string)]) {
		return urlComponents.string;
	} else {
		return urlComponents.URL.absoluteString;
	}
}

+ (BOOL)canBeConvertedToUrlEncodedFromString:(NSString *)string usingEncoding:(NSStringEncoding)encoding {
	if ([StringUtils isEmpty:string]) {
		return YES;
	}
	return ![RKURLEncodedStringFromDictionaryWithEncoding(@{ @"" : string }, encoding) isEqualToString:[NSString stringWithFormat:@"=%@", nil]];
}

#pragma mark - Number

+ (NSString *)percentStringWithNumber:(NSNumber *)number {
	NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
	formatter.numberStyle = NSNumberFormatterPercentStyle;
	return [formatter stringFromNumber:number];
}

#pragma mark - Date

+ (NSString *)localizedStringFromDate:(NSDate *)date template:(NSString *)template {
	return [DateFormatUtils format:date pattern:[NSDateFormatter dateFormatFromTemplate:template options:0 locale:[self applicationLocale]] locale:[self applicationLocale]];
}

+ (NSInteger)dateIntervalForComponent:(NSCalendarUnit)unit fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
	return [[[NSCalendar currentCalendar] components:unit fromDate:fromDate toDate:toDate options:0] valueForComponent:unit];
}

+ (NSArray *)continuousDatesInRange:(NSRange)range ofUnit:(NSCalendarUnit)unit forDate:(NSDate *)date {
	NSMutableArray *array = [NSMutableArray array];
	for (NSInteger i = range.location; i < range.length; i++) {
		[array addObject:[DateUtils add:date unit:unit amount:i]];
	}
	return [NSArray arrayWithArray:array];
}

+ (NSString *)durationStringWithUnitsStyle:(NSDateComponentsFormatterUnitsStyle)unitsStyle locale:(NSLocale *)locale fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
	if (!fromDate || !toDate) {
		return nil;
	}
	NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
	NSCalendar *calendar = [NSCalendar currentCalendar];
	calendar.locale = locale;
	formatter.calendar = calendar;
	formatter.unitsStyle = unitsStyle;
	return [formatter stringFromTimeInterval:[toDate timeIntervalSinceDate:fromDate]];
}

+ (NSTimeInterval )durationTimeWithUnitsStyle:(NSDateComponentsFormatterUnitsStyle)unitsStyle locale:(NSLocale *)locale fromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
	if (!fromDate || !toDate) {
		return 0;
	}
	NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
	NSCalendar *calendar = [NSCalendar currentCalendar];
	calendar.locale = locale;
	formatter.calendar = calendar;
	formatter.unitsStyle = unitsStyle;
	return [toDate timeIntervalSinceDate:fromDate];
}
#pragma mark - IndexPath

+ (NSIndexPath *)nextIndexPath:(NSIndexPath *)indexPath {
	return [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
}

+ (NSIndexPath *)prevIndexPath:(NSIndexPath *)indexPath {
	return [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:indexPath.section];
}

+ (NSArray *)indexPathsForRowsWithRange:(NSRange)range inSection:(NSUInteger)section {
	NSMutableArray *array = [NSMutableArray array];
	for (NSUInteger row = range.location; row < NSMaxRange(range); row++) {
		[array addObject:[NSIndexPath indexPathForRow:row inSection:section]];
	}
	return [NSArray arrayWithArray:array];
}

+ (NSIndexPath *)indexPathOfObjectIdenticalTo:(id)obj fromArray:(NSArray *)array {
	return [self indexPathOfObjectIdenticalTo:obj fromArray:array inSection:0];
}

+ (NSIndexPath *)indexPathOfObjectIdenticalTo:(id)obj fromArray:(NSArray *)array inSection:(NSUInteger)section {
	for (int row = 0; row < array.count; row++) {
		if ([array[row] isEqual:obj]) {
			return [NSIndexPath indexPathForRow:row inSection:section];
		}
	}
	return nil;
}

+ (NSIndexPath *)indexPathOfObjectIdenticalTo:(id)obj fromArrays:(NSArray *)arrays {
	for (int section = 0; section < arrays.count; section++) {
		NSIndexPath *indexPath = [self indexPathOfObjectIdenticalTo:obj fromArray:arrays[section] inSection:section];
		if (indexPath) {
			return indexPath;
		}
	}
	return nil;
}

#pragma mark - UserDefaults

+ (void)synchronizeUserDefaultsBySettingWithObject:(id)obj forKey:(NSString *)key {
	[[NSUserDefaults standardUserDefaults] setObject:obj forKey:key];
}

+ (id)objectFromUserDefaultsForKey:(NSString *)key {
	return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}

#pragma mark - Graphics

+ (UIColor *)colorWithRGBs:(NSUInteger)colors {
	return [UIColor im_colorWithRed:((colors & 0xFF0000) >> 16) / 255.0 green:((colors & 0x00FF00) >> 8) / 255.0 blue:((colors & 0x0000FF) >> 0) / 255.0 alpha:1];
}

+ (UIFont *)renderedFont:(UIFont *)font withSymbolicTraits:(UIFontDescriptorSymbolicTraits)symbolicTraits {
	return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits] size:font.pointSize];
}

// Chameleon
+ (UIStatusBarStyle)contrastingStatusBarStyleForColor:(UIColor *)backgroundColor {
	// Calculate Luminance
	CGFloat luminance;
	CGFloat red, green, blue;

	// Check for clear or uncalculatable color and assume white
	if (![backgroundColor getRed:&red green:&green blue:&blue alpha:nil]) {
		return UIStatusBarStyleDefault;
	}

	// Relative luminance in colorimetric spaces - http://en.wikipedia.org/wiki/Luminance_(relative)
	red *= 0.2126f; green *= 0.7152f; blue *= 0.0722f;
	luminance = red + green + blue;

	return (luminance > 0.6f) ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent;
}

#pragma mark - View

+ (CALayer *)sublayerWithName:(NSString *)name fromSuperlayer:(CALayer *)superlayer {
	return [superlayer.sublayers bk_match:^BOOL (CALayer *sublayer) {
		return [sublayer.name isEqualToString:name];
	}];
}

+ (NSArray *)descendantsOfView:(UIView *)superview {
	NSMutableArray *array = [NSMutableArray array];
	for (UIView *view in superview.subviews) {
		[array addObjectsFromArray:[self descendantsOfView:view]];
		[array addObject:view];
	}
	return [NSArray arrayWithArray:array];
}

+ (void)autoVerticalAlignmentToSubviews:(NSArray *)subviews spacing:(CGFloat)spacing {
	for (UIView *view in subviews) {
		[view autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:spacing];
		[view autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:spacing];
	}
	[self autoVerticalDistributionToSubviews:subviews spacing:spacing];
}

+ (void)autoVerticalDistributionToSubviews:(NSArray *)subviews spacing:(CGFloat)spacing {
	[self autoVerticalDistributionToSubviews:subviews spacing:spacing shouldSpacingToSuperviewEdge:YES];
}

+ (void)autoVerticalDistributionToSubviews:(NSArray *)subviews spacing:(CGFloat)spacing shouldSpacingToSuperviewEdge:(BOOL)shouldSpacingToSuperviewEdge {
	UIView *previousView = nil;
	for (UIView *view in subviews) {
		if (previousView) {
			[view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:previousView withOffset:spacing];
		}
		previousView = view;
	}
	if (shouldSpacingToSuperviewEdge) {
		[subviews.firstObject autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:spacing];
		[subviews.lastObject autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:spacing];
	}
}

+ (void)removeGestureRecognizerKindOfClass:(Class)klass fromView:(UIView *)view {
	for (UIGestureRecognizer *gestureRecognizer in view.gestureRecognizers) {
		if ([gestureRecognizer isKindOfClass:klass]) {
			[view removeGestureRecognizer:gestureRecognizer];
		}
	}
}

+ (void)pinButton:(UIButton *)button positioningImageEdge:(NSLayoutAttribute)edge nearTitleSpacing:(CGFloat)spacing {
	CGFloat imageWith = button.currentImage.size.width;
	CGFloat imageHeight = button.currentImage.size.height;
	CGSize labelSize = CGSizeZero;
	if (![StringUtils isEmpty:button.currentTitle]) {
		labelSize = [button.currentTitle boundingRectWithSize:button.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{ NSFontAttributeName : button.titleLabel.font } context:nil].size;
	} else if (![StringUtils isEmpty:button.currentAttributedTitle]) {
		labelSize = [button.currentAttributedTitle boundingRectWithSize:button.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil].size;
	}
	CGFloat labelWidth = labelSize.width;
	CGFloat labelHeight = labelSize.height;
	CGFloat imageOffsetX = (imageWith + labelWidth) / 2 - imageWith / 2;
	CGFloat imageOffsetY = imageHeight / 2 + spacing / 2;
	CGFloat labelOffsetX = (imageWith + labelWidth / 2) - (imageWith + labelWidth) / 2;
	CGFloat labelOffsetY = labelHeight / 2 + spacing / 2;

	CGFloat tempWidth = MAX(labelWidth, imageWith);
	CGFloat changedWidth = labelWidth + imageWith - tempWidth;
	CGFloat tempHeight = MAX(labelHeight, imageHeight);
	CGFloat changedHeight = labelHeight + imageHeight +spacing - tempHeight;

	switch (edge) {
		case NSLayoutAttributeLeft:
			button.imageEdgeInsets = UIEdgeInsetsMake(0, -spacing / 2, 0, spacing / 2);
			button.titleEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, -spacing / 2);
			button.contentEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, spacing / 2);
			break;

		case NSLayoutAttributeRight:
			button.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth + spacing / 2, 0, -(labelWidth + spacing / 2));
			button.titleEdgeInsets = UIEdgeInsetsMake(0, -(imageHeight + spacing / 2), 0, imageHeight + spacing / 2);
			button.contentEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, spacing / 2);
			break;

		case NSLayoutAttributeTop:
			button.imageEdgeInsets = UIEdgeInsetsMake(-imageOffsetY, imageOffsetX, imageOffsetY, -imageOffsetX);
			button.titleEdgeInsets = UIEdgeInsetsMake(labelOffsetY, -labelOffsetX, -labelOffsetY, labelOffsetX);
			button.contentEdgeInsets = UIEdgeInsetsMake(imageOffsetY, -changedWidth / 2, changedHeight - imageOffsetY, -changedWidth / 2);
			break;

		case NSLayoutAttributeBottom:
			button.imageEdgeInsets = UIEdgeInsetsMake(imageOffsetY, imageOffsetX, -imageOffsetY, -imageOffsetX);
			button.titleEdgeInsets = UIEdgeInsetsMake(-labelOffsetY, -labelOffsetX, labelOffsetY, labelOffsetX);
			button.contentEdgeInsets = UIEdgeInsetsMake(changedHeight - imageOffsetY, -changedWidth / 2, imageOffsetY, -changedWidth / 2);
			break;

		case NSLayoutAttributeLeading:
			button.imageEdgeInsets = UIEdgeInsetsMake(0, -spacing / 2, 0, spacing / 2);
			button.titleEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, -spacing / 2);
			button.contentEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, spacing / 2);
			break;

		case NSLayoutAttributeTrailing:
			button.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth + spacing / 2, 0, -(labelWidth + spacing / 2));
			button.titleEdgeInsets = UIEdgeInsetsMake(0, -(imageHeight + spacing / 2), 0, imageHeight + spacing / 2);
			button.contentEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, spacing / 2);
			break;

		default:
			break;
	}
}

+ (void)addKeyboardToolbarForTextField:(UITextField *)textField title:(NSString *)title handler:(void (^) (void))handler {
	[textField addPreviousNextDoneOnKeyboardWithTarget:[IQKeyboardManager sharedManager] previousAction:@selector(goPrevious) nextAction:@selector(goNext) doneAction:@selector(resignFirstResponder)];
	if ([textField.inputAccessoryView isKindOfClass:[IQToolbar class]]) {
		IQToolbar *toolbar = textField.inputAccessoryView;
		NSInteger idx = [toolbar.items indexOfObjectPassingTest:^BOOL (id obj, NSUInteger idx, BOOL *stop) {
			return [obj isKindOfClass:[IQTitleBarButtonItem class]];
		}];
		if (idx != NSNotFound) {
			NSMutableArray *items = [NSMutableArray arrayWithArray:toolbar.items];
			items[idx] = [[IQTitleBarButtonItem alloc] im_initWithTitle:title style:UIBarButtonItemStylePlain handler:handler ? ^(UIBarButtonItem *barButtonItem) {
				handler();
			} : nil];
			toolbar.items = [NSArray arrayWithArray:items];
		}
	}
}

+ (void)configureStarRatingView:(HCSStarRatingView *)ratingView editing:(BOOL)editing {
	if (!editing) {
		ratingView.allowsHalfStars = YES;
		ratingView.accurateHalfStars = YES;
		ratingView.userInteractionEnabled = NO;
	} else {
		ratingView.minimumValue = 1;
		ratingView.value = ratingView.maximumValue;
	}
}

+ (void)configureCalloutView:(SMModernCalloutView *)calloutView annotation:(PointAnnotation *)annotation {
	calloutView.textLabel.numberOfLines = 0;
	calloutView.detailTextLabel.numberOfLines = 0;
	if (annotation.attributedTitle) {
		calloutView.textLabel.attributedText = annotation.attributedTitle;
	} else if ([CocoaUtils isHtmlString:annotation.title]) {
		calloutView.textLabel.attributedText = [CocoaUtils attributedStringWithHtmlString:annotation.title fixAttributes:@{ NSFontAttributeName : calloutView.textLabel.font }];
	} else {
		calloutView.textLabel.text = annotation.title;
	}
	if (annotation.attributedSubtitle) {
		calloutView.detailTextLabel.attributedText = annotation.attributedSubtitle;
	} else if ([CocoaUtils isHtmlString:annotation.subtitle]) {
		calloutView.detailTextLabel.attributedText = [CocoaUtils attributedStringWithHtmlString:annotation.subtitle fixAttributes:@{NSFontAttributeName : calloutView.detailTextLabel.font}];
	} else {
		calloutView.detailTextLabel.text = annotation.subtitle;
	}
}

+ (void)showBusyIndicator:(UIView *)view {
	MBProgressHUD *busyIndicator = [MBProgressHUD showHUDAddedTo:view animated:YES];
	[view bringSubviewToFront:busyIndicator];
	[self adjustBusyIndicatorOffset:busyIndicator];
}

+ (void)showBusyIndicator:(UIView *)view title:(NSString *)title {
	[self showBusyIndicator:view title:title autoshrink:YES];
}

+ (void)showBusyIndicator:(UIView *)view title:(NSString *)title autoshrink:(BOOL)autoshrink {
	MBProgressHUD *busyIndicator = [MBProgressHUD showHUDAddedTo:view animated:YES];
	if (autoshrink) {
		busyIndicator.label.text = title;
	} else {
		busyIndicator.detailsLabel.text = title;
	}
	[self adjustBusyIndicatorOffset:busyIndicator];
}

+ (void)hideBusyIndicator:(UIView *)view {
	[MBProgressHUD hideHUDForView:view animated:YES];
}

+ (void)showNotificationWithMessage:(NSString *)message {
	[CRToastManager showNotificationWithMessage:message completionBlock:nil];
}

+ (void)showTooltipWithMessage:(NSString *)message inView:(UIView *)inView atFrame:(CGRect)frame{
	[[TooltipManager sharedManager] showTooltipWithMessage:message inView:inView atFrame:frame];
	
}

+ (void)showTooltipWithCustomView:(NSString *)customView inView:(UIView *)inView atFrame:(CGRect)frame {
	[[TooltipManager sharedManager] showTooltipWithCustomView:customView inView:inView atFrame:frame];
}

#pragma mark - TableView

+ (CGFloat)calculatedContentWidthForCell:(UITableViewCell *)cell onTableView:(UITableView *)tableView {
	CGFloat leftWidth = 0;
	if (cell.imageView.image) {
		leftWidth += cell.imageView.image.size.width + 15;
	}
	CGFloat rightWidth = 0;
	if (tableView.editing) {
		leftWidth += 22 + 15;
		if (cell.editingAccessoryView) {
			rightWidth += CGRectGetWidth(cell.editingAccessoryView.frame) + 15;
		} else {
			rightWidth += [UIConstants defaultTableViewCellAccessoryWidth:cell.editingAccessoryType];
		}
	} else {
		if (cell.accessoryView) {
			rightWidth += CGRectGetWidth(cell.accessoryView.frame) + 15;
		} else {
			rightWidth += [UIConstants defaultTableViewCellAccessoryWidth:cell.accessoryType];
		}
		if ([tableView.dataSource respondsToSelector:@selector(sectionIndexTitlesForTableView:)]) {
			if ([tableView.dataSource sectionIndexTitlesForTableView:tableView]) {
				static CGFloat w = 0;
				static dispatch_once_t onceToken;
				dispatch_once(&onceToken, ^{
					UIView *view = [self objectFilteredByClass:NSClassFromString(@"UITableViewIndex") fromArray:tableView.subviews];
					w = CGRectGetWidth(view.frame);
				});
				rightWidth += w > 0 ? w : [UIConstants defaultTableViewIndexWidth];
			}
		}
	}
	return CGRectGetWidth(tableView.frame) - rightWidth - leftWidth;
}

+ (void)hideSeparatorOfCell:(UITableViewCell *)cell {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [cell bk_eachSubview:^(UIView * separatorView) {
            if ([separatorView isMemberOfClass:NSClassFromString(@"_UITableViewCellSeparatorView")]) {
                separatorView.hidden = YES;
            }
        }];
    });
}

+ (void)fillSeparatorOfCell:(UITableViewCell *)cell {
	[self layoutSeparatorOfCell:cell inset:UIEdgeInsetsZero];
}

#pragma mark - ViewController

+ (UIViewController *)topMostViewController {
	if (topMostViewController) {
		return topMostViewController;
	}
	return [UIViewController topMostController];
}

+(UITableView *)topTableView{
	UIViewController *this = [self topMostViewController];
	UITableView *tablview = nil;
	if([this isKindOfClass:[UITableViewController class]]){
		UITableViewController *tableViewController = (UITableViewController *)this;
		tablview = tableViewController.tableView;
	}else{
		tablview = (UITableView *)[this.view.subviews bk_match:^BOOL(UIView * obj) {
			if([obj isKindOfClass:[UITableView class]]){
				return YES;
			}
			return NO;
		}];
	}
	return tablview;
}

+ (void)proxyTopMostViewController:(UIViewController *)viewController {
	topMostViewController = viewController;
}

+ (void)proxyAlertControllerAnimated:(BOOL)animated {
	alertControllerAnimated = animated;
}

+ (UINavigationController *)wrappingNavigationControllerAroundViewController:(UIViewController *)viewController {
	return [[CustomNavigationController alloc] initWithRootViewController:viewController];
}

+ (UIViewController *)prevViewControllerOnNavigationController:(UINavigationController *)navigationController {
	if (navigationController.viewControllers.count > 1) {
		return navigationController.viewControllers.reverseObjectEnumerator.allObjects[1];
	}
	return nil;
}

+ (UIViewController *)lastViewControllerWithClass:(Class)klass onNavigationController:(UINavigationController *)navigationController {
	return [navigationController.viewControllers.reverseObjectEnumerator.allObjects bk_match:^BOOL (id obj) {
		return [obj isKindOfClass:klass];
	}];
}

+ (void)popViewControllerInQuantity:(NSInteger)quantity onNavigationController:(UINavigationController *)navigationController animated:(BOOL)animated {
	if (quantity >= 0 && quantity < navigationController.viewControllers.count) {
		[navigationController popToViewController:navigationController.viewControllers.reverseObjectEnumerator.allObjects[quantity] animated:animated];
	}
}

#pragma mark - AlertController

+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
	[self showAlertWithTitle:title message:message cancelActionTitle:IMLocalizedString(@"OK")];
}

+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)message dismissalHandler:(void (^)(void))dismissalHandler {
	[self showAlertWithTitle:title message:message cancelActionTitle:IMLocalizedString(@"OK") cancelActionHandler:dismissalHandler];
}

+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)message cancelActionTitle:(NSString *)cancelActionTitle {
	[self showAlertWithTitle:title message:message cancelActionTitle:cancelActionTitle cancelActionHandler:nil];
}

+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)message cancelActionTitle:(NSString *)cancelActionTitle cancelActionHandler:(void (^)(void))cancelActionHandler {
	[self showMessageBoxWithTitle:title message:message cancelActionTitle:cancelActionTitle otherActionTitles:nil actionHandler:cancelActionHandler ? ^(NSInteger buttonIndex) {
		if (buttonIndex == -1) {
			cancelActionHandler();
		}
	} : nil];
}

+ (void)showConfirmWithTitle:(NSString *)title message:(NSString *)message dismissalHandler:(void (^)(void))dismissalHandler {
	[self showConfirmWithTitle:title message:message otherActionTitle:IMLocalizedString(@"OK") otherActionHandler:dismissalHandler];
}

+ (void)showConfirmWithTitle:(NSString *)title message:(NSString *)message otherActionTitle:(NSString *)otherActionTitle otherActionHandler:(void (^)(void))otherActionHandler {
	[self showConfirmWithTitle:title message:message cancelActionTitle:IMLocalizedString(@"Cancel") otherActionTitle:otherActionTitle otherActionHandler:otherActionHandler];
}

+ (void)showConfirmWithTitle:(NSString *)title message:(NSString *)message cancelActionTitle:(NSString *)cancelActionTitle otherActionTitle:(NSString *)otherActionTitle otherActionHandler:(void (^)(void))otherActionHandler {
	[self showMessageBoxWithTitle:title message:message cancelActionTitle:cancelActionTitle otherActionTitles:@[otherActionTitle] actionHandler:otherActionHandler ? ^(NSInteger buttonIndex) {
		if (buttonIndex == 0) {
			otherActionHandler();
		}
	} : nil];
}

+ (void)showMessageBoxWithTitle:(NSString *)title message:(NSString *)message cancelActionTitle:(NSString *)cancelActionTitle otherActionTitles:(NSArray *)otherActionTitles actionHandler:(void (^)(NSInteger buttonIndex))actionHandler {
		UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
		if (cancelActionTitle) {
			[alertController addAction:[UIAlertAction actionWithTitle:cancelActionTitle style:UIAlertActionStyleCancel handler:actionHandler ? ^(UIAlertAction *action) {
				actionHandler(-1);
			} : nil]];
		}
		for (NSString *otherButtonTitle in otherActionTitles) {
			[alertController addAction:[UIAlertAction actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:actionHandler ? ^(UIAlertAction *action) {
				actionHandler([otherActionTitles indexOfObject:otherButtonTitle]);
			} : nil]];
		}
		UIViewController *this = [self topMostViewController];
		[this presentViewController:alertController animated:alertControllerAnimated completion:nil];
}

+ (void)showPlainInputBoxWithTitle:(NSString *)title message:(NSString *)message configurationHandler:(void (^)(UITextField *textField))configurationHandler dismissalHandler:(void (^) (UITextField *textField))dismissalHandler {
	[self showInputBoxWithStyle:UIAlertViewStylePlainTextInput title:title message:message configurationHandler:configurationHandler ? ^(NSInteger inputIndex, UITextField *textField) {
		if (inputIndex == 0) {
			configurationHandler(textField);
		}
	} : nil cancelActionTitle:IMLocalizedString(@"Cancel") otherActionTitles:@[IMLocalizedString(@"OK")] actionHandler:dismissalHandler ? ^(NSInteger buttonIndex, NSArray *textFields) {
		if (buttonIndex == 0) {
			dismissalHandler(textFields[0]);
		}
	} : nil];
}

+ (void)showInputBoxWithStyle:(UIAlertViewStyle)alertViewStyle title:(NSString *)title message:(NSString *)message configurationHandler:(void (^)(NSInteger inputIndex, UITextField *textField))configurationHandler cancelActionTitle:(NSString *)cancelActionTitle otherActionTitles:(NSArray *)otherActionTitles actionHandler:(void (^)(NSInteger buttonIndex, NSArray *textFields))actionHandler {
		UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
		if (alertViewStyle == UIAlertViewStylePlainTextInput || alertViewStyle == UIAlertViewStyleLoginAndPasswordInput) {
			[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
				if (configurationHandler) {
					configurationHandler(0, textField);
				}
			}];
		}
		if (alertViewStyle == UIAlertViewStyleSecureTextInput || alertViewStyle == UIAlertViewStyleLoginAndPasswordInput) {
			[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
				textField.secureTextEntry = YES;
				if (configurationHandler) {
					configurationHandler(1, textField);
				}
			}];
		}
		if (cancelActionTitle) {
			[alertController addAction:[UIAlertAction actionWithTitle:cancelActionTitle style:UIAlertActionStyleCancel handler:actionHandler ? ^(UIAlertAction *action) {
				actionHandler(-1, alertController.textFields);
			} : nil]];
		}
		for (NSString *otherButtonTitle in otherActionTitles) {
			[alertController addAction:[UIAlertAction actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:actionHandler ? ^(UIAlertAction *action) {
				actionHandler([otherActionTitles indexOfObject:otherButtonTitle], alertController.textFields);
			} : nil]];
		}
		UIViewController *this = [self topMostViewController];
		[this presentViewController:alertController animated:alertControllerAnimated completion:nil];
}

#pragma mark - Contacts

+ (void)showSelectionOfContactWithDisplayedPropertyKeys:(NSArray *)displayedPropertyKeys completion:(void (^) (id contactOrProperty))completion {
	if (![ContactsManager authorizeContacts]) {
		[CocoaUtils showConfirmWithTitle:IMLocalizedString(@"Information") message:[NSString stringWithFormat:IMLocalizedString(@"Please allow \"%@\" to access your contacts"), [CocoaUtils bundleName]] otherActionTitle:IMLocalizedString(@"Open Settings") otherActionHandler:^{
			[CocoaUtils openSettings];
		}];
		return;
	}
	[ContactsManager displayedPropertyKeys:displayedPropertyKeys completion:completion];
}

#pragma mark - CoreLocation

+ (CLLocationDistance)distanceFromCoordinate:(CLLocationCoordinate2D)fromCoordinate toCoordinate:(CLLocationCoordinate2D)toCoordinate {
	CLLocation *fromLocation = [[CLLocation alloc] initWithLatitude:fromCoordinate.latitude longitude:fromCoordinate.longitude];
	CLLocation *toLocation = [[CLLocation alloc] initWithLatitude:toCoordinate.latitude longitude:toCoordinate.longitude];
	return [fromLocation distanceFromLocation:toLocation];
}

+ (void)locationManager:(CLLocationManager *)manager requestLocationWithCompletion:(void (^) (BOOL granted, CLLocation *location, NSError *error))completion {
	if (!completion) {
		return;
	}
	if ([CLLocationManager locationServicesEnabled]) {
		[LocationManager locationManager:(CLLocationManager *)manager requestLocationWithCompletion:^(BOOL granted, CLLocation *location, NSError *error) {
			if (!granted) {
				[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Information") message:[NSString stringWithFormat:IMLocalizedString(@"Please allow \"%@\" to access your location"), [CocoaUtils bundleName]]];
			}
			completion(granted, location, error);
		}];
	} else {
		[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Information") message:IMLocalizedString(@"Please turn on your location services")];
		completion(NO, nil, nil);
	}
}

+ (void)geocoder:(CLGeocoder *)geocoder reverseGeocodeLocation:(CLLocation *)location completionHandler:(void (^)(CLPlacemark *placemark, NSError *error))completionHandler {
	if (!completionHandler) {
		return;
	}
	[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
		completionHandler(placemarks.firstObject, error);
	}];
}

#pragma mark - StoreKit

+ (void)loadProductWithITunesItemIdentifier:(NSString *)appId {
	UIViewController *this = [CocoaUtils topMostViewController];
	SKStoreProductViewController *viewController = [[SKStoreProductViewController alloc] init];
	[viewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:appId} completionBlock:^(BOOL result, NSError * _Nullable error) {
		if (result) {
			[this presentViewController:viewController animated:YES completion:nil];
		}
	}];
}

#pragma mark - Private

+ (void)layoutSeparatorOfCell:(UITableViewCell *)cell inset:(UIEdgeInsets)separatorInset {
	if (separatorInset.left < cell.separatorInset.left) {
		if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
			cell.layoutMargins = separatorInset;
		}
		if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
			cell.preservesSuperviewLayoutMargins = NO;
		}
	}
	cell.separatorInset = separatorInset;
}

+ (void)adjustBusyIndicatorOffset:(MBProgressHUD *)busyIndicator {
	// if ([busyIndicator.superview isKindOfClass:[UIScrollView class]]) {
	// UIScrollView *scrollView = busyIndicator.superview;
	// busyIndicator.offset = scrollView.contentOffset;
	// }
}

@end

