AtChat/Pods/YYKit/YYKit/Text/Component/YYTextEffectWindow.m
//
//  YYTextEffectWindow.m
//  YYKit <https://github.com/ibireme/YYKit>
//
//  Created by ibireme on 15/2/25.
//  Copyright (c) 2015 ibireme.
//
//  This source code is licensed under the MIT-style license found in the
//  LICENSE file in the root directory of this source tree.
//

#import "YYTextEffectWindow.h"
#import "YYTextKeyboardManager.h"
#import "YYKitMacro.h"
#import "YYCGUtilities.h"
#import "UIView+YYAdd.h"
#import "UIApplication+YYAdd.h"


@implementation YYTextEffectWindow

+ (instancetype)sharedWindow {
    static YYTextEffectWindow *one = nil;
    if (one == nil) {
        // iOS 9 compatible
        NSString *mode = [NSRunLoop currentRunLoop].currentMode;
        if (mode.length == 27 &&
            [mode hasPrefix:@"UI"] &&
            [mode hasSuffix:@"InitializationRunLoopMode"]) {
            return nil;
        }
    }
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (![UIApplication isAppExtension]) {
            one = [self new];
            one.frame = (CGRect){.size = kScreenSize};
            one.userInteractionEnabled = NO;
            one.windowLevel = UIWindowLevelStatusBar + 1;
            one.hidden = NO;
            
            // for iOS 9:
            one.opaque = NO;
            one.backgroundColor = [UIColor clearColor];
            one.layer.backgroundColor = [UIColor clearColor].CGColor;
        }
    });
    return one;
}

// stop self from becoming the KeyWindow
- (void)becomeKeyWindow {
    [[[UIApplication sharedExtensionApplication].delegate window] makeKeyWindow];
}

- (UIViewController *)rootViewController {
    for (UIWindow *window in [[UIApplication sharedExtensionApplication] windows]) {
        if (self == window) continue;
        if (window.hidden) continue;
        UIViewController *topViewController = window.rootViewController;
        if (topViewController) return topViewController;
    }
    UIViewController *viewController = [super rootViewController];
    if (!viewController) {
        viewController = [UIViewController new];
        [super setRootViewController:viewController];
    }
    return viewController;
}

// Bring self to front
- (void)_updateWindowLevel {
    UIApplication *app = [UIApplication sharedExtensionApplication];
    if (!app) return;
    
    UIWindow *top = app.windows.lastObject;
    UIWindow *key = app.keyWindow;
    if (key && key.windowLevel > top.windowLevel) top = key;
    if (top == self) return;
    self.windowLevel = top.windowLevel + 1;

}

- (YYTextDirection)_keyboardDirection {
    CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
    keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
    if (CGRectIsNull(keyboardFrame) || CGRectIsEmpty(keyboardFrame)) return YYTextDirectionNone;
    
    if (CGRectGetMinY(keyboardFrame) == 0 &&
        CGRectGetMinX(keyboardFrame) == 0 &&
        CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame))
        return YYTextDirectionTop;
    
    if (CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame) &&
        CGRectGetMinY(keyboardFrame) == 0 &&
        CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame))
        return YYTextDirectionRight;
    
    if (CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame) &&
        CGRectGetMinX(keyboardFrame) == 0 &&
        CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame))
        return YYTextDirectionBottom;
    
    if (CGRectGetMinX(keyboardFrame) == 0 &&
        CGRectGetMinY(keyboardFrame) == 0 &&
        CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame))
        return YYTextDirectionLeft;
    
    return YYTextDirectionNone;
}

- (CGPoint)_correctedCaptureCenter:(CGPoint)center{
    CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
    keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
    if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
        YYTextDirection direction = [self _keyboardDirection];
        switch (direction) {
            case YYTextDirectionTop: {
                if (center.y < CGRectGetMaxY(keyboardFrame)) center.y = CGRectGetMaxY(keyboardFrame);
            } break;
            case YYTextDirectionRight: {
                if (center.x > CGRectGetMinX(keyboardFrame)) center.x = CGRectGetMinX(keyboardFrame);
            } break;
            case YYTextDirectionBottom: {
                if (center.y > CGRectGetMinY(keyboardFrame)) center.y = CGRectGetMinY(keyboardFrame);
            } break;
            case YYTextDirectionLeft: {
                if (center.x < CGRectGetMaxX(keyboardFrame)) center.x = CGRectGetMaxX(keyboardFrame);
            } break;
            default: break;
        }
    }
    return center;
}

- (CGPoint)_correctedCenter:(CGPoint)center forMagnifier:(YYTextMagnifier *)mag rotation:(CGFloat)rotation {
    CGFloat degree = RadiansToDegrees(rotation);
    
    degree /= 45.0;
    if (degree < 0) degree += (int)(-degree/8.0 + 1) * 8;
    if (degree > 8) degree -= (int)(degree/8.0) * 8;
    
    CGFloat caretExt = 10;
    if (degree <= 1 || degree >= 7) { //top
        if (mag.type == YYTextMagnifierTypeCaret) {
            if (center.y < caretExt)
                center.y = caretExt;
        } else if (mag.type == YYTextMagnifierTypeRanged) {
            if (center.y < mag.bounds.size.height)
                center.y = mag.bounds.size.height;
        }
    } else if (1 < degree && degree < 3) { // right
        if (mag.type == YYTextMagnifierTypeCaret) {
            if (center.x > self.bounds.size.width - caretExt)
                center.x = self.bounds.size.width - caretExt;
        } else if (mag.type == YYTextMagnifierTypeRanged) {
            if (center.x > self.bounds.size.width - mag.bounds.size.height)
                center.x = self.bounds.size.width - mag.bounds.size.height;
        }
    } else if (3 <= degree && degree <= 5) { // bottom
        if (mag.type == YYTextMagnifierTypeCaret) {
            if (center.y > self.bounds.size.height - caretExt)
                center.y = self.bounds.size.height - caretExt;
        } else if (mag.type == YYTextMagnifierTypeRanged) {
            if (center.y > mag.bounds.size.height)
                center.y = mag.bounds.size.height;
        }
    } else if (5 < degree && degree < 7) { // left
        if (mag.type == YYTextMagnifierTypeCaret) {
            if (center.x < caretExt)
                center.x = caretExt;
        } else if (mag.type == YYTextMagnifierTypeRanged) {
            if (center.x < mag.bounds.size.height)
                center.x = mag.bounds.size.height;
        }
    }

    
    CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
    keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
    if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
        YYTextDirection direction = [self _keyboardDirection];
        switch (direction) {
            case YYTextDirectionTop: {
                if (mag.type == YYTextMagnifierTypeCaret) {
                    if (center.y - mag.bounds.size.height / 2 < CGRectGetMaxY(keyboardFrame))
                        center.y = CGRectGetMaxY(keyboardFrame) + mag.bounds.size.height / 2;
                } else if (mag.type == YYTextMagnifierTypeRanged) {
                    if (center.y < CGRectGetMaxY(keyboardFrame)) center.y = CGRectGetMaxY(keyboardFrame);
                }
            } break;
            case YYTextDirectionRight: {
                if (mag.type == YYTextMagnifierTypeCaret) {
                    if (center.x + mag.bounds.size.height / 2 > CGRectGetMinX(keyboardFrame))
                        center.x = CGRectGetMinX(keyboardFrame) - mag.bounds.size.width / 2;
                } else if (mag.type == YYTextMagnifierTypeRanged) {
                    if (center.x > CGRectGetMinX(keyboardFrame)) center.x = CGRectGetMinX(keyboardFrame);
                }
            } break;
            case YYTextDirectionBottom: {
                if (mag.type == YYTextMagnifierTypeCaret) {
                    if (center.y + mag.bounds.size.height / 2 > CGRectGetMinY(keyboardFrame))
                        center.y = CGRectGetMinY(keyboardFrame) - mag.bounds.size.height / 2;
                } else if (mag.type == YYTextMagnifierTypeRanged) {
                    if (center.y > CGRectGetMinY(keyboardFrame)) center.y = CGRectGetMinY(keyboardFrame);
                }
            } break;
            case YYTextDirectionLeft: {
                if (mag.type == YYTextMagnifierTypeCaret) {
                    if (center.x - mag.bounds.size.height / 2 < CGRectGetMaxX(keyboardFrame))
                        center.x = CGRectGetMaxX(keyboardFrame) + mag.bounds.size.width / 2;
                } else if (mag.type == YYTextMagnifierTypeRanged) {
                    if (center.x < CGRectGetMaxX(keyboardFrame)) center.x = CGRectGetMaxX(keyboardFrame);
                }
            } break;
            default: break;
        }
    }
    
    return center;
}

/**
 Capture screen snapshot and set it to magnifier.
 @return Magnifier rotation radius.
 */
- (CGFloat)_updateMagnifier:(YYTextMagnifier *)mag {
    UIApplication *app = [UIApplication sharedExtensionApplication];
    if (!app) return 0;
    
    UIView *hostView = mag.hostView;
    UIWindow *hostWindow = [hostView isKindOfClass:[UIWindow class]] ? (id)hostView : hostView.window;
    if (!hostView || !hostWindow) return 0;
    CGPoint captureCenter = [self convertPoint:mag.hostCaptureCenter fromViewOrWindow:hostView];
    captureCenter = [self _correctedCaptureCenter:captureCenter];
    CGRect captureRect = {.size = mag.snapshotSize};
    captureRect.origin.x = captureCenter.x - captureRect.size.width / 2;
    captureRect.origin.y = captureCenter.y - captureRect.size.height / 2;
    
    CGAffineTransform trans = YYCGAffineTransformGetFromViews(hostView, self);
    CGFloat rotation = CGAffineTransformGetRotation(trans);
    
    if (mag.captureDisabled) {
        if (!mag.snapshot || mag.snapshot.size.width > 1) {
            static UIImage *placeholder;
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                CGRect rect = CGRectMake(0, 0, mag.width, mag.height);
                UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
                CGContextRef context = UIGraphicsGetCurrentContext();
                [[UIColor colorWithWhite:1 alpha:0.8] set];
                CGContextFillRect(context, rect);
                placeholder = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
            });
            mag.captureFadeAnimation = YES;
            mag.snapshot = placeholder;
            mag.captureFadeAnimation = NO;
        }
        return rotation;
    }
    
    UIGraphicsBeginImageContextWithOptions(captureRect.size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (!context) return rotation;
    
    CGPoint tp = CGPointMake(captureRect.size.width / 2, captureRect.size.height / 2);
    tp = CGPointApplyAffineTransform(tp, CGAffineTransformMakeRotation(rotation));
    CGContextRotateCTM(context, -rotation);
    CGContextTranslateCTM(context, tp.x - captureCenter.x, tp.y - captureCenter.y);
    
    NSMutableArray *windows = app.windows.mutableCopy;
    UIWindow *keyWindow = app.keyWindow;
    if (![windows containsObject:keyWindow]) [windows addObject:keyWindow];
    [windows sortUsingComparator:^NSComparisonResult(UIWindow *w1, UIWindow *w2) {
        if (w1.windowLevel < w2.windowLevel) return NSOrderedAscending;
        else if (w1.windowLevel > w2.windowLevel) return NSOrderedDescending;
        return NSOrderedSame;
    }];
    UIScreen *mainScreen = [UIScreen mainScreen];
    for (UIWindow *window in windows) {
        if (window.hidden || window.alpha <= 0.01) continue;
        if (window.screen != mainScreen) continue;
        if ([window isKindOfClass:self.class]) break; //don't capture window above self
        CGContextSaveGState(context);
        CGContextConcatCTM(context, YYCGAffineTransformGetFromViews(window, self));
        [window.layer renderInContext:context]; //render
        //[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO]; //slower when capture whole window
        CGContextRestoreGState(context);
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    if (mag.snapshot.size.width == 1) {
        mag.captureFadeAnimation = YES;
    }
    mag.snapshot = image;
    mag.captureFadeAnimation = NO;
    return rotation;
}

- (void)showMagnifier:(YYTextMagnifier *)mag {
    if (!mag) return;
    if (mag.superview != self) [self addSubview:mag];
    [self _updateWindowLevel];
    CGFloat rotation = [self _updateMagnifier:mag];
    CGPoint center = [self convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
    CGAffineTransform trans = CGAffineTransformMakeRotation(rotation);
    trans = CGAffineTransformScale(trans, 0.3, 0.3);
    mag.transform = trans;
    mag.center = center;
    if (mag.type == YYTextMagnifierTypeRanged) {
        mag.alpha = 0;
    }
    NSTimeInterval time = mag.type == YYTextMagnifierTypeCaret ? 0.08 : 0.1;
    [UIView animateWithDuration:time delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
        if (mag.type == YYTextMagnifierTypeCaret) {
            CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
            newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
            newCenter.x += center.x;
            newCenter.y += center.y;
            mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
        } else {
            mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
        }
        mag.transform = CGAffineTransformMakeRotation(rotation);
        mag.alpha = 1;
    } completion:^(BOOL finished) {
        
    }];
}

- (void)moveMagnifier:(YYTextMagnifier *)mag {
    if (!mag) return;
    [self _updateWindowLevel];
    CGFloat rotation = [self _updateMagnifier:mag];
    CGPoint center = [self convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
    if (mag.type == YYTextMagnifierTypeCaret) {
        CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
        newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
        newCenter.x += center.x;
        newCenter.y += center.y;
        mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
    } else {
        mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
    }
    mag.transform = CGAffineTransformMakeRotation(rotation);
}

- (void)hideMagnifier:(YYTextMagnifier *)mag {
    if (!mag) return;
    if (mag.superview != self) return;
    CGFloat rotation = [self _updateMagnifier:mag];
    CGPoint center = [self convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
    NSTimeInterval time = mag.type == YYTextMagnifierTypeCaret ? 0.20 : 0.15;
    [UIView animateWithDuration:time delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
        
        CGAffineTransform trans = CGAffineTransformMakeRotation(rotation);
        trans = CGAffineTransformScale(trans, 0.01, 0.01);
        mag.transform = trans;
        
        if (mag.type == YYTextMagnifierTypeCaret) {
            CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
            newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
            newCenter.x += center.x;
            newCenter.y += center.y;
            mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
        } else {
            mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
            mag.alpha = 0;
        }
        
    } completion:^(BOOL finished) {
        if (finished) {
            [mag removeFromSuperview];
            mag.transform = CGAffineTransformIdentity;
            mag.alpha = 1;
        }
    }];
}

- (void)_updateSelectionGrabberDot:(YYSelectionGrabberDot *)dot selection:(YYTextSelectionView *)selection{
    dot.mirror.hidden = YES;
    if (selection.hostView.clipsToBounds == YES && dot.visibleAlpha > 0.1) {
        CGRect dotRect = [dot convertRect:dot.bounds toViewOrWindow:self];
        BOOL dotInKeyboard = NO;
        
        CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
        keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
        if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
            CGRect inter = CGRectIntersection(dotRect, keyboardFrame);
            if (!CGRectIsNull(inter) && (inter.size.width > 1 || inter.size.height > 1)) {
                dotInKeyboard = YES;
            }
        }
        if (!dotInKeyboard) {
            CGRect hostRect = [selection.hostView convertRect:selection.hostView.bounds toView:self];
            CGRect intersection = CGRectIntersection(dotRect, hostRect);
            if (CGRectGetArea(intersection) < CGRectGetArea(dotRect)) {
                CGFloat dist = CGPointGetDistanceToRect(CGRectGetCenter(dotRect), hostRect);
                if (dist < CGRectGetWidth(dot.frame) * 0.55) {
                    dot.mirror.hidden = NO;
                }
            }
        }
    }
    CGPoint center = [dot convertPoint:CGPointMake(CGRectGetWidth(dot.frame) / 2, CGRectGetHeight(dot.frame) / 2) toViewOrWindow:self];
    if (isnan(center.x) || isnan(center.y) || isinf(center.x) || isinf(center.y)) {
        dot.mirror.hidden = YES;
    } else {
        dot.mirror.center = center;
    }
}

- (void)showSelectionDot:(YYTextSelectionView *)selection {
    if (!selection) return;
    [self _updateWindowLevel];
    [self insertSubview:selection.startGrabber.dot.mirror atIndex:0];
    [self insertSubview:selection.endGrabber.dot.mirror atIndex:0];
    [self _updateSelectionGrabberDot:selection.startGrabber.dot selection:selection];
    [self _updateSelectionGrabberDot:selection.endGrabber.dot selection:selection];
}

- (void)hideSelectionDot:(YYTextSelectionView *)selection {
    if (!selection) return;
    [selection.startGrabber.dot.mirror removeFromSuperview];
    [selection.endGrabber.dot.mirror removeFromSuperview];
}

@end
