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

import Foundation

public class ModalViewController: StatusBarStateViewController {

    var modalBackgroundColorIsSet = false

    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Only change the background colour if it hasn't already been set
        if (modalBackgroundColorIsSet) {
            return
        }

        // Detect the current status bar style and set our background so it contrasts with status bar.
        switch UIApplication.shared.statusBarStyle {
        case .default:
            view.backgroundColor = UIColor.white
        default:
            view.backgroundColor = UIColor.black
        }
    }
}

public class ModalViewPlugin: Plugin {

    // Refactoring suggestion: If we ever extract setBackgroundColor() to another plugin
    // then viewController can be removed and the view from the external plugin
    // can be set directly to contentViewController
    @objc let typedViewController = ModalViewController()
    @objc var viewController: UIViewController {
        return typedViewController
    }
    @objc var contentViewController: UIViewController? {
        willSet {
            if let contentViewController = contentViewController {
                contentViewController.view.removeFromSuperview()

                // these two methods must be called together
                contentViewController.willMove(toParent: nil)
                contentViewController.removeFromParent()
            }
        }
    }

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

        self.addRpcMethodShim("setContentView") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let address: MessageAddress = MethodShimUtils.getArg(params, key: "address", respond: respond) {
                self.setContentView(address, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addAsyncRpcMethodShim("show") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let options: JSONObject? = MethodShimUtils.getOptionalArg(params, key: "options", respond: respond) {
                self.show(options: options, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addAsyncRpcMethodShim("hide") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let options: JSONObject? = MethodShimUtils.getOptionalArg(params, key: "options", respond: respond) {
                self.hide(options: options, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setBackgroundColor") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let color: String = MethodShimUtils.getArg(params, key: "color", respond: respond) {
                self.setBackgroundColor(color, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }
    }

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

    // @RpcMethod
    func setContentView(_ address: MessageAddress, respond: RPCMethodCallback) {
        if let plugin: ViewPlugin = pluginResolver.pluginInstanceByAddress(address, respond: respond) {

            let pluginViewController = plugin.viewController
            contentViewController = pluginViewController

            let pluginView = pluginViewController.view!
            pluginView.translatesAutoresizingMaskIntoConstraints = false

            // these two methods must be called together
            viewController.addChild(pluginViewController)
            pluginViewController.didMove(toParent: viewController)

            viewController.view.addSubview(pluginView)
            pluginView.pinToSuperviewEdges()
        }
    }

    // @RpcMethod
    func show(options: JSONObject?, respond: @escaping RPCMethodCallback) {
        if viewController.view.window != nil || viewController.isBeingPresented {
            respond(.result(false))
            return
        }

        if let currentViewController = UIApplication.shared.currentViewController {
            // JS COORDINATION NECESSARY FOR CORRECT BEHAVIOUR
            // It is possible to hang a modal view plugin if it's show method is called 
            // while another instance is dismissing.
            // currentViewController will point to the dismissing view controller and then 
            // the call to currentViewController.presentViewController below will hang
            // because the instance that is trying to present it is in the middle of being 
            // dismissed.

            // The default modalPresentationStyle results in the eviction of all other views from the view hierarchy
            // when this viewController is presented - which results in degraded webview (app.js) performance. So
            // use OverFullScreen to maintain modal functionality and retain existing view hierarchy underneath
            // OverCurrentContext was used originally but it introduced an unexpected behaviour on dismiss animation for stacked modals:
            // e.g. the lowest modal is brought to the front and dismissed with animation and then the current modal is dismissed without animation
            viewController.modalPresentationStyle = .overFullScreen

            if let statusBarViewController = currentViewController as? StatusBarStateViewController {
                typedViewController.statusBarStyle = statusBarViewController.statusBarStyle
            }

            currentViewController.present(viewController, animated: extractAnimatedOption(options), completion: {
                respond(.result(true))
            })
        } else {
            respond(.result(false))
        }
    }

    // @RpcMethod
    func hide(options: JSONObject?, respond: @escaping RPCMethodCallback) {
        if viewController.view.window == nil || viewController.isBeingDismissed {
            respond(.result(false))
            return
        }

        viewController.presentingViewController?.dismiss(animated: extractAnimatedOption(options), completion: {
            respond(.result(true))
        })
    }

    // @RpcMethod
    func setBackgroundColor(_ color: String, respond: RPCMethodCallback) {
        if let backgroundColor = UIColor(hex: color) {
            viewController.view.backgroundColor = backgroundColor
            typedViewController.modalBackgroundColorIsSet = true
        } else {
            respond(.error("Invalid hex color provided: '\(color)'."))
        }
    }
}
