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

import Foundation

struct WebBridgeUtils {
     enum BridgeMessageResult {
        case result(Message)
        case error(String)
    }

    static func send(_ message: Message, toBridge bridge: WebBridge) {
        let bridgeMessage = message.toBridgeMessage()
        if let owner = bridge.webBridgeDelegate?.webBridgeOwner {
            let resolvedAddress = nativeAddressToJsAddress(bridgeMessage.address, owner: owner)
            let json = JSON.serialize(bridgeMessage.jsonObject)!
            bridge.sendMessage(to: resolvedAddress, data: json)
        }
    }

    static func message(from message: BridgeMessage, owner: Addressable) -> Message? {
        var errorMessages = [String]()

        func valueForKey<T>(_ jsonObject: JSONObject, key: String) -> T? {
            if let value = jsonObject[key] {
                if let value = value as? T {
                    return value
                } else {
                    errorMessages.append("\"\(key)\" is not a \"\(T.self)\"")
                }
            } else {
                errorMessages.append("\"\(key)\" is not present")
            }

            return nil
        }

        let jsonObject = message.jsonObject
        let to = jsAddressToNativeAddress(message.address, owner: owner)

        if let payload: JSONObject = valueForKey(jsonObject, key: "payload") {
            if let messageId: Int = valueForKey(jsonObject, key: "id") {
                if let method: String = valueForKey(payload, key: "method") {
                    if let params: JSONObject = valueForKey(payload, key: "params") {
                        if let isJsRpc: Bool = valueForKey(jsonObject, key: "isJsRpc"), isJsRpc {
                            return JSRPCRequest(to: to, from: owner.address, id: messageId, method: method, params: params)
                        } else {
                            return RPCRequest(to: to, from: owner.address, id: messageId, method: method, params: params)
                        }
                    }
                } else if let result: NSObject = valueForKey(payload, key: "result"), !(result is NSNull) {
                    return RPCResponse(to: to, from: owner.address, id: messageId, rpcMethodResult: RPCMethodResult.result(result))
                } else if let error: String = valueForKey(payload, key: "error") {
                    return RPCResponse(to: to, from: owner.address, id: messageId, rpcMethodResult: RPCMethodResult.error(error))
                }
            } else if let eventName: String = valueForKey(payload, key: "eventName") {
                if let params: JSONObject = valueForKey(payload, key: "params") {
                    return EventMessage(to: to, from: owner.address, eventName: eventName, params: params)
                }
            }
        }

        if errorMessages.count == 0 {
            errorMessages.append("Unknown error")
        }

        let errorMessage = errorMessages.joined(separator: ", ")
        AstroLog.logger(AstroLog.WebAdaptor).error("Error translating BridgeMessage to Message: \(errorMessage).  Ignoring!")

        return nil
    }

    ///
    /// Translate an address originating from a `WebBridge` to a native address
    /// routable by a `MessageBus`. This method will usually be used by a
    /// `protocol<Addressable, WebBridgeDelegate>.
    ///
    /// Example. If `owner` has an address of `"WebViewPlugin:0"` the following
    /// translations will be made:
    ///
    /// ```
    ///    self         ->  WebViewPlugin:0
    ///    self:events  ->  WebViewPlugin:0:events
    /// ```
    ///
    /// - parameter address: The bridge `MessageAddress` to translate
    /// - parameter owner:   `Addressable` used to resolve `"self"` in `address`
    ///
    /// - returns: The translated native `MessageAddress`
    ///
    static func jsAddressToNativeAddress(_ address: MessageAddress, owner: Addressable) -> MessageAddress {
        if address == "self" || address.hasPrefix("self:") {
            var translatedAddress = address
            translatedAddress.replaceSubrange(address.startIndex..<address.index(address.startIndex, offsetBy: "self".count), with: owner.address)
            return translatedAddress
        }

        return address
    }

    ///
    /// Translate an address originating from native to an address usable by a `WebBridge`.
    /// This method will usually be used by an `protocol<Addressable, WebBridgeDelegate>.
    ///
    /// Example. If the `owner` has an address of `WebViewPlugin:0` the following
    /// translations will be made:
    ///
    /// ```
    ///    WebViewPlugin:0         ->  self
    ///    WebViewPlugin:0:events  ->  self:events
    /// ```
    ///
    /// - parameter address: The native `MessageAddress` to translate
    /// - parameter owner:   The address of this `Addressable` will be replaced with `"self"`
    ///
    /// - returns: The translated bridge `MessageAddress`
    ///
    static func nativeAddressToJsAddress(_ address: MessageAddress, owner: Addressable) -> MessageAddress {
        if address == owner.address || address.hasPrefix("\(owner.address):") {
            var translatedAddress = address
            translatedAddress.replaceSubrange(address.startIndex..<address.index(address.startIndex, offsetBy: owner.address.count), with: "self")
            return translatedAddress
        }

        return address
    }
}
