// Copyright © 2020 Metabolist. All rights reserved.

import UIKit

protocol ZoomAnimatorDelegate: class {
    func transitionWillStartWith(zoomAnimator: ZoomAnimator)
    func transitionDidEndWith(zoomAnimator: ZoomAnimator)
    func referenceView(for zoomAnimator: ZoomAnimator) -> UIView?
    func referenceViewFrameInTransitioningView(for zoomAnimator: ZoomAnimator) -> CGRect?
}

final class ZoomAnimator: NSObject {
    weak var fromDelegate: ZoomAnimatorDelegate?
    weak var toDelegate: ZoomAnimatorDelegate?

    var transitionView: UIView?
    var isPresenting = true
}

extension ZoomAnimator: UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        isPresenting ? .defaultAnimationDuration : .shortAnimationDuration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        fromDelegate?.transitionWillStartWith(zoomAnimator: self)
        toDelegate?.transitionWillStartWith(zoomAnimator: self)

        if isPresenting {
            animateZoomInTransition(using: transitionContext)
        } else {
            animateZoomOutTransition(using: transitionContext)
        }
    }
}

private extension ZoomAnimator {
    private func animateZoomInTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard
            let toVC = transitionContext.viewController(forKey: .to),
            let fromVC = transitionContext.viewController(forKey: .from),
            let fromReferenceView = fromDelegate?.referenceView(for: self),
            let toReferenceView = toDelegate?.referenceView(for: self),
            let fromReferenceViewFrame = fromDelegate?.referenceViewFrameInTransitioningView(for: self)
            else { return }

        toVC.view.alpha = 0
        toReferenceView.isHidden = true
        transitionContext.containerView.addSubview(toVC.view)

        if transitionView == nil, let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() {
            transitionView.frame = fromReferenceViewFrame
            transitionView.layer.contentsRect = fromReferenceView.layer.contentsRect
            transitionView.layer.cornerRadius = fromReferenceView.layer.cornerRadius
            self.transitionView = transitionView
            transitionContext.containerView.addSubview(transitionView)
        }

        fromReferenceView.isHidden = true

        let referenceViewFrame = (fromReferenceView as? ZoomAnimatableView)?.frame(inView: toVC.view) ?? .zero
        let finalTransitionFrame = referenceViewFrame.containsNaN ? .zero : referenceViewFrame

        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.8,
            initialSpringVelocity: 0,
            options: [.transitionCrossDissolve]) {
            self.transitionView?.frame = finalTransitionFrame
            self.transitionView?.layer.contentsRect = .defaultContentsRect
            self.transitionView?.layer.cornerRadius = 0
            toVC.view.alpha = 1.0
            fromVC.tabBarController?.tabBar.alpha = 0
        } completion: { _ in
            self.transitionView?.removeFromSuperview()
            toReferenceView.isHidden = false
            fromReferenceView.isHidden = false

            self.transitionView = nil

            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            self.toDelegate?.transitionDidEndWith(zoomAnimator: self)
            self.fromDelegate?.transitionDidEndWith(zoomAnimator: self)
        }
    }

    private func animateZoomOutTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView

        guard
            let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
            let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let fromReferenceView = fromDelegate?.referenceView(for: self),
            let fromReferenceViewFrame = fromDelegate?.referenceViewFrameInTransitioningView(for: self)
            else { return }

        let toReferenceView = toDelegate?.referenceView(for: self)
        let toReferenceViewFrame = toDelegate?.referenceViewFrameInTransitioningView(for: self)

        toReferenceView?.isHidden = true

        if transitionView == nil, let transitionView = (fromReferenceView as? ZoomAnimatableView)?.transitionView() {
            transitionView.frame = fromReferenceViewFrame
            self.transitionView = transitionView
            containerView.addSubview(transitionView)
        }

        containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
        fromReferenceView.isHidden = true

        UIView.animate(
            withDuration: transitionDuration(using: transitionContext)) {
            fromVC.view.alpha = 0

            if let toReferenceViewFrame = toReferenceViewFrame {
                self.transitionView?.frame = toReferenceViewFrame
            } else {
                self.transitionView?.alpha = 0
            }

            self.transitionView?.layer.contentsRect = toReferenceView?.layer.contentsRect ?? .defaultContentsRect
            self.transitionView?.layer.cornerRadius = toReferenceView?.layer.cornerRadius ?? 0

            toVC.tabBarController?.tabBar.alpha = 1
        } completion: { _ in
            self.transitionView?.removeFromSuperview()
            toReferenceView?.isHidden = false
            fromReferenceView.isHidden = false

            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            self.toDelegate?.transitionDidEndWith(zoomAnimator: self)
            self.fromDelegate?.transitionDidEndWith(zoomAnimator: self)
        }
    }
}