/**
 *  BulletinBoard
 *  Copyright (c) 2017 - present Alexis Aubry. Licensed under the MIT license.
 */

import UIKit

/**
 * An interaction controller that handles swipe-to-dismiss for bulletins.
 */

class BulletinSwipeInteractionController: UIPercentDrivenInteractiveTransition, UIGestureRecognizerDelegate {

    /// Whether a panning interaction is in progress.
    var isInteractionInProgress = false

    var panGestureRecognizer: UIPanGestureRecognizer?

    // MARK: - State

    private var isFinished = false
    private var currentPercentage: CGFloat = -1
    private weak var viewController: BulletinViewController!

    private var snapshotView: UIView? {
        return viewController.activeSnapshotView
    }

    private var contentView: UIView {
        return viewController.contentView
    }

    private var activityIndicatorView: UIView {
        return viewController.activityIndicator
    }

    // MARK: - Preparation

    /**
     * Sets up the interaction recognizer for the given view controller and content view.
     */

    func wire(to viewController: BulletinViewController) {
        self.viewController = viewController
        prepareGestureRecognizer()
    }

    private func prepareGestureRecognizer() {

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture))
        panGesture.maximumNumberOfTouches = 1
        panGesture.cancelsTouchesInView = false
        panGesture.delegate = self

        self.panGestureRecognizer = panGesture
        contentView.addGestureRecognizer(panGesture)

    }


    // MARK: - Gesture Recognizer

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        return !(touch.view is UIControl)
    }

    @objc func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) {

        /// Constants

        let screenHeight = viewController.view.bounds.height
        let distanceFactor: CGFloat = screenHeight >= 500 ? 3/4 : 2/3

        let dismissThreshold: CGFloat = 256 * distanceFactor
        let elasticThreshold: CGFloat = 128 * distanceFactor
        let trackScreenPercentage = dismissThreshold / contentView.bounds.height

        switch gestureRecognizer.state {
        case .began:

            isFinished = false

            gestureRecognizer.setTranslation(.zero, in: contentView)

            let isCompactWidth = viewController.traitCollection.horizontalSizeClass == .compact

            guard viewController.isDismissable && isCompactWidth else {
                isInteractionInProgress = false
                return
            }

            isInteractionInProgress = true

            viewController.dismiss(animated: true) {

                guard self.isFinished else {
                    return
                }

                self.viewController.manager?.completeDismissal()

            }

        case .changed:

            guard !isFinished else {
                return
            }

            let translation = gestureRecognizer.translation(in: contentView)
            let verticalTranslation = translation.y
            isFinished = false

            guard (verticalTranslation > 0) && isInteractionInProgress else {
                update(0)
                updateCardViews(forTranslation: translation)
                return
            }

            snapshotView?.transform = .identity

            let adaptativeTranslation = self.adaptativeTranslation(for: verticalTranslation, elasticThreshold: elasticThreshold)
            let newPercentage = (adaptativeTranslation / dismissThreshold) * trackScreenPercentage

            guard currentPercentage != newPercentage else {
                return
            }

            currentPercentage = newPercentage
            update(currentPercentage)

        case .cancelled, .failed:

            isInteractionInProgress = false

            if !isFinished {
                resetCardViews()
            }

            panGestureRecognizer?.isEnabled = true

        case .ended:

            guard isInteractionInProgress else {
                resetCardViews()
                isFinished = false
                return
            }

            isInteractionInProgress = false

            let translation = gestureRecognizer.translation(in: contentView).y

            if translation >= dismissThreshold {
                isFinished = true
                finish()
            } else {
                resetCardViews()
                cancel()
                isFinished = false
            }

        default:
            break
        }

    }

    // MARK: - Math

    // Source: https://github.com/HarshilShah/DeckTransition
    let elasticTranslationCurve = { (translation: CGFloat, translationFactor: CGFloat) -> CGFloat in
        return 30 * atan(translation/120) + translation/10
    }

    private func adaptativeTranslation(for translation: CGFloat, elasticThreshold: CGFloat) -> CGFloat {

        let translationFactor: CGFloat = 2/3

        if translation >= elasticThreshold {
            let frictionLength = translation - elasticThreshold
            let frictionTranslation = elasticTranslationCurve(frictionLength, translationFactor)
            return frictionTranslation + (elasticThreshold * translationFactor)
        } else {
            return translation * translationFactor
        }

    }

    private func transform(forTranslation translation: CGPoint) -> CGAffineTransform {

        let translationFactor: CGFloat = 1/3
        var adaptedTranslation = translation

        // Vertical

        if translation.y < 0 || !(isInteractionInProgress) {
            adaptedTranslation.y = elasticTranslationCurve(translation.y, translationFactor)
        }

        let yTransform = adaptedTranslation.y * translationFactor

        if viewController.traitCollection.horizontalSizeClass == .compact {
            return CGAffineTransform(translationX: 0, y: yTransform)
        }

        // Horizontal

        adaptedTranslation.x = elasticTranslationCurve(translation.x, translationFactor)
        let xTransform = adaptedTranslation.x * translationFactor

        return CGAffineTransform(translationX: xTransform, y: yTransform)

    }

    // MARK: - Position Management

    private func updateCardViews(forTranslation translation: CGPoint) {

        let transform = self.transform(forTranslation: translation)

        snapshotView?.transform = transform
        contentView.transform = transform
        activityIndicatorView.transform = transform

    }

    private func resetCardViews() {

        let options = UIView.AnimationOptions(rawValue: 6 << 7)

        let animations = {
            self.snapshotView?.transform = .identity
            self.contentView.transform = .identity
            self.activityIndicatorView.transform = .identity
        }

        viewController.backgroundView.show()

        UIView.animate(withDuration: 0.15, delay: 0, options: options, animations: animations) { _ in
            self.update(0)
            self.cancel()
        }

    }

    // MARK: - Cancellation

    /**
     * Resets the view if needed.
     */

    func cancelIfNeeded() {

        if panGestureRecognizer?.state == .changed {
            panGestureRecognizer?.isEnabled = false
        }

    }

}