//
//  AstroApplication.swift
//  Astro
//
//  Created by Justin Vaillancourt on 2015-05-25.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

let APPLICATION_ADDRESS: MessageAddress = "AstroApplication:0"

class AstroApplication: BaseMessageReceiver, Addressable {
    @objc static let mainBundle = _mainBundle()
    @objc static let astroBundle = Bundle(for: AstroApplication.self)

    @objc let platformName = "iOS"
    @objc let address = APPLICATION_ADDRESS
    let messageBus: MessageBus
    let pluginResolver: PluginResolver
    @objc let viewController: AstroViewControllerBase

    var mainViewPlugin: ViewPlugin?

    init(viewController: AstroViewControllerBase, messageBus: MessageBus, pluginResolver: PluginResolver) {
        self.viewController = viewController
        self.messageBus = messageBus
        self.pluginResolver = pluginResolver
        super.init()

        self.addRpcMethodShim("restartAstro") { _, _ in
            ////////// This will be autogenerated at some point //////////
            // no-op. Undocumented Android-only operation to demonstrate
            // restarting of the Android AstroActivity. This is necessary
            // for future work around hot-reloading of app.js.
            /////////////////////////////////////////////////////////////
        }

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

        self.addRpcMethodShim("getOSInformation") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.getOSInformation(respond)
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("getAppInformation") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.getAppInformation(respond)
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("openStore") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.openStore(respond)
            /////////////////////////////////////////////////////////////
        }

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

        self.addRpcMethodShim("openLocationSettings") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.openLocationSettings(respond)
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setStatusBarLightText") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.setStatusBarLightText(respond)
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setStatusBarDarkText") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.setStatusBarDarkText(respond)
            /////////////////////////////////////////////////////////////
        }

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

        self.addRpcMethodShim("getStartUri") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.getStartUri(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)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("closeApp") { _, _ in
            ////////// This will be autogenerated at some point //////////
            self.closeApp()
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("dismissLaunchImage") { _, _ in
            ////////// This will be autogenerated at some point //////////
            self.dismissLaunchImage()
            /////////////////////////////////////////////////////////////
        }

        self.messageBus.register(self)
    }

    // @RpcMethod
    func setMainViewPlugin(_ address: MessageAddress, respond: RPCMethodCallback) {
        guard let viewPlugin: ViewPlugin = self.pluginResolver.pluginInstanceByAddress(address, respond: respond) else {
            respond(.error("Plugin with address '\(address)' does not implement `ViewPlugin` protocol and cannot be set as main view plugin."))
            return
        }

        // Remove old main plugin if it's been set.
        if let oldViewPlugin: ViewPlugin = self.mainViewPlugin {
            oldViewPlugin.viewController.view.removeFromSuperview()

            oldViewPlugin.viewController.willMove(toParent: nil)
            oldViewPlugin.viewController.removeFromParent()
        }

        self.viewController.addChild(viewPlugin.viewController)
        viewPlugin.viewController.didMove(toParent: self.viewController)

        // ViewPlugins can only manage the hidden state of the status bar (not style)
        self.viewController.setChildViewControllerForStatusBarHidden(viewPlugin.viewController)

        viewPlugin.viewController.view.translatesAutoresizingMaskIntoConstraints = false

        // HATE this `as? AstroViewController`. Right now tests are forcing us to
        // use the AstroViewControllerBase so that the tests don't have to provide
        // and new up all the AstroViewController's dependencies. Maybe it's time
        // for a protocol?
        if let launchScreenViewController = (viewController as? AstroViewController)?.launchScreen {
            self.viewController.view.insertSubview(viewPlugin.viewController.view,
                                                   belowSubview: launchScreenViewController.view)
        } else {
            self.viewController.view.addSubview(viewPlugin.viewController.view)
        }

        viewPlugin.viewController.view.pinToSuperviewEdges()

        self.mainViewPlugin = viewPlugin
    }

    // @RpcMethod
    func getOSInformation(_ respond: RPCMethodCallback) {
        let osVersion = ProcessInfo.processInfo.operatingSystemVersion
        let info = [
            "os": platformName,
            "version": "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
        ]
        respond(.result(info))
    }

    // @RpcMethod
    func getAppInformation(_ respond: RPCMethodCallback) {
        guard let installationID = UIDevice.current.identifierForVendor?.uuidString else {
            respond(.error("Could not retrieve the identifierForVendor"))
            return
        }

        guard let bundleID = AstroApplication.mainBundle.bundleIdentifier else {
            respond(.error("Could not retrieve the bundleIdentifier"))
            return
        }

        let info = [
            "installationID": installationID,
            "bundleID": bundleID
        ]
        respond(.result(info))
    }

    // @RpcMethod
    func openStore(_ respond: RPCMethodCallback) {
        guard let appID = Bundle.main.infoDictionary?["appStoreID"] as? String else {
            fatalError("Could not retrieve appStoreID from info plist")
        }

        let url = URL(string: "https://itunes.apple.com/app/id\(appID)")!
        UIApplication.shared.openURL(url)
    }

    // @RpcMethod
    func openInBrowser(_ url: MessageAddress, respond: RPCMethodCallback) {
        if let url = URL(string: url) {
            UIApplication.shared.openURL(url)
        } else {
            respond(.error("Given url '\(url)' is invalid."))
        }
    }

    // @RpcMethod
    func openLocationSettings(_ respond: RPCMethodCallback) {
        UIApplication.shared.openURL(URL(string: UIApplication.openSettingsURLString)!)
    }

    // @RpcMethod
    func setStatusBarLightText(_ respond: RPCMethodCallback) {
        viewController.statusBarStyle = .lightContent
    }

    // @RpcMethod
    func setStatusBarDarkText(_ respond: RPCMethodCallback) {
        viewController.statusBarStyle = .default
    }

    // @RpcMethod
    func setStatusBarColor(_ color: String, respond: RPCMethodCallback) {
        // setStatusBarColor is not relevant on iOS
    }

    // @RpcMethod
    func getStartUri(_ respond: RPCMethodCallback) {
        if let uriString = viewController.launchDeepLinkUri?.absoluteString {
            respond(.result(uriString))
            return
        }
        respond(.result(NSNull()))
    }

    // @RpcMethod
    func setBackgroundColor(_ color: String, respond: RPCMethodCallback) {
        if let color = UIColor(hex: color) {
            viewController.view.backgroundColor = color
        } else {
            respond(.error("Given color '\(color)' is invalid."))
        }
    }

    // @RpcMethod
    @objc func closeApp() {
        AstroLog.logger(AstroLog.Application).info("closeApp method is a no-op on iOS.")
    }

    // @RpcMethod
    @objc func dismissLaunchImage() {
        guard let viewController = viewController as? AstroViewController else {
            return
        }

        viewController.dismissLaunchScreen()
    }

    @objc func didReceiveDeeplink(for url: URL) {
        let deepLinkParams = ["uri": url.absoluteString]
        let eventMessage = EventMessage(to: self.eventAddress, from: self.address, eventName: "receivedDeepLink", params: deepLinkParams)
        self.messageBus.send(eventMessage)
    }

    // Triggers an event, exactly like we do in Plugin.swift
    @objc func trigger(_ eventName: String, params: JSONObject = JSONObject()) {
        let eventMessage = EventMessage(to: self.eventAddress, from: self.address, eventName: eventName, params: params)
        messageBus.send(eventMessage)
    }
}
