//
//  InteractivePushTransition.swift
//  Tusker
//
//  Created by Shadowfacts on 2/19/20.
//  Copyright © 2020 Shadowfacts. All rights reserved.
//

import UIKit

/// Allows interactively moving forward through the navigation stack after popping
/// Based on https://github.com/NSExceptional/TBInteractivePushTransition
class InteractivePushTransition: UIPercentDrivenInteractiveTransition {
    
    fileprivate let minimumPushVelocityThreshold: CGFloat = 700
    fileprivate let minimumPushDistanceThreshold: CGFloat = 0.5
    fileprivate let pushAnimationDuration: TimeInterval = 0.35
    
    private(set) weak var navigationController: EnhancedNavigationViewController!

    private(set) weak var interactivePushGestureRecognizer: UIScreenEdgePanGestureRecognizer!
    
    private(set) var interactive = false
    private(set) weak var pushingViewController: UIViewController?
    
    init(navigationController: EnhancedNavigationViewController) {
        super.init()
        
        self.navigationController = navigationController

        let interactivePushGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:)))
        self.interactivePushGestureRecognizer = interactivePushGestureRecognizer
        
        navigationController.delegate = self
        interactivePushGestureRecognizer.edges = .right
        interactivePushGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
        navigationController.view.addGestureRecognizer(interactivePushGestureRecognizer)
        
        let trackpadGestureRecognizer = TrackpadScrollGestureRecognizer(target: self, action: #selector(handleSwipeForward(_:)))
        trackpadGestureRecognizer.require(toFail: navigationController.interactivePopGestureRecognizer!)
        navigationController.view.addGestureRecognizer(trackpadGestureRecognizer)
    }

    @objc func handleSwipeForward(_ recognizer: UIPanGestureRecognizer) {
        interactive = true
        
        let velocity = recognizer.velocity(in: recognizer.view)
        let translation = recognizer.translation(in: recognizer.view)
        let dx = -translation.x / navigationController.view.bounds.width
        let vx = -velocity.x
        
        switch recognizer.state {
        case .began:
            if let viewController = navigationController.poppedViewControllers.first {
                pushingViewController = viewController
                navigationController.poppedViewControllers.removeFirst()
                navigationController.skipResetPoppedOnNextPush = true
                navigationController.pushViewController(viewController, animated: true)
            } else {
                interactive = false
            }
            
        case .changed:
            update(dx)

        case .ended:
            if (dx > minimumPushDistanceThreshold || vx > minimumPushVelocityThreshold) {
                finish()
            } else {
                cancel()
            }
            interactive = false
            
        default:
            cancel()
            interactive = false
        }
    }

}

extension InteractivePushTransition: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        self.navigationController.onWillShow()
    }
    
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if operation == .push, interactive {
            return self
        }
        
        return nil
    }
    
    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if interactive {
            return self
        }
        return nil
    }
}

extension InteractivePushTransition: UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return pushAnimationDuration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let toView = transitionContext.view(forKey: .to)!
        let fromView = transitionContext.view(forKey: .from)!

        let dimmingView = UIView()
        dimmingView.backgroundColor = .black
        dimmingView.alpha = 0
        dimmingView.frame = fromView.frame

        transitionContext.containerView.addSubview(dimmingView)
        transitionContext.containerView.addSubview(toView)
        
        // modify frame of presented view to go from x = <screen width> to x = 0
        var frame = fromView.frame
        frame.origin.x = frame.width
        toView.frame = frame
        frame.origin.x = 0
        
        // we want it linear while interactive, but we want the "ease out" animation
        // if the user flicks the screen ahrd enough to finish the transition without interaction
        let options = interactive ? UIView.AnimationOptions.curveLinear : .curveEaseOut
        
        let duration = transitionDuration(using: transitionContext)
        UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
            // these magic numbers scientifically determined by repeatedly adjusting and comparing to the system animation
            let translationDistance = -frame.size.width * 0.3
            fromView.transform = CGAffineTransform(translationX: translationDistance, y: 0)
            toView.frame = frame
            dimmingView.alpha = 0.075
        }) { (finished) in
            fromView.transform = .identity
            dimmingView.removeFromSuperview()
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}
