/**
 * vtkWeb JavaScript Library.
 *
 * This module allow the Web client to connect to a remote vtkWeb session.
 * The session must already exist and waiting for connections.
 *
 * This module registers itself as: 'vtkweb-connect'
 *
 * @class vtkWeb.connect
 *
 * {@img paraview/ParaViewWeb-simple.png
 *  alt Focus on the communication between the client and the vtkWeb process}
 */
(function (GLOBAL, $) {

    // Connections field used to store a map of the active sessions
    var connections = {}, module = {};

    /*
     * Create a transport object appropriate to the protocol specified
     * in the given url.
     */
    function getTransportObject(url) {
        var idx = url.indexOf(':'),
            protocol = url.substring(0, idx);
        if (protocol === 'ws' || protocol === 'wss') {
            return {
                'type': 'websocket',
                'url': url,
            };
        } else if (protocol === 'http' || protocol === 'https') {
            return {
                'type': 'longpoll',
                'url': url,
                request_timeout: 300000
            };
        } else {
            throw "Unknown protocol (" + protocol + ") for url (" + url + ").  Unable to create transport object.";
        }
    }

    /**
     * @class vtkWeb.Session
     * vtkWeb Session object on which RPC method calls can be made.
     *
     *     session.call("viewport.image.render", request).then( function (reply) {
     *        // Do something with the reply
     *     });
     */
    /**
     * @member vtkWeb.Session
     * @method call
     * Returns a future of the RPC call.
     * @param {String} method
     * Full path method name.
     * @param {Object|String|Number} args
     * Arguments of the RPC call.
     *
     * @return {vtkWeb.Future} of the RPC call
     */
    /**
     * @class vtkWeb.Future
     * Object on which can be attached a callback.
     */
    /**
     * @member vtkWeb.Future
     * @method then
     * @param {Function} callback
     * Function to be called once the RPC called is done. The argument of the
     * function is the response of the RPC method.
     */


    /**
     * Connect to a running vtkWeb session
     *
     * @member vtkWeb.connect
     *
     * @param {vtkWeb.Connection} connection
     * A connection object that should have been generated by the Launcher
     * part if any.
     *
     *      connection = {
     *          sessionURL: "http://localhost:8080/ws"
     *      }
     *
     *      get extended to once the readyCallback get called:
     *
     *      connection = {
     *          sessionURL: "http://localhost:8080/ws",
     *          session: {vtkWeb.Session}
     *      }
     *
     *
     * @param {Function} readyCallback
     * Callback function called when a connection that has been extended with
     * a valid {@link vtkWeb.Session}.
     *
     * @param {Function} closeCallback
     * Callback function called when the session end.
     *
     *      vtkWeb.connect(
     *          connection,
     *          function(connection) {
     *             // Now that we have a valid session let's add a viewport to
     *             // see the 3D view of our vtkWeb pipeline.
     *             var viewport = vtkWeb.createViewport(connection.session);
     *             viewport.bind(".viewport-3d");
     *         },
     *         function(code,reason) {
     *             if (code == ab.CONNECTION_UNSUPPORTED) {
     *                  alert("Connection not supported");
     *              } else {
     *                  console.log(reason);
     *              }
     *          }
     *      );
     */
    function connect(connectionInfo, readyCallback, closeCallback) {
        var uri = connectionInfo.sessionURL,
            onReady = readyCallback,
            onClose = closeCallback,
            uriList = [].concat(uri),
            transports = [];

        if(!connectionInfo.hasOwnProperty("secret")) {
            connectionInfo.secret = "vtkweb-secret"; // Default value
        }

        for (var i = 0; i < uriList.length; i+=1) {
            var url = uriList[i],
                transport = null;
            try {
                transport = getTransportObject(url);
                transports.push(transport);
            } catch (transportCreateError) {
                console.error(transportCreateError);
            }
        }

        connectionInfo.connection = new autobahn.Connection({
            transports: transports,
            realm: "vtkweb",
            authmethods: ["wampcra"],
            authid: "vtkweb",
            onchallenge: function(session, method, extra) {
                if (method === "wampcra") {
                    var secretKey = autobahn.auth_cra.derive_key(connectionInfo.secret, "salt123");
                    return autobahn.auth_cra.sign(secretKey, extra.challenge);
                } else {
                    throw "don't know how to authenticate using '" + method + "'";
                }
            }
        });

        connectionInfo.connection.onopen = function(session, details) {
            try {
                connectionInfo.session = session;
                connections[connectionInfo.sessionURL] = connectionInfo;

                if (onReady) {
                    onReady(connectionInfo);
                }
            } catch(e) {
                console.log(e);
            }
        }

        connectionInfo.connection.onclose = function() {
            delete connections[connectionInfo.sessionURL];
            if (onClose) {
                onClose(arguments[0], arguments[1].reason);
            }
            return false;
        }

        connectionInfo.connection.open();
    }

    /**
     * Create a session that uses only http to make calls to the
     * server-side protocols.
     *
     * @member {object} vtkWeb.connect
     * A json object that only needs a 'sessionURL' string in to know where
     * to send method requests.
     *
     * @param connetionInfo
     *
     * @param {Function} readyCallback
     * Callback function called when a connection that has been extended with
     * a valid session that can be used to make server-side method calls.
     *
     */
    function httpConnect(connectionInfo, readyCallback) {
        connectionInfo.session = {
            'call': function(methodName, args) {
                var dfd = $.Deferred();

                $.ajax({
                    type: 'POST',
                    url: connectionInfo.sessionURL + methodName,
                    dataType: 'json',
                    data: JSON.stringify({ 'args': args }),
                    success: function(result) {
                        dfd.resolve(result);
                    },
                    error: function(error) {
                        dfd.reject(error);
                    }
                });

                return dfd.promise();
            }
        };

        readyCallback(connectionInfo);
    }

    /**
     * Return any existing session for a given connection or Wamp URL
     *
     * @member vtkWeb.connect
     *
     * @param {String} sessionURL
     * The sessionURL String.
     *
     * @return {vtkWeb.Connection} that contains a {@link vtkWeb.Session}
     */
    function getConnection(sessionURL) {
        return connections[sessionURL];
    }

    /**
     * Return all the available connections stored in an Object like follow:
     *
     *     {
     *       "ws://localhost:8080/proxy?sessionId=2345": connection
     *     }
     *
     * {@link vtkWeb.Connection}
     *
     * @member vtkWeb.connect
     */
    function getConnections() {
        return connections;
    }


    // ----------------------------------------------------------------------
    // Init vtkWeb module if needed
    // ----------------------------------------------------------------------
    if (GLOBAL.hasOwnProperty("vtkWeb")) {
        module = GLOBAL.vtkWeb || {};
    } else {
        GLOBAL.vtkWeb = module;
    }

    // ----------------------------------------------------------------------
    // Export methods to the vtkWeb module
    // ----------------------------------------------------------------------
    module.connect = function (connection, ready, close) {
        connect(connection, ready, close);
    };
    module.getConnection = function (connection) {
        return getConnection(connection);
    };
    module.getConnections = function () {
        return getConnections();
    };
    module.httpConnect = function(connection, readyCallback) {
        return httpConnect(connection, readyCallback);
    };

    // ----------------------------------------------------------------------
    // Local module registration
    // ----------------------------------------------------------------------
    try {
      // Tests for presence of autobahn, then registers this module
      if (GLOBAL.autobahn !== undefined) {
        module.registerModule('vtkweb-connect');
      } else {
        console.error('Module failed to register, autobahn is missing');
      }
    } catch(err) {
      console.error('Caught exception while registering module: ' + err.message);
    }

}(window, jQuery));
