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

import XCTest
@testable import Astro

class TestPlugin: Plugin {
    var receivedMessage: Message?

    override func receive(_ message: Message) {
        receivedMessage = message
    }
}

class TestViewPlugin: Plugin, ViewPlugin {
    @objc let viewController = UIViewController()
}

class TestMessageBus: MessageBus {
    var receivers = [MessageAddress: MessageReceiver]()

    override func listen(on address: MessageAddress, receiver: MessageReceiver) {
        receivers[address] = receiver
    }
}

func doNothingCallback(_: RPCMethodResult) -> Void {}

class TestPluginManager: PluginManager {
    @objc var plugin: Plugin?
    override func pluginInstanceByAddress(_ pluginAddress: MessageAddress) -> Plugin? {
        return plugin
    }
}

class PluginTests: AstroTestCase {
    var pluginManager: PluginManager!
    var testMessageBus: TestMessageBus!

    override func setUp() {
        testMessageBus = TestMessageBus()
        pluginManager = PluginManager(messageBus: testMessageBus, viewController: UIViewController())
        pluginManager.registerPlugin(name: "TestPlugin", type: TestPlugin.self)
    }

    func createPlugin(_ name: String) -> MessageAddress? {
        var pluginAddress: MessageAddress?

        pluginManager.createPlugin(name: "TestPlugin", options: nil) { result in
            if case let .result(address) = result {
                pluginAddress = address as? MessageAddress
            }
        }

        return pluginAddress
    }

    func testCanCreatePlugin() {
        let result = createPlugin("TestPlugin")
        XCTAssertNotNil(result)
    }

    func testPluginIsAssignedThePluginManagersMessageBus() {
        expectAssertion { expectation in
            if let pluginAddress = self.createPlugin("TestPlugin"),
                let plugin: Plugin = self.pluginManager.pluginInstanceByAddress(pluginAddress, respond: doNothingCallback) {
                    XCTAssertTrue(plugin.messageBus === self.pluginManager.messageBus)
                    expectation.fulfill()
            }
        }
    }

    func testPluginAddressesAreUnique() {
        var pluginAddresses = [MessageAddress]()

        for _ in 0...1 {
            let pluginAddress = createPlugin("TestPlugin")
            XCTAssertNotNil(pluginAddress)
            pluginAddresses.append(pluginAddress!)
        }

        XCTAssertNotEqual(pluginAddresses[0], pluginAddresses[1])
    }

    func testThePluginManagerRegistersAPluginWhenCreated() {
        expectAssertion { expectation in
            if let pluginAddress = self.createPlugin("TestPlugin") {
                if let plugin = self.testMessageBus.receivers[pluginAddress] {
                    XCTAssertTrue(plugin is TestPlugin)
                    expectation.fulfill()
                }
            }
        }
    }

    func testAPluginsTriggeredEventsAreSentOnTheBus() {
        let messageBus = MessageBus()
        let plugin = Plugin(address: "address", messageBus: messageBus, pluginResolver: pluginManager, options: nil)
        let receiver = TestReceiver(address: "ignored")

        messageBus.listen(on: plugin.eventAddress, receiver: receiver)

        plugin.trigger("anEvent")

        XCTAssertTrue(receiver.receivedMessage is EventMessage)
    }

    // We don't have a test to verify that a plugin address is prefixed with the plugin
    // name because we treat addresses as opaque identifiers.
}

class PlugInstanceByAddressTests: AstroTestCase {

    var testMessageBus: TestMessageBus!
    var testPluginManager: TestPluginManager!
    var pluginToResolve: Plugin!

    var respondCalled = false
    var error: RPCMethodResult?

    func capturingRespond(_ result: RPCMethodResult) {
        respondCalled = true

        if case .error(_) = result {
            error = result
        }
    }

    override func setUp() {
        respondCalled = false

        testMessageBus = TestMessageBus()
        testPluginManager = TestPluginManager(messageBus: testMessageBus, viewController: UIViewController())
        pluginToResolve = TestPlugin(address: "plugin", messageBus: MessageBus(), pluginResolver: testPluginManager, options: nil)
    }

    func testCanResolveAPluginAddress() {
        testPluginManager.plugin = pluginToResolve
        let resolvedPlugin: Plugin? = testPluginManager.pluginInstanceByAddress("plugin", respond: capturingRespond)

        XCTAssert(resolvedPlugin == pluginToResolve)
    }

    func testDoesNotCallRespondIfSuccessful() {
        testPluginManager.plugin = pluginToResolve
        let _: Plugin? = testPluginManager.pluginInstanceByAddress("plugin", respond: capturingRespond)

        XCTAssert(!respondCalled)
    }

    func testRespondsWithAnErrorIfInstanceIsIncorrectType() {
        testPluginManager.plugin = pluginToResolve
        let resolvedPlugin: ViewPlugin? = testPluginManager.pluginInstanceByAddress("plugin", respond: capturingRespond)

        XCTAssert(resolvedPlugin == nil)
        XCTAssert(error != nil)
    }

    func testRespondsWithAnErrorIfNoInstanceFound() {
        let resolvedPlugin: Plugin? = testPluginManager.pluginInstanceByAddress("plugin", respond: capturingRespond)

        XCTAssert(resolvedPlugin == nil)
        XCTAssert(error != nil)
    }
}
