package com.smartgwt.client.rpc;

import com.smartgwt.client.rpc.RPCCallback;
import com.smartgwt.client.util.SC;

/**
 * The Messaging class provides APIs to make use of SmartGWT Real-Time Messaging module, an
 * optional module available with SmartGWT Power or Enterprise Edition which allows browser based web applications to
 * publish and subscribe to HTTP Messaging channels, allowing the application to receive (and send) messages
 * delivered via server push for "real-time" updates from the server without polling.
 * <P>
 * To use this class, you will need to inherit <code>RealtimeMessaging</code> or 
 * <code>RealtimeMessagingNoScript</code> from the <code>messaging.jar</code>
 * (required for client side functionality only), and include the <code>isomorphic_messaging</code>
 * and <code>isomorphic_js_parser</code> jar files for deployment on the server.
 * <P>
 * See the Messaging Quick Reference PDF file for more information on the optional Messaging module.
 *  
 */
public class Messaging {
    /**
     * Set the url of the messaging servlet. 
     * 
     * @param URL May be prefixed with [ISOMORPHIC] to use the isomorphicDir
     */
	public static native void setMessagingURL(String URL) /*-{
	    if ($wnd.isc.Messaging == null) return;
		$wnd.isc.Messaging.messagingURL = URL;
	}-*/;
	
	/**
	 * Get the URL of the messaging servlet.
	 * @return
	 */
	public static native String getMessagingURL() /*-{
	    if ($wnd.isc.Messaging == null) return null;
		return $wnd.isc.Messaging.messagingURL;
    }-*/;

	/**
	 * Specifies how long we wait for the handshake to the server to complete, before trying again.
	 * @param timeout in ms
	 */
	public static native void setConnectTimeout(int timeout) /*-{
	    if ($wnd.isc.Messaging == null) return;
		$wnd.isc.Messaging.connectTimeout = timeout;
	}-*/;

	public static native int getConnectTimeout() /*-{
	    if ($wnd.isc.Messaging == null) return 0;
		return $wnd.isc.Messaging.connectTimeout;
	}-*/;
	
	/**
	 * Actually send data to a list of channels. Each browser subscribed to the specified channel will be sent this
	 * data from the server in realtime.
	 * <P>
	 * Note that the data is of type Object - typically this will be just a String.
	 * To send a complex data type such as a Map to the server, use the JSOHelper utility to get a
	 * JavaScript equivalent and pass in the JavaScriptObject.
	 * @param channels
	 * @param data
	 * @param callback
	 */
	public static void send (String[] channels, Object data, RPCCallback callback) {
	    checkMessagingLoaded();
		sendJS(channels, data, callback);
	}
	/**
     * Actually send data to a channel. Each browser subscribed to the specified channel will be sent this
     * data from the server in realtime.
     * <P>
     * Note that the data is of type Object - typically this will be just a String.
     * To send a complex data type such as a Map to the server, use the JSOHelper utility to get a
     * JavaScript equivalent and pass in the JavaScriptObject.
     * @param channels
     * @param data
     * @param callback
     */
	public static void send (String channel, Object data, RPCCallback callback) {
		String[] channels = new String[] {channel};
		send(channels, data, callback);
	}
	
	private static native void sendJS(String[] channels, Object data, RPCCallback callback) /*-{
		
		var jsCallback = function (rpcResponse, data, rpcRequest) {
			if (callback == null) return;
			var responseJ = @com.smartgwt.client.rpc.RPCResponse::new(Lcom/google/gwt/core/client/JavaScriptObject;)(rpcResponse);
            var requestJ = @com.smartgwt.client.rpc.RPCRequest::new(Lcom/google/gwt/core/client/JavaScriptObject;)(rpcRequest);
            callback.@com.smartgwt.client.rpc.RPCCallback::execute(Lcom/smartgwt/client/rpc/RPCResponse;Ljava/lang/Object;Lcom/smartgwt/client/rpc/RPCRequest;)(responseJ, data, requestJ);
        };
        
        var jsChannels = @com.smartgwt.client.util.JSOHelper::convertToJavaScriptArray([Ljava/lang/Object;)(channels);
        $wnd.isc.Messaging.send(jsChannels, data, jsCallback);
        
	}-*/;
	

	/**
	 * Returns list of channels that we're currently subscribed to.
	 * @return
	 */
	public static native String[] getSubscribedChannels () /*-{
	    if ($wnd.isc.Messaging == null) return null;
		var channels = $wnd.isc.Messaging.getSubscribedChannels();
		// convert to string array and return
		return @com.smartgwt.client.util.JSOHelper::convertToJavaStringArray(Lcom/google/gwt/core/client/JavaScriptObject;)(channels);
	}-*/;
	
	/**
	 * Call to subscribe to channel.  This will cause a reconnect to the server - for this reason we
	 * defer the actuall reconnect to allow for multiple subscribe() calls in sequence.
	 * @param channel name of the channel we are subscribing to.
	 * @param callback this will execute whenever data is received from the server on this messaging channel.
	 */
	public static void subscribe(String channel, MessagingCallback callback) {
	    checkMessagingLoaded();
	    subscribeJS(channel, callback, null);
	}
	
	/**
	 * Call to subscribe to channel.  This will cause a reconnect to the server - for this reason we
	 * defer the actuall reconnect to allow for multiple subscribe() calls in sequence.
	 * @param channel name of the channel we are subscribing to.
	 * @param callback this will execute whenever data is received from the server on this messaging channel.
	 * @param selector JMS selector used with Queues to filter the messages that arrive to the channel (optional).
	 */
	public static void subscribe(String channel, MessagingCallback callback, String selector) {
	    checkMessagingLoaded();
	    subscribeJS(channel, callback, selector);
	}

	private static native void subscribeJS(String channel, MessagingCallback callback, String selector) /*-{
			
		var jsCallback = function (data) {
			if (callback == null) return;
			callback.@com.smartgwt.client.rpc.MessagingCallback::execute(Ljava/lang/Object;)(data);
        };
        
        $wnd.isc.Messaging.subscribe(channel, jsCallback, null, selector);
        
	}-*/;
	
	/**
	 * call to unsubscribe from channel(s).  This will cause a reconnect to the server - for this reason
	 * we defer the actual reconnect to allow for multiple unsubscribeToChannel() calls in
	 * sequence.
     * @param channel
	 */
	public static native void unsubscribe (String channel) /*-{
	    if ($wnd.isc.Messaging == null) return;
		$wnd.isc.Messaging.unsubscribe(channel);
	}-*/;
	
	/**
	 * Returns true if we are currently connected to any channels.
	 * @return
	 */
	public static native boolean connected () /*-{
	    if ($wnd.isc.Messaging == null) return false;
		var connected = $wnd.isc.Messaging.connected();
		if (connected == null) connected = false;
		return connected;
	 }-*/;
	
	/**
	 * disconnect from all channels
	 */
	public static native void disconnect() /*-{
	    if ($wnd.isc.Messaging == null) return;
		$wnd.isc.Messaging.disconnect();
	}-*/;
	
	// For the major APIs (subscribe / send) we explicitly check for the Messaging components being present and
	// throw an error if they are not.
	private static void checkMessagingLoaded () {
        if (!messagingLoaded()) {
            String errorMessage = "Attempt to use Messaging APIs without the optional Realtime Messaging module. " +
                "Verify that your license includes this optional module and that you have included the necessary " +
                "client and server-side components in your project.";
            SC.logWarn(errorMessage);
            throw new RuntimeException(errorMessage);
        }
    }

    /**
     * Static method indicating whether the optional RealtimeMessaging module is loaded for the page.
     * The Messaging class cannot function without this module being present.
     * @return true if the RealtimeMessaging module is present
     */
    public static boolean messagingLoaded() {
        return SC.hasRealtimeMessaging();
    }


    /**
     * Called when the messaging connection allowing the server to send messages to the client is disconnected.  This can occur
     * either when you explicitly disconnect the connection or if a keepalive packet from the server does not arrive on time.
     * This latter is defined as the {@link com.smartgwt.client.rpc.Messaging#getKeepaliveInterval keepaliveInterval}
     * plus the {@link com.smartgwt.client.rpc.Messaging#getKeepaliveReestablishDelay keepaliveReestablishDelay}. With
     * default settings, a maximum of 4 seconds will elapse between the connection actually going down and you receiving this
     * callback.
     *
     * @param callback MessagingConnectionDownCallback the callback to set.
     */
        public static native void setConnectionDownCallback(MessagingConnectionDownCallback callback) /*-{
        $wnd.isc.Messaging.addClassProperties({
                connectionDown:
                        $entry( function() {
                                if(callback!=null) callback.@com.smartgwt.client.rpc.MessagingConnectionDownCallback::execute()(
                                );
                        })
        });
        }-*/;

    /**
     * Called when the messaging connection allowing the server to send messages to the client is established - whether that's
     * the result of an initial connect() or a re-establishment after it is severed.
     *
     * @param callback MessagingConnectionUpCallback the callback to set.
     */
        public static native void setConnectionUpCallback(MessagingConnectionUpCallback callback) /*-{
        $wnd.isc.Messaging.addClassProperties({
                connectionUp:
                        $entry( function() {
                                if(callback!=null) callback.@com.smartgwt.client.rpc.MessagingConnectionUpCallback::execute()(
                                );
                        })
        });
        }-*/;    
}
