// 
// Copyright 2020 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit

/// Controller for custom sized presentations.
/// By default, presented view controller will be sized as both half of the screen in width and height, and will be centered to the screen.
/// Implement `CustomSizedPresentable` in presented view controller to change that if needed.
/// This class can also be set as `transitioningDelegate` as presented view controller, as it's conforming `UIViewControllerTransitioningDelegate`.
@objcMembers
class CustomSizedPresentationController: UIPresentationController {
    
    //  MARK: - Public Properties
    
    /// Corner radius for presented view controller's view. Default value is `8.0`.
    var cornerRadius: CGFloat = 8.0
    
    /// Background color of dimming view, which is located behind the presented view controller's view. Default value is `white with 0.5 alpha`.
    var dimColor: UIColor = UIColor(white: 0.0, alpha: 0.5)
    
    /// Dismiss view controller when background tapped. Default value is `true`.
    var dismissOnBackgroundTap: Bool = true
    
    //  MARK: - Private Properties
    
    /// Dim view
    private var dimmingView: UIView!
    
    /// Wrapper view for presentation. It's introduced to handle corner radius on presented view controller's view and it's superview of all other views.
    private var presentationWrappingView: UIView!
    
    //  MARK: - Initializer
    
    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        
        presentedViewController.modalPresentationStyle = .custom
    }
    
    //  MARK: - Actions
    
    @objc private func dimmingViewTapped(_ sender: UITapGestureRecognizer) {
        if dismissOnBackgroundTap {
            presentedViewController.dismiss(animated: true, completion: nil)
        }
    }
    
    //  MARK: - Presentation
    
    override func presentationTransitionWillBegin() {
        guard let presentedViewControllerView = super.presentedView else { return }
        
        // Wrap the presented view controller's view in an intermediate hierarchy
        // that applies a shadow and rounded corners to the top-left and top-right
        // edges.  The final effect is built using three intermediate views.
        //
        // presentationWrapperView              <- shadow
        //   |- presentationRoundedCornerView   <- rounded corners (masksToBounds)
        //        |- presentedViewControllerWrapperView
        //             |- presentedViewControllerView (presentedViewController.view)
        //
        // SEE ALSO: The note in AAPLCustomPresentationSecondViewController.m.
        do {
            let presentationWrapperView = UIView(frame: frameOfPresentedViewInContainerView)
            presentationWrapperView.layer.shadowOffset = CGSize(width: 0, height: -2)
            presentationWrapperView.layer.shadowRadius = 10
            presentationWrapperView.layer.shadowColor = UIColor(white: 0, alpha: 0.5).cgColor
            presentationWrappingView = presentationWrapperView
            
            // presentationRoundedCornerView is CORNER_RADIUS points taller than the
            // height of the presented view controller's view.  This is because
            // the cornerRadius is applied to all corners of the view.  Since the
            // effect calls for only the top two corners to be rounded we size
            // the view such that the bottom CORNER_RADIUS points lie below
            // the bottom edge of the screen.
            let cornerViewRect = presentationWrapperView.bounds// .inset(by: UIEdgeInsets(top: 0, left: 0, bottom: -cornerRadius, right: 0))
            
            let presentationRoundedCornerView = UIView(frame: cornerViewRect)
            presentationRoundedCornerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            presentationRoundedCornerView.layer.cornerRadius = cornerRadius
            presentationRoundedCornerView.layer.masksToBounds = true
            
            // To undo the extra height added to presentationRoundedCornerView,
            // presentedViewControllerWrapperView is inset by CORNER_RADIUS points.
            // This also matches the size of presentedViewControllerWrapperView's
            // bounds to the size of -frameOfPresentedViewInContainerView.
            let wrapperRect = presentationRoundedCornerView.bounds
            
            let presentedViewControllerWrapperView = UIView(frame: wrapperRect)
            presentedViewControllerWrapperView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            
            // Add presentedViewControllerView -> presentedViewControllerWrapperView.
            presentedViewControllerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            presentedViewControllerView.frame = presentedViewControllerWrapperView.bounds
            presentedViewControllerWrapperView.addSubview(presentedViewControllerView)
            
            // Add presentedViewControllerWrapperView -> presentationRoundedCornerView.
            presentationRoundedCornerView.addSubview(presentedViewControllerWrapperView)
            
            // Add presentationRoundedCornerView -> presentationWrapperView.
            presentationWrapperView.addSubview(presentationRoundedCornerView)
        }
        
        // Add a dimming view behind presentationWrapperView.  self.presentedView
        // is added later (by the animator) so any views added here will be
        // appear behind the -presentedView.
        do {
            let dimmingView = UIView(frame: containerView?.bounds ?? .zero)
            dimmingView.backgroundColor = dimColor
            dimmingView.isOpaque = false
            dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            
            let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped(_:)))
            dimmingView.addGestureRecognizer(tapGestureRecognizer)
            
            self.dimmingView = dimmingView
            containerView?.addSubview(dimmingView)
            
            // Get the transition coordinator for the presentation so we can
            // fade in the dimmingView alongside the presentation animation.
            let transitionCoordinator = self.presentingViewController.transitionCoordinator
            
            dimmingView.alpha = 0.0
            transitionCoordinator?.animate(alongsideTransition: { _ in
                self.dimmingView?.alpha = 1.0
            }, completion: nil)
        }
    }
    
    override func presentationTransitionDidEnd(_ completed: Bool) {
        if !completed {
            presentationWrappingView = nil
            dimmingView = nil
        }
    }
    
    //  MARK: - Dismissal
    
    override func dismissalTransitionWillBegin() {
        guard let coordinator = presentingViewController.transitionCoordinator else {
            dimmingView.alpha = 0.0
            return
        }
        
        coordinator.animate(alongsideTransition: { _ in
            self.dimmingView.alpha = 0.0
        })
    }
    
    override func dismissalTransitionDidEnd(_ completed: Bool) {
        if completed {
            presentationWrappingView = nil
            dimmingView = nil
        }
    }
    
    //  MARK: - Overrides
    
    override var presentedView: UIView? {
        return presentationWrappingView
    }
    
    override func size(forChildContentContainer container: UIContentContainer,
                       withParentContainerSize parentSize: CGSize) -> CGSize {
        guard container === presentedViewController else {
            return super.size(forChildContentContainer: container, withParentContainerSize: parentSize)
        }
        
        //  return value from presentable if implemented
        if let presentable = presentedViewController as? CustomSizedPresentable,
           let customSize = presentable.customSize?(withParentContainerSize: parentSize) {
            return customSize
        }
        if let navController = presentedViewController as? UINavigationController,
           let presentable = navController.viewControllers.first(where: { $0 is CustomSizedPresentable }) as? CustomSizedPresentable,
           let customSize = presentable.customSize?(withParentContainerSize: parentSize) {
            return customSize
        }
        
        //  half of the width/height by default
        return CGSize(width: parentSize.width/2.0, height: parentSize.height/2.0)
    }
    
    override var frameOfPresentedViewInContainerView: CGRect {
        guard let containerView = containerView else {
            return super.frameOfPresentedViewInContainerView
        }
        
        let size = self.size(forChildContentContainer: presentedViewController,
                             withParentContainerSize: containerView.bounds.size)
        
        //  use origin value from presentable if implemented
        if let presentable = presentedViewController as? CustomSizedPresentable,
           let origin = presentable.position?(withParentContainerSize: containerView.bounds.size) {
            return CGRect(origin: origin, size: size)
        }
        if let navController = presentedViewController as? UINavigationController,
           let presentable = navController.viewControllers.first(where: { $0 is CustomSizedPresentable }) as? CustomSizedPresentable,
           let origin = presentable.position?(withParentContainerSize: containerView.bounds.size) {
            return CGRect(origin: origin, size: size)
        }
        
        //  center presented view by default
        let origin = CGPoint(x: (containerView.bounds.width - size.width)/2,
                             y: (containerView.bounds.height - size.height)/2)
        
        return CGRect(origin: origin, size: size)
    }
    
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        
        self.dimmingView?.frame = containerView?.bounds ?? .zero
        self.presentationWrappingView?.frame = frameOfPresentedViewInContainerView
    }
    
    override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
        super.preferredContentSizeDidChange(forChildContentContainer: container)
        
        if container === presentedViewController {
            self.containerView?.setNeedsLayout()
        }
    }
    
}

//  MARK: - UIViewControllerTransitioningDelegate

extension CustomSizedPresentationController: UIViewControllerTransitioningDelegate {
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let controller = CustomSizedPresentationController(presentedViewController: presented, presenting: presenting)
        controller.cornerRadius = cornerRadius
        controller.dimColor = dimColor
        controller.dismissOnBackgroundTap = dismissOnBackgroundTap
        return controller
    }
    
}
