//
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

protocol PromptViewContollerDelegate: class {
    func promptViewController(_ controller: PromptViewController, cancelled: Bool, text: String)
}

public class PromptViewController: UIViewController, UITextViewDelegate {

    private let backColor = UIColor.black.withAlphaComponent(0.4)
    private let overlayColor = UIColor.white
    @objc var animate = true

    @objc var submitText: String = "prompt_submit"
    @objc var cancelText: String = "prompt_cancel"
    @objc var placeholderText: String = ""
    @objc var promptText: String = ""

    private var isCompactHeight: Bool {
        return view.traitCollection.verticalSizeClass == .compact
    }

    private struct Defaults {
        static let overlayHeight = CGFloat(200)
        static let horizontalMargin = CGFloat(20)
        static let animationDuration = TimeInterval(0.2)
        static let initialScale = CGFloat(1.2)
        static let margin = 20
    }

    @objc lazy var overlayView = UIView()
    @objc lazy var titleLabel = UILabel()
    @objc lazy var cancelButton = UIButton(type: .system)
    @objc lazy var submitButton = UIButton(type: .system)
    @objc lazy var textArea = UITextView()
    @objc lazy var separator = UIView()

    @objc var overlayConstraints: [NSLayoutConstraint] = []

    var response: RPCResponse?

    weak var delegate: PromptViewContollerDelegate?

    init() {
        super.init(nibName: nil, bundle: nil)

        // Defaults
        setSubmitButtonLabel(submitText)
        setCancelButtonLabel(cancelText)
    }

    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override open func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(overlayView)
        overlayView.addSubview(titleLabel)
        overlayView.addSubview(cancelButton)
        overlayView.addSubview(submitButton)
        overlayView.addSubview(textArea)
        overlayView.addSubview(separator)

        overlayView.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        cancelButton.translatesAutoresizingMaskIntoConstraints = false
        submitButton.translatesAutoresizingMaskIntoConstraints = false
        textArea.translatesAutoresizingMaskIntoConstraints = false
        separator.translatesAutoresizingMaskIntoConstraints = false

        view.layoutSubviews()
        overlayView.layoutSubviews()

        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)))

        // Set initial values and positions so we can animate on didAppear
        view.backgroundColor = UIColor.clear
        overlayView.transform = CGAffineTransform(scaleX: Defaults.initialScale, y: Defaults.initialScale)
        overlayView.alpha = 0

        cancelButton.addTarget(self, action: #selector(cancelButtonDidTouch), for: .touchUpInside)
        submitButton.addTarget(self, action: #selector(submitButtonDidTouch), for: .touchUpInside)

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)

        setupViews()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    override public func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        UIView.animate(withDuration: animate ? Defaults.animationDuration : 0, delay: 0, options: .curveEaseInOut, animations: {
            self.view.backgroundColor = self.backColor
            self.overlayView.alpha = 1
            self.overlayView.transform = CGAffineTransform(scaleX: 1, y: 1)
            }, completion: { _ in
        })
    }

    // View setup
    private func setupViews() {
        setupOverlay()
        setupOverlayHeader()
        setupTextArea()
    }

    private func setupOverlay() {
        overlayView.backgroundColor = overlayColor
        overlayView.clipsToBounds = true
        overlayView.layer.cornerRadius = 10
        overlayView.layer.masksToBounds = true

        setupOverlayConstraints()
    }

    private func setupOverlayConstraints() {
        // Remove old overlay constraints
        view.removeConstraints(overlayConstraints)

        if isCompactHeight {
            // Compact height
            let horizontalContraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-margin-[view]-margin-|", options: [], metrics: ["margin": Defaults.margin], views: ["view": overlayView])
            let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-margin-[view]-margin-|", options: [], metrics: ["margin": Defaults.margin], views: ["view": overlayView])
            overlayConstraints = horizontalContraints + verticalConstraints
        } else {
            // Regular height
            let centerXConstraint = NSLayoutConstraint(item: overlayView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
            let centerYConstraint = NSLayoutConstraint(item: overlayView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
            let heightConstraint = NSLayoutConstraint(item: overlayView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: Defaults.overlayHeight)
            let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-margin-[view]-margin-|", options: [], metrics: ["margin": Defaults.margin], views: ["view": overlayView])
            overlayConstraints = [centerXConstraint, centerYConstraint, heightConstraint] + horizontalConstraints
        }
        view.addConstraints(overlayConstraints)
    }

    // Before orientation change
    public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        dismissKeyboard()
    }

    public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        setupOverlayConstraints()
    }

    private func setupOverlayHeader() {
        // Title Label
        titleLabel.font = titleLabel.font.bold()

        titleLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: .horizontal)
        let centerXConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: overlayView, attribute: .centerX, multiplier: 1, constant: 0)
        let titleTopConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-14-[view]", options: [], metrics: nil, views: ["view": titleLabel])
        NSLayoutConstraint.activate([centerXConstraint] + titleTopConstraints)

        // Cancel Button
        cancelButton.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal)
        let cancelBaselineConstraint = NSLayoutConstraint(item: cancelButton, attribute: .lastBaseline, relatedBy: .equal, toItem: titleLabel, attribute: .lastBaseline, multiplier: 1, constant: 0)
        NSLayoutConstraint.activate([cancelBaselineConstraint])

        // Submit Button
        submitButton.titleLabel?.font = submitButton.titleLabel?.font.bold()
        submitButton.isEnabled = false

        submitButton.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal)
        let submitBaselineConstraint = NSLayoutConstraint(item: submitButton, attribute: .lastBaseline, relatedBy: .equal, toItem: titleLabel, attribute: .lastBaseline, multiplier: 1, constant: 0)
        NSLayoutConstraint.activate([submitBaselineConstraint])

        let headerLayoutHorizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|-[cancel]-(>=8)-[title]-(>=8)-[submit]-|", options: [], metrics: nil, views: ["cancel": cancelButton, "title": titleLabel, "submit": submitButton])
        NSLayoutConstraint.activate(headerLayoutHorizontalConstraint)

        // Separator
        separator.backgroundColor = UIColor.lightGray.withAlphaComponent(0.4)
        separator.constrainToHeight(1)

        let separtorHorizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|", options: [], metrics: nil, views: ["view": separator])
        let separtorVerticalConstraint = NSLayoutConstraint(item: separator, attribute: .top, relatedBy: .equal, toItem: overlayView, attribute: .top, multiplier: 1, constant: 40)
        NSLayoutConstraint.activate([separtorVerticalConstraint] + separtorHorizontalConstraints)
    }

    private func setupTextArea() {
        // Text area
        textArea.delegate = self
        textArea.font = textArea.font?.withSize(17)

        let textAreaHorizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-[view]-|", options: [], metrics: nil, views: ["view": textArea])
        let textAreaVerticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[sep][text]|", options: [], metrics: nil, views: ["sep": separator, "text": textArea])
        NSLayoutConstraint.activate(textAreaHorizontalConstraints + textAreaVerticalConstraints)
    }

    @objc func dismissOverlay(_ completion: @escaping (_ finished: Bool) -> Void) {
        dismissKeyboard()
        UIView.animate(withDuration: animate ? Defaults.animationDuration : 0, delay: 0, options: .curveEaseInOut, animations: {
            self.view.backgroundColor = UIColor.clear
            self.overlayView.alpha = 0
            }, completion: completion)
    }

    // MARK: Actions
    @objc func submitButtonDidTouch() {
        if !textArea.text.isEmpty {
            dismissOverlay { _ in
                self.delegate?.promptViewController(self, cancelled: false, text: self.textArea.text)
            }
        }
    }

    @objc func cancelButtonDidTouch() {
        dismissOverlay { _ in
            self.delegate?.promptViewController(self, cancelled: true, text: "")
        }
    }

    public func textViewDidBeginEditing(_ textView: UITextView) {
        if textArea.textColor == UIColor.lightGray {
            textArea.text = nil
            textArea.textColor = UIColor.darkGray
        }
    }

    public func textViewDidChange(_ textView: UITextView) {
        submitButton.isEnabled = !textArea.text.isEmpty
    }

    @objc public func keyboardWillShow(_ notification: Notification) {
        if let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue {

            // Convert the keyboard CGRect to this view controllers coordinate system
            let convertedKeyboardFrame = view.convert(keyboardFrame, from: nil)
            let keyboardHeight = convertedKeyboardFrame.height

            // Animate the overlay up so it is not in the way of keyboard

            let difference = keyboardHeight + CGFloat(Defaults.margin) - overlayView.frame.minY

            if difference > 0 {
                // When compact height animate the bottom constraint to avoid keyboard
                if isCompactHeight {
                    if let bottomConstraint = overlayView.findPositioningConstraint(inParent: view, direction: .up) {
                        view.layoutIfNeeded()
                        bottomConstraint.constant = (keyboardHeight + CGFloat(Defaults.margin))
                        UIView.animate(withDuration: 200, animations: {
                            self.view.layoutIfNeeded()
                        })
                    }
                } else {
                    // When regular height, animate the entire overlay up to avoid keyboard
                    UIView.animate(withDuration: 0.1, animations: {
                        self.overlayView.transform = CGAffineTransform(translationX: 0, y: -1 * difference)
                    })
                }
            }
        }
    }

    @objc public func keyboardWillHide(_ notification: Notification) {
        let bottomConstraint = overlayView.findPositioningConstraint(inParent: view, direction: .up)
        self.view.layoutIfNeeded()
        bottomConstraint?.constant = CGFloat(Defaults.margin)
        UIView.animate(withDuration: 0.1, animations: {
            self.overlayView.transform = CGAffineTransform(translationX: 0, y: 0)
            self.view.layoutIfNeeded()
        })
    }

    @objc public func dismissKeyboard() {
        self.view.endEditing(true)
        if self.textArea.text.isEmpty {
            setPlaceholder(placeholderText)
        }
    }

    // Dynamic setup
    @objc func setPrompt(_ prompt: String) {
        promptText = prompt
        titleLabel.text = Localization.translate(prompt)
    }

    @objc func setSubmitButtonLabel(_ label: String) {
        submitText = label
        let translatedLabel = Localization.translate(label)
        submitButton.setTitle(translatedLabel, for: .normal)
    }

    @objc func setCancelButtonLabel(_ label: String) {
        cancelText = label
        cancelButton.setTitle(Localization.translate(label), for: .normal)
    }

    @objc func setPlaceholder(_ placeholder: String) {
        placeholderText = placeholder
        textArea.text = Localization.translate(placeholder)
        textArea.textColor = UIColor.lightGray
    }

    @objc func localeDidChange() {
        setPrompt(promptText)
        setSubmitButtonLabel(submitText)
        setCancelButtonLabel(cancelText)
        setPlaceholder(placeholderText)
    }
}

open class PromptViewPlugin: Plugin, PromptViewContollerDelegate, LocaleChangedListener {
    @objc let promptViewController = PromptViewController()
    @objc var viewController: UIViewController {
        return promptViewController
    }

    var respond: RPCMethodCallback?

    public required init(address: MessageAddress, messageBus: MessageBus, pluginResolver: PluginResolver, options: JSONObject?) {
        super.init(address: address, messageBus: messageBus, pluginResolver: pluginResolver, options: options)

        promptViewController.delegate = self
        Localization.addLocaleChangedListener(self)

        // Rpc Methods should be automated
        self.addRpcMethodShim("setPrompt") { params, respond in
            if let prompt: String = MethodShimUtils.getArg(params, key: "prompt", respond: respond) {
                self.setPrompt(prompt, respond: respond)
            }
        }

        self.addRpcMethodShim("setSubmitButtonLabel") { params, respond in
            if let label: String = MethodShimUtils.getArg(params, key: "label", respond: respond) {
                self.setSubmitButtonLabel(label, respond: respond)
            }
        }

        self.addRpcMethodShim("setCancelButtonLabel") { params, respond in
            if let label: String = MethodShimUtils.getArg(params, key: "label", respond: respond) {
                self.setCancelButtonLabel(label, respond: respond)
            }
        }

        self.addRpcMethodShim("setPlaceholder") { params, respond in
            if let placeholder: String = MethodShimUtils.getArg(params, key: "placeholder", respond: respond) {
                self.setPlaceholder(placeholder, respond: respond)
            }
        }

        self.addAsyncRpcMethodShim("show") { params, respond in
            if let options: JSONObject? = MethodShimUtils.getOptionalArg(params, key: "options", respond: respond) {
                self.show(options, respond: respond)
            }
        }
    }

    @objc func extractAnimatedOption(_ options: JSONObject?) -> Bool {
        return options?["animated"] as? Bool ?? true
    }

    // @RpcMethod
    func setPrompt(_ prompt: String, respond: RPCMethodCallback) {
        promptViewController.setPrompt(prompt)
    }

    // @RpcMethod
    func setSubmitButtonLabel(_ label: String, respond: RPCMethodCallback) {
        promptViewController.setSubmitButtonLabel(label)
    }

    // @RpcMethod
    func setCancelButtonLabel(_ label: String, respond: RPCMethodCallback) {
        promptViewController.setCancelButtonLabel(label)
    }

    // @RpcMethod
    func setPlaceholder(_ text: String, respond: RPCMethodCallback) {
        promptViewController.setPlaceholder(text)
    }

    // @RpcMethod
    func show(_ options: JSONObject?, respond: @escaping RPCMethodCallback) {
        self.respond = respond
        if let currentViewController = UIApplication.shared.currentViewController {
            promptViewController.animate = extractAnimatedOption(options)
            viewController.modalPresentationStyle = .overCurrentContext
            currentViewController.present(viewController, animated: false, completion: nil)
        }
    }

    private func dismissPromptView() {
        if let currentViewController: PromptViewController = UIApplication.shared.currentViewController as? PromptViewController {
            currentViewController.dismiss(animated: false, completion: nil)
        }
    }

    // Delegate Method
    @objc func promptViewController(_ controller: PromptViewController, cancelled: Bool, text: String) {
        dismissPromptView()
        let result: JSONObject = ["cancelled": cancelled, "text": text]
        respond?(.result(result))
    }

    public func localeDidChange(newLocale: Locale) {
        promptViewController.localeDidChange()
    }
}
