/*
 Copyright 2019 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

@objc protocol RoomContextualMenuViewControllerDelegate: AnyObject {
    func roomContextualMenuViewControllerDidTapBackgroundOverlay(_ viewController: RoomContextualMenuViewController)
}

@objcMembers
final class RoomContextualMenuViewController: UIViewController, Themable {
        
    // MARK: - Constants
    
    private enum Constants {
        static let reactionsMenuViewVerticalMargin: CGFloat = 10.0
        static let reactionsMenuViewHiddenScale: CGFloat = 0.97
    }
    
    // MARK: - Properties
    
    // MARK: Outlets
    
    @IBOutlet private weak var backgroundOverlayView: UIView!
    @IBOutlet private weak var menuToolbarView: RoomContextualMenuToolbarView!
    
    @IBOutlet private weak var menuToolbarViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet private weak var menuToolbarViewBottomConstraint: NSLayoutConstraint!
    
    @IBOutlet private weak var reactionsMenuContainerView: UIView!
    @IBOutlet private weak var reactionsMenuViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet private weak var reactionsMenuViewBottomConstraint: NSLayoutConstraint!
    
    // MARK: Private
    
    private var theme: Theme!
    private var contextualMenuItems: [RoomContextualMenuItem] = []
    private var reactionsMenuViewModel: ReactionsMenuViewModel?
    
    private weak var reactionsMenuView: ReactionsMenuView?
    
    private var reactionsMenuViewBottomStartConstraintConstant: CGFloat?
    private var reactionsMenuViewBottomEndConstraintConstant: CGFloat?
    
    private var hiddenToolbarViewBottomConstant: CGFloat {
        let bottomSafeAreaHeight: CGFloat
        
        bottomSafeAreaHeight = self.view.safeAreaInsets.bottom
        
        return -(self.menuToolbarViewHeightConstraint.constant + bottomSafeAreaHeight)
    }
    
    private var shouldPresentReactionsMenu: Bool {
        return self.reactionsMenuContainerView.isHidden == false
    }
    
    // MARK: Public
    
    var contentToReactFrame: CGRect?
    var shouldPerformTappedReactionAnimation: Bool {
        return self.reactionsMenuView?.reactionHasBeenTapped ?? false
    }
    
    weak var delegate: RoomContextualMenuViewControllerDelegate?
    
    // MARK: - Setup
    
    class func instantiate() -> RoomContextualMenuViewController {
        let viewController = StoryboardScene.RoomContextualMenuViewController.initialScene.instantiate()
        viewController.theme = ThemeService.shared().theme
        return viewController
    }
    
    // MARK: - Life cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.        

        self.backgroundOverlayView.isUserInteractionEnabled = true
        self.setupBackgroundOverlayGestureRecognizers()
        
        self.updateViews()
        
        self.registerThemeServiceDidChangeThemeNotification()
        self.update(theme: self.theme)
    }
    
    // MARK: - Public
    
    func update(contextualMenuItems: [RoomContextualMenuItem], reactionsMenuViewModel: ReactionsMenuViewModel?) {
        self.contextualMenuItems = contextualMenuItems
        self.reactionsMenuViewModel = reactionsMenuViewModel
        if self.isViewLoaded {
            self.updateViews()
        }
    }
    
    func showMenuToolbar() {
        self.menuToolbarViewBottomConstraint.constant = 0
        self.menuToolbarView.alpha = 1
        
        // Force VoiceOver to focus on the menu bar actions
        UIAccessibility.post(notification: .screenChanged, argument: self.menuToolbarView)
    }
    
    func hideMenuToolbar() {
        self.menuToolbarViewBottomConstraint.constant = self.hiddenToolbarViewBottomConstant
        self.menuToolbarView.alpha = 0
        UIAccessibility.post(notification: .screenChanged, argument: nil)
    }
    
    func prepareReactionsMenuAnimations() {
        guard let frame = self.contentToReactFrame, frame.equalTo(CGRect.null) == false else {
            return
        }
        
        let menuHeight = self.reactionsMenuViewHeightConstraint.constant
        let verticalMargin = Constants.reactionsMenuViewVerticalMargin
        
        let reactionsMenuViewBottomStartConstraintConstant: CGFloat?
        let reactionsMenuViewBottomEndConstraintConstant: CGFloat?
        
        // Try to display the menu at the top of the message first
        // Then, try at the bottom
        // Else, keep the position defined in the storyboard
        if frame.origin.y - verticalMargin >= menuHeight {
            let menuViewBottomY = frame.origin.y - verticalMargin
            reactionsMenuViewBottomStartConstraintConstant = menuViewBottomY + menuHeight/2
            reactionsMenuViewBottomEndConstraintConstant = menuViewBottomY
        } else {
            let frameBottomY = frame.origin.y + frame.size.height + verticalMargin
            let visibleViewHeight = self.view.frame.size.height - self.menuToolbarView.frame.size.height
            
            if frameBottomY + menuHeight < visibleViewHeight {
                let menuViewBottomY = frameBottomY + menuHeight
                
                reactionsMenuViewBottomEndConstraintConstant = menuViewBottomY
                reactionsMenuViewBottomStartConstraintConstant = menuViewBottomY - menuHeight/2
            } else {
                reactionsMenuViewBottomEndConstraintConstant = nil
                reactionsMenuViewBottomStartConstraintConstant = nil
            }
        }
        
        self.reactionsMenuViewBottomStartConstraintConstant = reactionsMenuViewBottomStartConstraintConstant
        self.reactionsMenuViewBottomEndConstraintConstant = reactionsMenuViewBottomEndConstraintConstant
        
        self.reactionsMenuContainerView.isHidden = false
    }
    
    func showReactionsMenu() {
        guard self.shouldPresentReactionsMenu, let reactionsMenuView = self.reactionsMenuView else {
            return
        }
        
        if let reactionsMenuViewBottomEndConstraintConstant = self.reactionsMenuViewBottomEndConstraintConstant {
            self.reactionsMenuViewBottomConstraint.constant = reactionsMenuViewBottomEndConstraintConstant
        }
        
        reactionsMenuView.alpha = 1
        reactionsMenuContainerView.transform = CGAffineTransform.identity
    }
    
    func hideReactionsMenu() {
        guard self.shouldPresentReactionsMenu, let reactionsMenuView = self.reactionsMenuView else {
            return
        }
        
        if let reactionsMenuViewBottomStartConstraintConstant = self.reactionsMenuViewBottomStartConstraintConstant {
            self.reactionsMenuViewBottomConstraint.constant = reactionsMenuViewBottomStartConstraintConstant
        }
        
        reactionsMenuView.alpha = 0
        
        let transformScale = Constants.reactionsMenuViewHiddenScale
        self.reactionsMenuContainerView.transform = CGAffineTransform(scaleX: transformScale, y: transformScale)
    }
    
    func selectedReactionAnimationsIntructionsPart1() {
        self.reactionsMenuView?.selectionAnimationInstructionPart1()
    }
    
    func selectedReactionAnimationsIntructionsPart2() {
        self.reactionsMenuView?.selectionAnimationInstructionPart2()
    }
    
    func update(theme: Theme) {
        self.menuToolbarView.update(theme: theme)
        self.reactionsMenuView?.update(theme: theme)
    }
    
    // MARK: - Private
    
    private func updateViews() {
        self.menuToolbarView.fill(contextualMenuItems: self.contextualMenuItems)
        
        let hideReactionMenu: Bool
        
        if let reactionsMenuViewModel = self.reactionsMenuViewModel {
            hideReactionMenu = false
            self.updateReactionsMenu(with: reactionsMenuViewModel)
        } else {
            hideReactionMenu = true
        }
        
        self.reactionsMenuContainerView.isHidden = hideReactionMenu
    }
    
    private func updateReactionsMenu(with viewModel: ReactionsMenuViewModel) {
        
        if self.reactionsMenuContainerView.subviews.isEmpty {
            let reactionsMenuView = ReactionsMenuView.loadFromNib()
            self.reactionsMenuContainerView.vc_addSubViewMatchingParent(reactionsMenuView)
            reactionsMenuView.update(theme: self.theme)
            self.reactionsMenuView = reactionsMenuView
        }
        
        self.reactionsMenuView?.viewModel = viewModel
    }
    
    private func setupBackgroundOverlayGestureRecognizers() {
        
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handle(gestureRecognizer:)))
        tapGestureRecognizer.delegate = self
        
        let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(handle(gestureRecognizer:)))
        swipeGestureRecognizer.direction = [.down, .up]
        swipeGestureRecognizer.delegate = self
        
        self.backgroundOverlayView.addGestureRecognizer(tapGestureRecognizer)
        self.backgroundOverlayView.addGestureRecognizer(swipeGestureRecognizer)
    }
    
    @objc private func handle(gestureRecognizer: UIGestureRecognizer) {
        self.delegate?.roomContextualMenuViewControllerDidTapBackgroundOverlay(self)
    }
    
    private func registerThemeServiceDidChangeThemeNotification() {
        NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
    }
    
    @objc private func themeDidChange() {
        self.update(theme: ThemeService.shared().theme)
    }
}

// MARK: - UIGestureRecognizerDelegate
extension RoomContextualMenuViewController: UIGestureRecognizerDelegate {
    
    // Avoid triggering background overlay gesture recognizers when touching reactions menu
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        return touch.vc_isInside(view: self.reactionsMenuContainerView) == false
    }
}
