//
//  PluginManager.swift
//  Astro
//
//  Created by Jeremy Wiebe on 2015-04-28.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

public protocol PluginResolver {
    func pluginInstanceByAddress<T>(_ address: MessageAddress, respond: RPCMethodCallback) -> T?
    func remove(instance plugin: Plugin)
}

public protocol PluginRegistrar {
    func registerPlugin(name: String, type: Plugin.Type)
}

class PluginManager: BaseMessageReceiver, Addressable, PluginResolver, PluginRegistrar {
    @objc let viewController: UIViewController
    let messageBus: MessageBus
    @objc let address: MessageAddress = "PluginManager:0"

    @objc var pluginTypes = [String: Plugin.Type]()
    @objc var pluginInstances = [MessageAddress: Plugin]()
    @objc var nextPluginAddress = [String: Int]()

    init(messageBus: MessageBus, viewController: UIViewController) {
        self.messageBus = messageBus
        self.viewController = viewController

        super.init()

        self.addRpcMethodShim("createPlugin") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let pluginName: String = MethodShimUtils.getArg(params, key: "pluginName", respond: respond) {
                if let options: JSONObject? = MethodShimUtils.getOptionalArg(params, key: "options", respond: respond) {
                    self.createPlugin(name: pluginName, options: options, respond: respond)
                }
            }
            /////////////////////////////////////////////////////////////
        }

        self.messageBus.register(self)
    }

    @objc func registerPlugin(name: String, type: Plugin.Type) {
        pluginTypes[name] = type
    }

    private func nextInstanceID(forPlugin name: String) -> Int {
        if nextPluginAddress[name] == nil {
            nextPluginAddress[name] = 0
        }

        let address = nextPluginAddress[name]!
        nextPluginAddress[name]! += 1

        return address
    }

    func createPlugin(name: String, options: JSONObject?, respond: RPCMethodCallback) {
        if let pluginType = pluginTypes[name] {
            if nextPluginAddress[name] == nil {
                nextPluginAddress[name] = 0
            }

            let instanceId = nextInstanceID(forPlugin: name)
            let address = "\(name):\(instanceId)"

            let plugin = pluginType.init(address: address, messageBus: messageBus, pluginResolver: self, options: options)
            messageBus.register(plugin)
            pluginInstances[plugin.address] = plugin

            respond(.result(plugin.address))
        } else {
            let errorMessage = "Plugin with name \"\(name)\" is not registered."
            AstroLog.logger(AstroLog.Plugins).error(errorMessage)
            respond(.error(errorMessage))
        }
    }

    @objc func remove(instance plugin: Plugin) {
        pluginInstances.removeValue(forKey: plugin.address)
        messageBus.deregister(plugin)
    }

    @objc func pluginInstanceByAddress(_ pluginAddress: MessageAddress) -> Plugin? {
        return pluginInstances[pluginAddress]
    }

    func pluginInstanceByAddress<T>(_ address: MessageAddress, respond: RPCMethodCallback) -> T? {
        if let plugin = pluginInstanceByAddress(address) {
            if let plugin = plugin as? T {
                return plugin
            } else {
                respond(.error("Plugin with address '\(address)' is not a \(T.self)."))
            }
        } else {
            respond(.error("Plugin with address '\(address)' does not exist."))
        }
        return nil
    }
}
