//
//  MVMCoreUIUtility.m
//  MVMCoreUI
//
//  Created by Scott Pfeil on 1/10/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

#import "MVMCoreUIUtility.h"
#import "MVMCoreUIConstants.h"
#import "MVMCoreUISession.h"
#import "MVMCoreUISplitViewController.h"
#import <MVMCoreUI/MVMCoreUI-Swift.h>
@import MVMCore.MVMCoreNavigationHandler;
@import MVMCore.MVMCoreGetterUtility;

@implementation MVMCoreUIUtility

#pragma mark - Getters

// The bundle for this framework
+ (nullable NSBundle *)bundleForMVMCoreUI {
    return [NSBundle bundleWithIdentifier:@"com.vzw.MVMCoreUI"];
}

+ (nullable NSString *)hardcodedStringWithKey:(nonnull NSString *)key {
    // Redirect key with relevant module.
    return [MVMCoreGetterUtility hardcodedStringWithKey:key bundle:[MVMCoreUIUtility bundleForMVMCoreUI]];
}

+ (nullable NSString *)localizedImageName:(nullable NSString *)imageName {
    if (imageName.length > 0) {
        if ([MVMCoreGetterUtility userPrefersSpanish]) {
            imageName = [NSString stringWithFormat:@"%@_es", imageName];
        } else {
            imageName = [NSString stringWithFormat:@"%@_en", imageName];
        }
    }
    return imageName;
}

+ (nullable UIImage *)imageNamed:(nullable NSString *)imageName {
    UIImage *image = nil;
    if (imageName.length > 0) {
        image = [UIImage imageNamed:imageName inBundle:[MVMCoreUIUtility bundleForMVMCoreUI] compatibleWithTraitCollection:nil];
    }
    return image;
}

+ (UIEdgeInsets)getMarginsForView:(nullable UIView *)view {
    return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing);
}

+ (UIViewController *)getCurrentVisibleController {
    UIViewController *baseViewController = [MVMCoreNavigationHandler sharedNavigationHandler].viewControllerToPresentOn ?: [UIApplication sharedApplication].keyWindow.rootViewController;
    UIViewController *viewController = nil;
    while (baseViewController.presentedViewController && !baseViewController.presentedViewController.isBeingDismissed) {
        viewController = baseViewController.presentedViewController;
        baseViewController = viewController;
    }
    if ([viewController isKindOfClass:[UINavigationController class]]) {
        viewController = ((UINavigationController *)viewController).topViewController;
    }
    // if it is not presented viewcontroller, existing BAU logic will be working
    if (!viewController) {
        viewController = [self getViewControllerTraversingManagers:[MVMCoreUISession sharedGlobal].navigationController.topViewController];
    }
    return viewController;
}

+ (UIViewController *)getViewControllerTraversingManagers:(UIViewController *)viewController {
    UIViewController *controller = viewController;
    while ([controller conformsToProtocol:@protocol(MVMCoreViewManagerProtocol)]) {
        controller = [controller performSelector:@selector(getCurrentViewController)];
    }
    return controller;
}

+ (BOOL)viewContainsAccessiblityFocus:(nonnull UIView *)view {
    if (!UIAccessibilityIsVoiceOverRunning()) {
        return NO;
    }
    id focusedElement = UIAccessibilityFocusedElement(UIAccessibilityNotificationVoiceOverIdentifier);
    if (![focusedElement isKindOfClass:[UIView class]]) {
        return NO;
    }
    __block BOOL containsFocus;
    [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
        containsFocus = [(UIView  *)focusedElement isDescendantOfView:view];
    }];
    return containsFocus;
}

+ (BOOL)isView:(nonnull UIView *)view visibleIn:(nonnull UIView *)rootView {
    return [view isDescendantOfView:rootView] && [self isViewVisibleInParent:view];
}

// Climb up the tree. Break early.
+ (BOOL)isViewVisibleInParent:(nonnull UIView *)view {
    UIView *superview = view.superview;
    UIView *ancestor = view;
    // Begin climbing its ancestor views, checking if it remains visible in *each* of their bounds and if the children at views closer to the front block the visibility of this view.
    while (superview) {
        if (superview.clipsToBounds && !CGRectIntersectsRect(superview.bounds, [view convertRect:view.bounds toView:superview])) {
            return false;
        } else {
            // Check the superview's children up to the common ancestor. (Reworking the ancestor would put us in an infinite loop and we want to ignore the children with a lower z-order.)
            for (UIView *subview in [superview.subviews reverseObjectEnumerator]) {
                if (subview == ancestor) {
                    break;
                } else if ([self doesView:subview cover:view]) {
                    return false;
                }
            }
        }
        ancestor = superview;
        superview = superview.superview;
    }
    return true;
}

// Climb down the tree.
+ (BOOL)doesView:(nonnull UIView *)overlappingView cover:(nonnull UIView *)view {
    if (![self isViewTransparent:overlappingView] && CGRectContainsRect(overlappingView.bounds, [view convertRect:view.bounds toView:overlappingView])) {
        return true;
    }
    for (UIView *subview in overlappingView.subviews) {
        if ([self doesView:subview cover:view]) {
            return true;
        }
    }
    return false;
}

+ (BOOL)isViewTransparent:(nonnull UIView *)view {
    return view.alpha < 1 || view.backgroundColor == nil || CGColorGetAlpha(view.backgroundColor.CGColor) < 1;
}

#pragma mark - Setters

+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom {
    view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing);
}

#pragma mark - Formatting

// As per business confirmation format should be xxx.xxx.xxxx same across application
+ (nullable NSString *)formatMdn:(nullable NSString *)mdn {
    if(mdn) {
        if(mdn.length == LengthContactNumber) {
            NSMutableString * newmdn = [NSMutableString stringWithCapacity:14];
            [newmdn appendString:mdn];
            [newmdn insertString:@"." atIndex:6];
            [newmdn insertString:@"." atIndex:3];
            
            return newmdn;
        }
    }
    
    return mdn;
}

+ (nullable NSString *)removeMdnFormat:(nullable NSString *)mdn {
    if (mdn) {
        NSCharacterSet *symbols = [NSCharacterSet characterSetWithCharactersInString:@".+()-  "]; // the 2nd space is a special space character (suspecting a tab) that iOS 8.3's Address book adds between ')' and 4th digit. 8.1 and earlier and sumulator 8.3 uses normal space character... lol.  Also ios has + in phone number sometimes
        return [[mdn componentsSeparatedByCharactersInSet:symbols] componentsJoinedByString:@""];
    }
    return mdn;
}

+ (nullable NSString *)getOrdinalStringForIndex:(nonnull NSNumber *)number {
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    formatter.numberStyle = NSNumberFormatterOrdinalStyle;
    return [formatter stringFromNumber:number];
}

#pragma mark - Validations

+ (BOOL)validateString:(nonnull NSString *)string withRegularExpression:(nonnull NSString *)regExpression {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regExpression];
    return [predicate evaluateWithObject:string];
}

+ (BOOL)validateMDNString:(nullable NSString *)mdn {
    return (mdn && mdn.length == LengthContactNumber) && [MVMCoreUIUtility validateString:mdn withRegularExpression:RegularExpressionDigitOnly];
}

+ (BOOL)validateInternationalMDNString:(nullable NSString *)mdn {
    return (mdn && mdn.length >= LengthContactNumber && mdn.length <= LengthInternationalNumber) && [MVMCoreUIUtility validateString:mdn withRegularExpression:RegularExpressionDigitOnly];
}

#pragma mark - Sizing

+ (CGFloat)getWidth {
    UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController.navigationController ?: [MVMCoreUISession sharedGlobal].navigationController;
    if (controller) {
        return CGRectGetWidth(controller.view.bounds);
    } else {
        return CGRectGetWidth([UIScreen mainScreen].bounds);
    }
}

+ (CGFloat)getTopSpaceWithStatusBarForView:(UIView *)view {
    CGPoint viewStartPointInWindowSpace = [[UIApplication sharedApplication].keyWindow convertPoint:CGPointMake(CGRectGetMinX(view.frame), CGRectGetMinY(view.frame)) fromView:[view superview]];
    if (CGRectGetMaxY([UIApplication sharedApplication].statusBarFrame) > viewStartPointInWindowSpace.y) {
        
        return CGRectGetMaxY([UIApplication sharedApplication].statusBarFrame) - viewStartPointInWindowSpace.y;
    } else {
        return 0;
    }
}

+ (CGFloat)getHeightOfView:(nonnull UIView *)view forWidth:(nullable NSNumber *)width {
    CGFloat floatWidth = (width != nil ? width.floatValue : [MVMCoreUIUtility getWidth]);
    return [view systemLayoutSizeFittingSize:CGSizeMake(floatWidth, UILayoutFittingCompressedSize.height) withHorizontalFittingPriority:1000 verticalFittingPriority:250].height;
}

+ (CGFloat)getRemainingSpaceBetweenContentAndFrame:(nonnull UIScrollView *)scrollview {
    
    // -1 at the end to avoid scrolling due to a thousandth of a float value.
    CGFloat frameHeight = CGRectGetHeight(scrollview.frame);
    CGFloat contentSizeHeight = scrollview.contentSize.height;
    
    // For some reason the content size calculates differently for the table view (doesn't include status bar).
    CGFloat topInset = scrollview.contentInset.top;
    CGFloat bottomInset = scrollview.contentInset.bottom;
    
    if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic || scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAlways) {
        topInset = scrollview.adjustedContentInset.top;
        bottomInset = scrollview.adjustedContentInset.bottom;
    }
    CGFloat remainingSpace = frameHeight - contentSizeHeight - topInset - bottomInset;
    return remainingSpace - 1;
}

+ (CGFloat)getVariableConstraintHeight:(CGFloat)currentConstant inScrollView:(nonnull UIScrollView *)scrollView minimumHeight:(CGFloat)minimumHeight {
    
    if (scrollView.contentSize.height > 0) {
        
        CGFloat remainingSpace = [MVMCoreUIUtility getRemainingSpaceBetweenContentAndFrame:scrollView];
        
        // There's a chance this can get called multiple times and the current constant has already been set to the remaining space previously, but now the remaining space is slightly different. If the current constant is not the minimum, we will assume that this happened and we have to append the current space.
        CGFloat newSpace = remainingSpace;
        if (!fequal(currentConstant, minimumHeight)) {
            newSpace += currentConstant;
        }
        
        if (newSpace > minimumHeight) {
            return newSpace;
        } else {
            return minimumHeight;
        }
    }
    return minimumHeight;
}

+ (void)sizeViewToFit:(UIView *)view {
    CGFloat height = [MVMCoreUIUtility getHeightOfView:view forWidth:nil];
    CGRect frame = view.frame;
    frame.size.height = height;
    view.frame = frame;
}

+ (void)sizeViewToFit:(UIView *)view forWidth:(nullable NSNumber *)width {
    CGFloat height = [MVMCoreUIUtility getHeightOfView:view forWidth:width];
    CGRect frame = view.frame;
    frame.size.height = height;
    view.frame = frame;
}

#pragma mark - Keyboard

+ (void)setScrollViewInsetForKeyboardShow:(nonnull NSNotification *)notification scrollView:(nonnull UIScrollView *)scrollView viewController:(nonnull UIViewController *)viewController rectToScrollTo:(nonnull CGRect (^)(void))rectToScrollTo {
    
    NSDictionary *info = [notification userInfo];
    
    CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    
    // Gets the scrollview end point in the window coordinate space to see if the keyboard will cover the scroll view at all. (This conversion allows the scrollview to be anyhwere in the hierarchy).
    CGPoint scrollEndPointInWindowSpace = [[UIApplication sharedApplication].keyWindow convertPoint:CGPointMake(CGRectGetMinX(scrollView.frame), CGRectGetMaxY(scrollView.frame)) fromView:[scrollView superview]];
    
    // Also takes into account the current content inset. (Even if the keyboard covers the frame, it might not matter if the bottom content inset is set to above the keyboard anyway).
    if (scrollEndPointInWindowSpace.y - scrollView.contentInset.bottom > CGRectGetMinY(keyboardRect)) {
        
        // Sets the scrollview insets to account for the portion covered by the keyboard (as apposed to changing the frame size).
        UIEdgeInsets contentInsets = UIEdgeInsetsMake(scrollView.contentInset.top, scrollView.contentInset.left, scrollEndPointInWindowSpace.y - CGRectGetMinY(keyboardRect), scrollView.contentInset.right);
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;
    }
    
    // Looks smoother if animated
    [UIView animateWithDuration:[[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue] delay:0 options:[[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue] animations:^{
        
        // Gets the field view rect in the scroll view coordinate space and scrolls to it. (This conversion allows the field to be anyhwere in the scrollviews subview hierarchy).
        CGRect fieldRectInScrollViewSpace = rectToScrollTo();
        [scrollView scrollRectToVisible:fieldRectInScrollViewSpace animated:YES];
        [viewController updateViewConstraints];
        [viewController.view layoutIfNeeded];
    } completion:NULL];
}

+ (void)setScrollViewInsetForKeyboardHide:(nonnull NSNotification *)notification scrollView:(nonnull UIScrollView *)scrollView viewController:(nonnull UIViewController *)viewController contentInset:(UIEdgeInsets)contentInset {
    
    [UIView animateWithDuration:[[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue] delay:0 options:[[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue] animations:^{
        
        // Go back to how it was before the user touched a text field.
        scrollView.contentInset = contentInset;
        scrollView.scrollIndicatorInsets = contentInset;
        [viewController updateViewConstraints];
        [viewController.view layoutIfNeeded];
    } completion:NULL];
}


@end
