
import UIKit
import CocoaLumberjackSwift

// MARK: - Delegate
@objc public protocol WMFTableOfContentsAnimatorDelegate {
    
    func tableOfContentsAnimatorDidTapBackground(_ controller: WMFTableOfContentsAnimator)
}

open class WMFTableOfContentsAnimator: UIPercentDrivenInteractiveTransition, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning, UIGestureRecognizerDelegate, WMFTableOfContentsPresentationControllerTapDelegate, Themeable {
    
    fileprivate var theme = Theme.standard
    public func apply(theme: Theme) {
        self.theme = theme
        self.presentationController?.apply(theme: theme)
    }
    
    var displaySide = WMFTableOfContentsDisplaySide.left
    var displayMode = WMFTableOfContentsDisplayMode.modal
    
    // MARK: - init
    public required init(presentingViewController: UIViewController, presentedViewController: UIViewController) {
        self.presentingViewController = presentingViewController
        self.presentedViewController = presentedViewController
        self.isPresenting = true
        self.isInteractive = false
        presentationGesture = UIPanGestureRecognizer()
        super.init()
        presentationGesture.addTarget(self, action: #selector(WMFTableOfContentsAnimator.handlePresentationGesture(_:)))
        presentationGesture.maximumNumberOfTouches = 1
        presentationGesture.delegate = self
        self.presentingViewController!.view.addGestureRecognizer(presentationGesture)
    }
    
    deinit {
        removeDismissalGestureRecognizer()
        presentationGesture.removeTarget(self, action: #selector(WMFTableOfContentsAnimator.handlePresentationGesture(_:)))
        presentationGesture.view?.removeGestureRecognizer(presentationGesture)
    }
    
    weak var presentingViewController: UIViewController?
    
    weak var presentedViewController: UIViewController?

    open var gesturePercentage: CGFloat = 0.4

    weak open var delegate: WMFTableOfContentsAnimatorDelegate?
    
    fileprivate(set) open var isPresenting: Bool
    
    fileprivate(set) open var isInteractive: Bool
    
    // MARK: - WMFTableOfContentsPresentationControllerTapDelegate
    open func tableOfContentsPresentationControllerDidTapBackground(_ controller: WMFTableOfContentsPresentationController) {
        delegate?.tableOfContentsAnimatorDidTapBackground(self)
    }
    
    // MARK: - UIViewControllerTransitioningDelegate
    
    weak var presentationController: WMFTableOfContentsPresentationController?
    
    open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        guard presented == self.presentedViewController else {
            return nil
        }
        let presentationController = WMFTableOfContentsPresentationController(presentedViewController: presented, presentingViewController: self.presentingViewController, tapDelegate: self)
        presentationController.apply(theme: theme)
        presentationController.displayMode = displayMode
        presentationController.displaySide = displaySide
        self.presentationController = presentationController
        return presentationController
    }
    
    open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard presented == self.presentedViewController else {
            return nil
        }
        self.isPresenting = true
        return self
    }
    
    open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard dismissed == self.presentedViewController else {
            return nil
        }
        self.isPresenting = false
        return self
    }
    
    open func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if self.isInteractive {
            return self
        }else{
            return nil
        }
    }
    
    open func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if self.isInteractive {
            return self
        }else{
            return nil
        }
    }

    // MARK: - UIViewControllerAnimatedTransitioning
    open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return self.isPresenting ? 0.5 : 0.8
    }
    
    open func animateTransition(using transitionContext: UIViewControllerContextTransitioning)  {
        if isPresenting {
            removeDismissalGestureRecognizer()
            addDismissalGestureRecognizer(transitionContext.containerView)
            animatePresentationWithTransitionContext(transitionContext)
        }
        else {
            animateDismissalWithTransitionContext(transitionContext)
        }
    }
    
    var tocMultiplier:CGFloat {
        switch displaySide {
        case .left:
            return -1.0
        case .right:
            return 1.0
        case .center:
            fallthrough
        default:
            return UIApplication.shared.wmf_isRTL ? -1.0 : 1.0
        }
    }
    
    // MARK: - Animation
    func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
        let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
        let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        let containerView = transitionContext.containerView
        
        // Position the presented view off the top of the container view
        var f = transitionContext.finalFrame(for: presentedController)
        f.origin.x += f.size.width * tocMultiplier
        presentedControllerView.frame = f
        
        containerView.addSubview(presentedControllerView)
        
        animateTransition(self.isInteractive, duration: self.transitionDuration(using: transitionContext), animations: { () -> Void in
            var f = presentedControllerView.frame
            f.origin.x -= f.size.width * self.tocMultiplier
            presentedControllerView.frame = f
            }, completion: {(completed: Bool) -> Void in
                let cancelled = transitionContext.transitionWasCancelled
                transitionContext.completeTransition(!cancelled)
        })
    }
    
    func animateDismissalWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
        let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
        
        animateTransition(self.isInteractive, duration: self.transitionDuration(using: transitionContext), animations: { () -> Void in
            var f = presentedControllerView.frame
            switch self.displaySide {
            case .left:
                f.origin.x = -1*f.size.width
                break
            case .right:
                fallthrough
            case .center:
                fallthrough
            default:
                f.origin.x = transitionContext.containerView.bounds.size.width
            }
            presentedControllerView.frame = f

            }, completion: {(completed: Bool) -> Void in
                let cancelled = transitionContext.transitionWasCancelled
                transitionContext.completeTransition(!cancelled)
        })
    }
    
    func animateTransition(_ interactive: Bool, duration: TimeInterval, animations: @escaping () -> Void, completion: ((Bool) -> Void)?){
        
        if(interactive){
            UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in
                animations()
                }, completion: { (completed: Bool) -> Void in
                    completion?(completed)
            })
    
        }else{
            UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: {
                animations()
                }, completion: {(completed: Bool) -> Void in
                    completion?(completed)
            })
        }
    }
    
    
    // MARK: - Gestures
    private let presentationGesture: UIPanGestureRecognizer
    
    var dismissalGesture: UIPanGestureRecognizer?
    
    func addDismissalGestureRecognizer(_ containerView: UIView) {
        let gesture = UIPanGestureRecognizer(target: self, action: #selector(WMFTableOfContentsAnimator.handleDismissalGesture(_:)))
        gesture.delegate = self
        containerView.addGestureRecognizer(gesture)
        dismissalGesture = gesture
    }

    func removeDismissalGestureRecognizer() {
        if let dismissalGesture = dismissalGesture {
            dismissalGesture.view?.removeGestureRecognizer(dismissalGesture)
            dismissalGesture.removeTarget(self, action: #selector(WMFTableOfContentsAnimator.handleDismissalGesture(_:)))
        }
        dismissalGesture = nil
    }
    @objc func handlePresentationGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
        
        switch(gesture.state) {
        case (.began):
            self.isInteractive = true
            self.presentingViewController?.present(self.presentedViewController!, animated: true, completion: nil)
        case (.changed):
            let translation = gesture.translation(in: gesture.view)
            let transitionProgress = max(min(translation.x * -tocMultiplier / self.presentedViewController!.view.bounds.maxX, 0.99), 0.01)
            self.update(transitionProgress)
        case (.ended):
            self.isInteractive = false
            let velocityRequiredToPresent = -gesture.view!.bounds.width * tocMultiplier
            let velocityRequiredToDismiss = -velocityRequiredToPresent
            
            let velocityX = gesture.velocity(in: gesture.view).x
            if velocityX*velocityRequiredToDismiss > 1 && abs(velocityX) > abs(velocityRequiredToDismiss){
                cancel()
                return
            }
            
            if velocityX*velocityRequiredToPresent > 1 && abs(velocityX) > abs(velocityRequiredToPresent){
                finish()
                return
            }
            
            let progressRequiredToPresent = 0.33
            
            if(self.percentComplete >= CGFloat(progressRequiredToPresent)){
                finish()
                return
            }
            
            cancel()
            
        case (.cancelled):
            self.isInteractive = false
            cancel()
            
        default :
            break
        }
    }
    
    @objc func handleDismissalGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
        
        switch(gesture.state) {
        case .began:
            self.isInteractive = true
            self.presentingViewController?.dismiss(animated: true, completion: nil)
        case .changed:
            let translation = gesture.translation(in: gesture.view)
            let transitionProgress = max(min(translation.x * tocMultiplier / self.presentedViewController!.view.bounds.maxX, 0.99), 0.01)
        
            self.update(transitionProgress)
            DDLogVerbose("TOC transition progress: \(transitionProgress)")
        case .ended:
            self.isInteractive = false
            let velocityRequiredToPresent = -gesture.view!.bounds.width * tocMultiplier
            let velocityRequiredToDismiss = -velocityRequiredToPresent
            
            let velocityX = gesture.velocity(in: gesture.view).x
            if velocityX*velocityRequiredToDismiss > 1 && abs(velocityX) > abs(velocityRequiredToDismiss){
                finish()
                return
            }
            
            if velocityX*velocityRequiredToPresent > 1 && abs(velocityX) > abs(velocityRequiredToPresent){
                cancel()
                return
            }
            
            let progressRequiredToDismiss = 0.50
            
            if(self.percentComplete >= CGFloat(progressRequiredToDismiss)){
                finish()
                return
            }
            
            cancel()
            
        case .cancelled:
            self.isInteractive = false
            cancel()
            
        default :
            break
        }
    }
    
    // MARK: - UIGestureRecognizerDelegate
    
    open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard displayMode == .modal else {
            return false
        }
        
        let isRTL = UIApplication.shared.wmf_isRTL
        guard (displaySide == .center) || (isRTL && displaySide == .left) || (!isRTL && displaySide == .right) else  {
            return false
        }
        
        if gestureRecognizer == self.dismissalGesture {
            
            if let translation = self.dismissalGesture?.translation(in: dismissalGesture?.view) {
                if (translation.x * tocMultiplier > 0){
                    return true
                } else {
                    return false
                }
            } else {
                return false
            }
            
        } else if gestureRecognizer == self.presentationGesture {
            
            let translation = presentationGesture.translation(in: presentationGesture.view)
            let location = presentationGesture.location(in: presentationGesture.view)
            let gestureWidth = presentationGesture.view!.frame.width * gesturePercentage
            let maxLocation: CGFloat
            let isInStartBoundary: Bool
            switch displaySide {
            case .left:
                maxLocation = gestureWidth
                isInStartBoundary = maxLocation - location.x > 0
            default:
                maxLocation =  presentationGesture.view!.frame.maxX - gestureWidth
                isInStartBoundary = location.x - maxLocation > 0
            }
            if (translation.x * tocMultiplier < 0) && isInStartBoundary {
                return true
            } else {
                return false
            }
        } else {
            return true
        }
    }
    
    open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return otherGestureRecognizer.isKind(of: UIScreenEdgePanGestureRecognizer.self)
    }
}
