/**
 * Qik Engine basic API & session classes.
 */

/**
 * Qik Engine API.
 *
 * Abstracting JSON-RPC interface and making all calls
 * Deferred. All errors are passed via Deferred.
 */

QikEngine.API = Class.create({
    /**
     * Constructor.
     *
     * Construct API URL.
     *
     * @param url: basic API url (for example, http://engine.qik.com/api/jsonrpc/)
     * @type url: String
     */

    initialize : function(url, apikey)
    {
        if (!url)
            this.url = document.location.protocol + '//' + document.location.host + '/api/jsonrpc';
        else
            this.url = url;

        if (apikey)
            this.url = this.url + '?apikey=' + apikey;

        this.loading = 0;
    },

    /**
     * Call Qik Engine API method.
     *
     * API result is passed in Deferred callback. Any API Error is passed
     * in errback as Error object with faultCode/faultString filled with information
     * from server.
     *
     * Transport errors (HTTP error codes) are passed as Errors with faultCode==100.
     * Timeout error is faultCode==101.
     *
     * @param method: method name
     * @type method: String
     * @param params: array of parameters for this method
     * @type params: Array
     * @param synchronous: make this request synchronous (by default, requests are asynchronous)
     * @type synchronous: Boolean
     * @param timeout: timeout for method call in seconds (default 1 minute)
     * @type timeout: Integer
     * @param ignore_loading: ignore this query in "loading" signalling
     * @type ignore_loading: Boolean
     * @return: Deferred
     */

    call : function(method, params, synchronous, timeout, ignore_loading)
    {
        var requestBody = $H({ 'method' : method, 'params' : params }).toJSON();
        var d = new Deferred();

        var onSuccess = function(transport)
        {
            result = transport.responseText.evalJSON();
            if ('faultCode' in result && 'faultString' in result)
            {
                if (QikEngine.debug)
                    console.error("API error: call was", method, params, "error is", result);
                var err = new Error(result.faultString);
                err.faultString = result.faultString;
                err.faultCode = result.faultCode;
                if (d.fired == -1)
                    d.errback(err);
            }
            else
            {
                result = result[0];
                if (QikEngine.debug)
                    console.log("Got result: ", result);
                if (d.fired == -1)
                    d.callback(result);
            }
        };

        var onFailure = function(transport)
        {
            var err = new Error("Transport error " + transport.status);
            err.faultString = "Transport error";
            err.faultCode = 100;
            err.status = transport.status;
            if (d.fired == -1)
                d.errback(err)
        };

        var onException = function(transport, error)
        {
            if (transport._aborted)
                return;

            if (d.fired == -1)
                d.errback(error);
        }

        if (QikEngine.debug)
            console.log("Calling api at", this.url, "with", method, params);

        var options = {
                method : 'post',
                postBody : requestBody,
                requestHeaders : { 'Content-Type' : 'application/json' },
                onSuccess : onSuccess,
                onFailure : onFailure,
                onException : onException,
                    };
        if (synchronous)
            options['asynchronous'] = false;

        if (!ignore_loading)
        {
            this.loading++;
            (this.loading > 0 ? QikEngine.showLoading() : QikEngine.hideLoading());

            d.addBoth(function(r) { this.loading--; (this.loading > 0 ? QikEngine.showLoading() : QikEngine.hideLoading()); return r; }.bind(this));
        }

        var request = new Ajax.Request(this.url, options);

        if (!synchronous)
        {
            if (!timeout)
                timeout = 60;

             var abortRequest = function()
             {
                 if (!request._complete && request.transport && request.transport.readyState && [1,2,3].include(request.transport.readyState))
                 {
                     request._aborted = true;
                     request.transport.abort();
                     var err = new Error("Timeout error");
                     err.faultString = "Timeout error";
                     err.faultCode = 101;
                     if (d.fired == -1)
                         d.errback(err);
                 }
             }

            _timeout = window.setTimeout(abortRequest, timeout*1000);
        }

        return d;
    },
});

/**
 * Session class at Qik API side.
 *
 * I abstract session create/destroy. I can
 * be authorized session
 */

QikEngine.Session = Class.create({

    /**
     * Init session object.
     *
     * No session is created at server side with this call.
     *
     * @param api: instance of QikEngine.API for this session. By default, QikEngine.api is used.
     * @type api: QikEngine.API
     */

    initialize : function(api)
    {
        this.id = null;
        if (!api)
            this.api = QikEngine.api;
        else
            this.api = api;
        this.authorized = false;
        this.auth_method = null;
    },

    /**
     * Create this session at server side.
     *
     * When this method completes, .id property is filled with session ID.
     * Hook is put into window.onunload to destroy session.
     *
     * @returns: Deferred, result of operation
     */

    create : function()
    {
        if (this.id)
            return Deferred.succeed(null);

        var gotSession = function(id)
            {
                this.id = id;
                this.authorized = false;
                Event.observe(window, 'unload', this._destroy.bind(this));
            };

        return Deferred.retry(function () { return this.api.call('qik.session.create', []); }.bind(this)).addCallback(gotSession.bind(this));
    },

    /**
     * Destroy session at server side.
     *
     * This method is not intended for shutdown.
     *
     * @returns: Deferred, result of operation
     */

    destroy : function()
    {
        if (!this.id)
            return Deferrred.succeed(null);

        var destroyedSession = function(id)
            {
                this.id = null;
                Event.stopObserving(window, 'unload', this._destroy.bind(this));
            };

        return this.api.call('qik.session.destroy', [this.id]).addCallback(destroyedSession.bind(this));
    },

    /**
     * Destroy session on window shutdown.
     *
     * This makes synchronous API call.
     */

    _destroy : function()
    {
        if (!this.id)
            return;

        this.api.call('qik.session.destroy', [this.id], true);
        this.id = null;
    },

    /**
     * Recreate session and authorize it, if that's required.
     *
     * @return: Deferred successful after session being recreated
     */

    recreate : function()
    {
        this.id = null;
        return this.create().addCallback(function() 
                {
                    if (this.auth_method == "authorize")
                        return this.authorize(this.login, this.password);
                    if (this.auth_method == "authorize_secret_token")
                        return this.authorize_secret_token(this.login, this.secret_token);
                }.bind(this));
    },

    /**
     * Authorize this session.
     *
     * @param login: user credentials
     * @type login: String
     * @param password: user credentials
     * @type password: String
     * @return: Deferred successful after signin
     */

    authorize : function(login, password)
    {
        if (this.authorized)
            throw new Error('Already authorized!');

        return this.api.call('qik.session.authorize', [this.id, login, password]).addCallback(function(user_id)
                {
                    this.authorized = true;
                    this.user_id = user_id;
                    this.auth_method = 'authorize';
                    this.login = login;
                    this.password = password;
                }.bind(this));
    },

    /**
     * Authorize this session via secret-token.
     *
     * @param login: user credentials
     * @type login: String
     * @param secret_token: user secret token
     * @type password: String
     * @return: Deferred successful after signin
     */

    authorize_secret_token : function(login, password)
    {
        if (this.authorized)
            throw new Error('Already authorized!');

        return this.api.call('qik.session.authorize_secret_token', [this.id, login, secret_token]).addCallback(function(user_id)
                {
                    this.authorized = true;
                    this.user_id = user_id;
                    this.auth_method = 'authorize_secret_token';
                    this.login = login;
                    this.secret_token = secret_token;
                }.bind(this));
    },
});
