import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit

public protocol StandalonePresentableController: ViewController {
}

private func findCurrentResponder(_ view: UIView) -> UIResponder? {
    if view.isFirstResponder {
        return view
    } else {
        for subview in view.subviews {
            if let result = findCurrentResponder(subview) {
                return result
            }
        }
        return nil
    }
}

func findWindow(_ view: UIView) -> WindowHost? {
    if let view = view as? WindowHost {
        return view
    } else if let superview = view.superview {
        return findWindow(superview)
    } else {
        return nil
    }
}

public enum ViewControllerPresentationAnimation {
    case none
    case modalSheet
}

public struct ViewControllerSupportedOrientations: Equatable {
    public var regularSize: UIInterfaceOrientationMask
    public var compactSize: UIInterfaceOrientationMask
    
    public init(regularSize: UIInterfaceOrientationMask, compactSize: UIInterfaceOrientationMask) {
        self.regularSize = regularSize
        self.compactSize = compactSize
    }
    
    public func intersection(_ other: ViewControllerSupportedOrientations) -> ViewControllerSupportedOrientations {
        return ViewControllerSupportedOrientations(regularSize: self.regularSize.intersection(other.regularSize), compactSize: self.compactSize.intersection(other.compactSize))
    }
}

open class ViewControllerPresentationArguments {
    public let presentationAnimation: ViewControllerPresentationAnimation
    public let completion: (() -> Void)?
    
    public init(presentationAnimation: ViewControllerPresentationAnimation, completion: (() -> Void)? = nil) {
        self.presentationAnimation = presentationAnimation
        self.completion = completion
    }
}

public enum ViewControllerNavigationPresentation {
    case `default`
    case master
    case modal
    case flatModal
    case standaloneModal
    case standaloneFlatModal
    case modalInLargeLayout
    case modalInCompactLayout
}

public enum TabBarItemContextActionType {
    case none
    case always
    case whenActive
}

public protocol CustomViewControllerNavigationData: AnyObject {
    func combine(summary: CustomViewControllerNavigationDataSummary?) -> CustomViewControllerNavigationDataSummary?
}

public protocol CustomViewControllerNavigationDataSummary: AnyObject {
}

@objc open class ViewController: UIViewController, ContainableController {
    public struct NavigationLayout {
        public var navigationFrame: CGRect
        public var defaultContentHeight: CGFloat

        public init(navigationFrame: CGRect, defaultContentHeight: CGFloat) {
            self.navigationFrame = navigationFrame
            self.defaultContentHeight = defaultContentHeight
        }
    }

    private var validLayout: ContainerViewLayout?
    public var currentlyAppliedLayout: ContainerViewLayout? {
        return self.validLayout
    }
    
    public let presentationContext: PresentationContext
    
    public final var supportedOrientations: ViewControllerSupportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) {
        didSet {
            if self.supportedOrientations != oldValue {
                if self.isNodeLoaded {
                    self.window?.invalidateSupportedOrientations()
                }
            }
        }
    }
    public final var lockedOrientation: UIInterfaceOrientationMask?
    public final var lockOrientation: Bool = false {
        didSet {
            if self.lockOrientation != oldValue {
                if !self.lockOrientation {
                    self.lockedOrientation = nil
                }
                if let window = self.window {
                    window.invalidateSupportedOrientations()
                }
            }
        }
    }
    
    var blocksInteractionUntilReady: Bool = false
    
    public final var isOpaqueWhenInOverlay: Bool = false
    public final var blocksBackgroundWhenInOverlay: Bool = false
    public final var acceptsFocusWhenInOverlay: Bool = false
    public final var automaticallyControlPresentationContextLayout: Bool = true
    public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
    
    public func requestUpdateParameters() {
        self.modalStyleOverlayTransitionFactorUpdated?(.immediate)
    }
    
    public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
        return self.supportedOrientations
    }
    
    public final var deferScreenEdgeGestures: UIRectEdge = [] {
        didSet {
            if self.deferScreenEdgeGestures != oldValue {
                self.window?.invalidateDeferScreenEdgeGestures()
            }
        }
    }
    
    public final var prefersOnScreenNavigationHidden: Bool = false {
        didSet {
            if self.prefersOnScreenNavigationHidden != oldValue {
                self.window?.invalidatePrefersOnScreenNavigationHidden()
            }
        }
    }
    
    override open var prefersHomeIndicatorAutoHidden: Bool {
        return self.prefersOnScreenNavigationHidden
    }
    
    open var previousItem: NavigationPreviousAction?
    
    open var navigationPresentation: ViewControllerNavigationPresentation = .default
    open var _presentedInModal: Bool = false
    
    public var presentedOverCoveringView: Bool = false
    
    public var presentationArguments: Any?
    
    public var tabBarItemDebugTapAction: (() -> Void)?
    
    public private(set) var modalStyleOverlayTransitionFactor: CGFloat = 0.0
    public var modalStyleOverlayTransitionFactorUpdated: ((ContainedViewLayoutTransition) -> Void)?
    public var customModalStyleOverlayTransitionFactorUpdated: ((ContainedViewLayoutTransition) -> Void)?
    public func updateModalStyleOverlayTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
        if self.modalStyleOverlayTransitionFactor != value {
            self.modalStyleOverlayTransitionFactor = value
            self.modalStyleOverlayTransitionFactorUpdated?(transition)
            self.customModalStyleOverlayTransitionFactorUpdated?(transition)
        }
    }
    
    private var _displayNode: ASDisplayNode?
    public final var displayNode: ASDisplayNode {
        get {
            if let value = self._displayNode {
                return value
            }
            else {
                self.loadDisplayNode()
                if self._displayNode == nil {
                    fatalError("displayNode should be initialized after loadDisplayNode()")
                }
                return self._displayNode!
            }
        }
        set(value) {
            self._displayNode = value
        }
    }
    
    public final var isNodeLoaded: Bool {
        return self._displayNode != nil
    }
    
    public let statusBar: StatusBar
    public let navigationBar: NavigationBar?
    open var transitionNavigationBar: NavigationBar? {
        return self.navigationBar
    }
    public private(set) var toolbar: Toolbar?
    
    public var displayNavigationBar = true
    open var navigationBarRequiresEntireLayoutUpdate: Bool {
        return true
    }
    
    private weak var activeInputViewCandidate: UIResponder?
    private weak var activeInputView: UIResponder?
    
    open var hasActiveInput: Bool = false
    
    open var overlayWantsToBeBelowKeyboard: Bool {
        return false
    }
    
    var internalOverlayWantsToBeBelowKeyboardUpdated: ((ContainedViewLayoutTransition) -> Void)?
    public func overlayWantsToBeBelowKeyboardUpdated(transition: ContainedViewLayoutTransition) {
        self.internalOverlayWantsToBeBelowKeyboardUpdated?(transition)
    }
    
    private var navigationBarOrigin: CGFloat = 0.0
        
    open var interactiveNavivationGestureEdgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth? {
        return nil
    }

    open func navigationLayout(layout: ContainerViewLayout) -> NavigationLayout {
        let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
        var defaultNavigationBarHeight: CGFloat
        if self._presentedInModal && layout.orientation == .portrait {
            defaultNavigationBarHeight = 56.0
        } else {
            defaultNavigationBarHeight = 44.0
        }
        let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight(defaultHeight: defaultNavigationBarHeight) ?? defaultNavigationBarHeight)

        var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: navigationBarHeight))

        navigationBarFrame.size.height += self.additionalNavigationBarHeight

        if !self.displayNavigationBar {
            navigationBarFrame.origin.y = -navigationBarFrame.size.height
        }

        self.navigationBarOrigin = navigationBarFrame.origin.y

        return NavigationLayout(navigationFrame: navigationBarFrame, defaultContentHeight: defaultNavigationBarHeight)
    }
    
    open var cleanNavigationHeight: CGFloat {
        if let navigationBar = self.navigationBar {
            var height = navigationBar.frame.maxY
            if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
                height += contentNode.nominalHeight - contentNode.height
            }
            return height
        } else {
            return 0.0
        }
    }

    open var additionalNavigationBarHeight: CGFloat {
        return 0.0
    }
    
    public var additionalSideInsets: UIEdgeInsets = UIEdgeInsets()
    
    private let _ready = Promise<Bool>(true)
    open var ready: Promise<Bool> {
        return self._ready
    }
    
    private var scrollToTopView: ScrollToTopView?
    public var scrollToTop: (() -> Void)? {
        didSet {
            if self.isViewLoaded {
                self.updateScrollToTopView()
            }
        }
    }
    public var scrollToTopWithTabBar: (() -> Void)?
    public var longTapWithTabBar: (() -> Void)?
    
    public var customPresentPreviewingController: ((ViewController, ASDisplayNode) -> ViewController?)?
    
    open func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
        
    }
    
    open var customData: Any? {
        get {
            return nil
        }
    }
    
    open var customNavigationData: CustomViewControllerNavigationData? {
        get {
            return nil
        }
    }
    open var customNavigationDataSummary: CustomViewControllerNavigationDataSummary?
    
    public internal(set) var isInFocus: Bool = false {
        didSet {
            if self.isInFocus != oldValue {
                self.inFocusUpdated(isInFocus: self.isInFocus)
            }
        }
    }
    open func inFocusUpdated(isInFocus: Bool) {
    }

    public var attemptNavigation: (@escaping () -> Void) -> Bool = { _ in
        return true
    }
    
    open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
        return nil
    }
    
    open func didAppearInContextPreview() {
    }
    
    private func updateScrollToTopView() {
        /*if self.scrollToTop != nil {
            if let displayNode = self._displayNode , self.scrollToTopView == nil {
                let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.bounds.size.width, height: 1.0))
                scrollToTopView.action = { [weak self] in
                    if let scrollToTop = self?.scrollToTop {
                        scrollToTop()
                    }
                }
                self.scrollToTopView = scrollToTopView
                self.view.addSubview(scrollToTopView)
            }
        } else*/ if let scrollToTopView = self.scrollToTopView {
            scrollToTopView.removeFromSuperview()
            self.scrollToTopView = nil
        }
    }
    
    public var titleSignal: Signal<String?, NoError> {
        return Signal { [weak self] subscriber in
            guard let self else {
                return EmptyDisposable
            }
            subscriber.putNext(self.navigationItem.title)
            let listenerIndex = self.navigationItem.addSetTitleListener { title, _ in
                subscriber.putNext(title)
            }
            return ActionDisposable { [weak self] in
                if let self {
                    self.navigationItem.removeSetTitleListener(listenerIndex)
                }
            }
        }
    }
    
    public init(navigationBarPresentationData: NavigationBarPresentationData?) {
        self.statusBar = StatusBar()
        if let navigationBarPresentationData = navigationBarPresentationData {
            self.navigationBar = NavigationBar(presentationData: navigationBarPresentationData)
        } else {
            self.navigationBar = nil
        }
        self.presentationContext = PresentationContext()
        
        super.init(nibName: nil, bundle: nil)
        
        self.navigationBar?.backPressed = { [weak self] in
            if let strongSelf = self, strongSelf.attemptNavigation({
                guard let strongSelf = self else {
                    return
                }
                if let navigationController = strongSelf.navigationController as? NavigationController {
                    navigationController.filterController(strongSelf, animated: true)
                } else {
                    strongSelf.navigationController?.popViewController(animated: true)
                }
            }) {
                if let navigationController = strongSelf.navigationController as? NavigationController {
                    navigationController.filterController(strongSelf, animated: true)
                } else {
                    strongSelf.navigationController?.popViewController(animated: true)
                }
            }
        }
        self.navigationBar?.requestContainerLayout = { [weak self] transition in
            if let strongSelf = self, strongSelf.isNodeLoaded, let validLayout = strongSelf.validLayout {
                if strongSelf.navigationBarRequiresEntireLayoutUpdate {
                    strongSelf.containerLayoutUpdated(validLayout, transition: transition)
                } else {
                    strongSelf.updateNavigationBarLayout(validLayout, transition: transition)
                }
            }
        }
        self.navigationBar?.item = self.navigationItem
        //self.automaticallyAdjustsScrollViewInsets = false
        
        self.scrollToTopWithTabBar = { [weak self] in
            self?.scrollToTop?()
        }
    }
    
    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        
    }

    open func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: 0.0, transition: transition)
    }
    
    public func applyNavigationBarLayout(_ layout: ContainerViewLayout, navigationLayout: NavigationLayout, additionalBackgroundHeight: CGFloat, transition: ContainedViewLayoutTransition) {
        let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0

        var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: navigationLayout.navigationFrame.maxY))
        
        if !self.displayNavigationBar {
            navigationBarFrame.origin.y = -navigationBarFrame.size.height
        }
        
        self.navigationBarOrigin = navigationBarFrame.origin.y

        var isLandscape = layout.size.width > layout.size.height
        if case .regular = layout.metrics.widthClass {
            isLandscape = false
        }
        if let navigationBar = self.navigationBar {
            if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar {
                navigationBarFrame.origin.y -= navigationLayout.defaultContentHeight
                navigationBarFrame.size.height += contentNode.height + navigationLayout.defaultContentHeight + statusBarHeight
            }
            if let _ = navigationBar.contentNode, let _ = navigationBar.secondaryContentNode, !self.displayNavigationBar {
                navigationBarFrame.size.height += navigationBar.secondaryContentHeight
            }
            
            navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition)
            if !transition.isAnimated {
                navigationBar.layer.removeAnimation(forKey: "bounds")
                navigationBar.layer.removeAnimation(forKey: "position")
            }
            transition.updateFrame(node: navigationBar, frame: navigationBarFrame)
            navigationBar.setHidden(!self.displayNavigationBar, animated: transition.isAnimated)
        }
    }
    
    open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.validLayout = layout
        
        if !self.isViewLoaded {
            self.loadView()
        }
        if let _ = layout.statusBarHeight {
            self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0))
        }
        
        self.updateNavigationBarLayout(layout, transition: transition)
        
        if self.automaticallyControlPresentationContextLayout {
            self.presentationContext.containerLayoutUpdated(layout, transition: transition)
        }
        
        if let scrollToTopView = self.scrollToTopView {
            scrollToTopView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 10.0)
        }
    }
    
    open func navigationStackConfigurationUpdated(next: [ViewController]) {
    }
    
    open override func loadView() {
        self.view = self.displayNode.view
        if let navigationBar = self.navigationBar {
            if navigationBar.supernode == nil {
                self.displayNode.addSubnode(navigationBar)
            }
        }
        self.view.autoresizingMask = []
        self.view.addSubview(self.statusBar.view)
        self.presentationContext.view = self.view
    }
    
    open func loadDisplayNode() {
        self.displayNode = ASDisplayNode()
        self.displayNodeDidLoad()
    }
    
    open func displayNodeDidLoad() {
        self.updateScrollToTopView()
        if let backgroundColor = self.displayNode.backgroundColor, backgroundColor.alpha.isEqual(to: 1.0) {
            self.blocksBackgroundWhenInOverlay = true
            self.isOpaqueWhenInOverlay = true
        }
    }
    
    public func requestLayout(transition: ContainedViewLayoutTransition) {
        if self.isViewLoaded, let validLayout = self.validLayout {
            self.containerLayoutUpdated(validLayout, transition: transition)
        }
    }
    
    open func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
        
    }
    
    public func setDisplayNavigationBar(_ displayNavigationBar: Bool, transition: ContainedViewLayoutTransition = .immediate) {
        if displayNavigationBar != self.displayNavigationBar {
            self.displayNavigationBar = displayNavigationBar
            if let parent = self.parent as? TabBarController {
                if parent.currentController === self {
                    parent.displayNavigationBar = displayNavigationBar
                    parent.requestLayout(transition: transition)
                }
            } else {
                self.requestLayout(transition: transition)
            }
        }
    }
    
    public func setNavigationBarPresentationData(_ presentationData: NavigationBarPresentationData, animated: Bool) {
        if animated, let navigationBar = self.navigationBar {
            UIView.transition(with: navigationBar.view, duration: 0.3, options: [.transitionCrossDissolve], animations: {
            }, completion: nil)
        }
        self.navigationBar?.updatePresentationData(presentationData)
        if let parent = self.parent as? TabBarController {
            if parent.currentController === self {
                if animated, let navigationBar = parent.navigationBar {
                    UIView.transition(with: navigationBar.view, duration: 0.3, options: [.transitionCrossDissolve], animations: {
                    }, completion: nil)
                }
                parent.navigationBar?.updatePresentationData(presentationData)
            }
        }
    }
    
    public func setStatusBarStyle(_ style: StatusBarStyle, animated: Bool) {
        self.statusBar.updateStatusBarStyle(style, animated: animated)
        if let parent = self.parent as? TabBarController {
            if parent.currentController === self {
                parent.statusBar.updateStatusBarStyle(style, animated: animated)
            }
        }
    }
    
    override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.view.window?.rootViewController?.present(viewControllerToPresent, animated: flag, completion: completion)
    }
    
    override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        if let navigationController = self.navigationController as? NavigationController {
            var animated = flag
            if case .standaloneModal = self.navigationPresentation {
                animated = false
            }
            navigationController.filterController(self, animated: animated)
        } else {
            self.presentingViewController?.dismiss(animated: flag, completion: nil)
        }
    }
    
    public final var window: WindowHost? {
        if let window = self.view.window as? WindowHost {
            return window
        } else if let result = findWindow(self.view) {
            return result
        } else {
            if let parent = self.parent as? ViewController {
                return parent.window
            }
            return nil
        }
    }
    
    public func push(_ controller: ViewController) {
        (self.navigationController as? NavigationController)?.pushViewController(controller)
    }
    
    open func replace(with controller: ViewController) {
        if let navigationController = self.navigationController as? NavigationController {
            var controllers = navigationController.viewControllers
            controllers.removeAll(where: { $0 === self })
            controllers.append(controller)
            navigationController.setViewControllers(controllers, animated: true)
        }
    }
    
    open func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) {
        if !(controller is StandalonePresentableController), case .window = context, let arguments = arguments as? ViewControllerPresentationArguments, case .modalSheet = arguments.presentationAnimation, self.navigationController != nil {
            controller.navigationPresentation = .modal
            self.push(controller)
        } else {
            controller.presentationArguments = arguments
            switch context {
            case .current:
                self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0), completion: completion)
            case let .window(level):
                self.window?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
            }
        }
    }
    
    public func forEachController(_ f: (ContainableController) -> Bool) {
        for (controller, _) in self.presentationContext.controllers {
            if !f(controller) {
                break
            }
        }
    }
    
    public func presentInGlobalOverlay(_ controller: ViewController, with arguments: Any? = nil) {
        controller.presentationArguments = arguments
        self.window?.presentInGlobalOverlay(controller)
    }
    
    public func addGlobalPortalHostView(sourceView: PortalSourceView) {
        self.window?.addGlobalPortalHostView(sourceView: sourceView)
    }
    
    open override func viewWillDisappear(_ animated: Bool) {
        self.activeInputViewCandidate = findCurrentResponder(self.view)
        
        super.viewWillDisappear(animated)
    }
    
    open override func viewDidDisappear(_ animated: Bool) {
        self.activeInputView = self.activeInputViewCandidate
        
        super.viewDidDisappear(animated)
    }
    
    open func viewWillLeaveNavigation() {
    }
    
    open override func viewDidAppear(_ animated: Bool) {
        self.activeInputView = nil
        
        super.viewDidAppear(animated)
    }
    
    open func dismiss(completion: (() -> Void)? = nil) {
        if let navigationController = self.navigationController as? NavigationController {
            navigationController.filterController(self, animated: true)
        } else {
            self.presentingViewController?.dismiss(animated: true, completion: nil)
        }
    }
    
    public final func navigationNextSibling() -> UIViewController? {
        if let navigationController = self.navigationController as? NavigationController {
            if let index = navigationController.viewControllers.firstIndex(where: { $0 === self }) {
                if index != navigationController.viewControllers.count - 1 {
                    return navigationController.viewControllers[index + 1]
                }
            }
        }
        return nil
    }
    
    public func traceVisibility() -> Bool {
        if !self.isViewLoaded {
            return false
        }
        return traceViewVisibility(view: self.view, rect: self.view.bounds)
    }
    
    open func setToolbar(_ toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
        if self.toolbar != toolbar {
            self.toolbar = toolbar
            if let parent = self.parent as? TabBarController {
                if parent.currentController === self {
                    parent.requestLayout(transition: transition)
                }
            }
        }
    }
    
    open func toolbarActionSelected(action: ToolbarActionOption) {
    }
    
    open var tabBarItemContextActionType: TabBarItemContextActionType = .none
    
    open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
    }
    
    open func tabBarDisabledAction() {
    }
    
    open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
    }
    
    open func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) {
    }
    
    open func acceptPossibleControllerDropContent(content: NavigationControllerDropContent) -> Bool {
        return false
    }
}

func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool {
    if layer.bounds.contains(rect) {
        if layer.isHidden {
            return false
        }
        if layer.opacity < 0.01 {
            return false
        }
        if let backgroundColor = layer.backgroundColor {
            var alpha: CGFloat = 0.0
            UIColor(cgColor: backgroundColor).getRed(nil, green: nil, blue: nil, alpha: &alpha)
            if alpha > 0.95 {
                return true
            }
        }
        if let sublayers = layer.sublayers {
            for sublayer in sublayers {
                let sublayerRect = layer.convert(rect, to: sublayer)
                if traceIsOpaque(layer: sublayer, rect: sublayerRect) {
                    return true
                }
            }
        }
        return false
    } else {
        return false
    }
}

private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool {
    if view.isHidden {
        return false
    }
    if view is UIWindow {
        return true
    } else if let superview = view.superview, let siblings = superview.layer.sublayers {
        if view.window == nil {
            return false
        }
        if let index = siblings.firstIndex(where: { $0 === view.layer }) {
            let viewFrame = view.convert(rect, to: superview)
            for i in (index + 1) ..< siblings.count {
                if siblings[i].frame.contains(viewFrame) {
                    let siblingSubframe = view.layer.convert(viewFrame, to: siblings[i])
                    if traceIsOpaque(layer: siblings[i], rect: siblingSubframe) {
                        return false
                    }
                }
            }
            return traceViewVisibility(view: superview, rect: viewFrame)
        } else {
            return false
        }
    } else {
        return false
    }
}