﻿/**
 * Copyright 2007 Fox Interactive Media
 * <DISCLAIMER HERE>
 * @fileoverview Interface for container and associated classes; everything needed to query/update MySpace API data via OpenSocial interface.
 * 
 * @author mnewbould [_at_] myspace [_dot_] com (Max Newbould)
 * @author crussell [_at_] myspace [_dot_] com (Chad Russell)
 * @author [Chris Cole] [c c o l e A@T myspace.com]
 */

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the photo
 * @param {Number} photo_id The ID of the photo to fetch
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */
MyOpenSpace.DataRequest.newFetchPhotoRequest = function(id, photo_id, opt_params){
    return opensocial.Container.get().newFetchPhotoRequest(id, photo_id, opt_params);
}

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the photos
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the photos.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */
MyOpenSpace.DataRequest.newFetchPhotosRequest = function(id, photo_id, opt_params){
    return opensocial.Container.get().newFetchPhotosRequest(id, photo_id, opt_params);
}

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the album
 * @param {Number} album_id The ID of the photo to fetch
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */   
MyOpenSpace.DataRequest.newFetchAlbumRequest = function(id, photo_id, opt_params){
    return opensocial.Container.get().newFetchAlbumRequest(id, photo_id, opt_params);
}

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the albums
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the albums.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */
MyOpenSpace.DataRequest.newFetchAlbumsRequest = function(id, photo_id, opt_params){
    return opensocial.Container.get().newFetchAlbumsRequest(id, photo_id, opt_params);
}

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the album
 * @param {Number} video_id The ID of the photo to fetch
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */
MyOpenSpace.DataRequest.newFetchVideoRequest = function(id, photo_id, opt_params){
    return opensocial.Container.get().newFetchVideoRequest(id, photo_id, opt_params);
}

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the albums
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the videos.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */
MyOpenSpace.DataRequest.newFetchVideosRequest = function(id, photo_id, opt_params){
    return opensocial.Container.get().newFetchVideosRequest(id, photo_id, opt_params);
}

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the albums
 * @param {String} key The key of the data to delete, or "*" for all.
 * @return {Object} A request object
 * @static
 * @memberOf MyOpenSpace.DataRequest
 */
MyOpenSpace.DataRequest.newDeletePersonAppDataRequest = function(id, key){
    return opensocial.Container.get().newDeletePersonAppDataRequest(id, key);
}

// temporary alias until these calls are fully deprecated.
opensocial.DataRequest.prototype.newFetchPhotoRequest = MyOpenSpace.DataRequest.newFetchPhotoRequest;
opensocial.DataRequest.prototype.newFetchPhotosRequest = MyOpenSpace.DataRequest.newFetchPhotosRequest;
opensocial.DataRequest.prototype.newFetchAlbumRequest = MyOpenSpace.DataRequest.newFetchAlbumRequest;
opensocial.DataRequest.prototype.newFetchAlbumsRequest = MyOpenSpace.DataRequest.newFetchAlbumsRequest;
opensocial.DataRequest.prototype.newFetchVideoRequest = MyOpenSpace.DataRequest.newFetchVideoRequest;
opensocial.DataRequest.prototype.newFetchVideosRequest = MyOpenSpace.DataRequest.newFetchVideosRequest;
 
/** 
 * @constructor Ajax implentation of a request processor - still very basic : more to flesh here
 * @internal
 * Used by RequestProcessor
 */
if (typeof(MyOpenSpace.Ajax) == "undefined") MyOpenSpace.Ajax = {};

/**
 * Ajax is the XMLHTTP implementation for fetching REST data.
 * HTTP stack has a limit of 2 XHR requests at a time, so we will be operating serially - one for API, one for content
 * Leveraged by RequestProcessor
 * @class
 * @name MyOpenSpace.Ajax
 * @internal
 */
MyOpenSpace.Ajax = {
    /**
     * getConnection will return a valid XMLHttpRequest object, determined by browser capabilities.
     * @function
     * @return {XMLHttpRequest} A valid XMLHttpRequest object as supported by client's browser OR false if no XmlHttpRequest objects were created.
     * @internal
     */
      getConnection: function() {
            myOsTrace("Ajax.getConnection()");
        return Try.these(
                  function() {return new XMLHttpRequest()},
                  function() {return new ActiveXObject("MSXML2.XMLHTTP.3.0")},
                  function() {return new ActiveXObject("Msxml2.XMLHTTP")},
                  function() {return new ActiveXObject("Microsoft.XMLHTTP")}
                  ) || false;
      },
      
      /**
     * connection is the internal connection object for content requests. 
     * not instantiated until getConnection is invoked.
     * @internal
     */
      contentConnection: [],
      /**
       * activeContentConnections stops duplicate url/params/method calls to allow polling mechanisms to only occupy one connection
       * @type Number
     * @internal
       */
       activeContentConnections: [],
      /**
     * openConnections holds the current number of connections in use by the Ajax class.
     * @type Number
     * @internal
     */
      openConnections: 0,
      /**
     * openConnections holds the current number of connections in use by the Ajax class.
     * @type Number
     * @internal
     */
      openContentConnections: 0,
      /**
     * connection is the internal connection object. 
     * not instantiated until getConnection is invoked.
     * @internal
     */
      connection: [],
      
    /**
     * async determines if the Ajax request will be asynchronous or not.
     * @type Boolean
     * @internal
     */
      async: true,
      Completed: [],
      Errored: [],
      content_Completed : [],
      content_Errored : [], 
      
    /**
     * sendRequest will dispatch a request using XmlHttpRequest and execute the completion or error callback upon return.
     * @param {opensocial.DataRequest} dataRequest The DataRequest associated with this call.
     * @param {String} key The unique key name to associate the DataRequest and DataResponse.
     * @param {function} onComplete The function to execute when the call completes.
     * @param {function} onError The function to execute when the call results in an error
     * @param {Boolean} async Sets the mode for XmlHttpRequest; true = asyncronous / false = syncronous
     * @internal
     */
    sendRequest: function(dataRequest, type, onComplete, onError, async, opt_key) {
        myOsTrace("Ajax.sendRequest()");
        var key = opt_key ? opt_key : type;

        this.Completed[key] = onComplete;
        this.Errored[key] = onError;
        if (async == null) async = true;
        var ptr = this;
        this.connection[key] = this.getConnection();
            
        try {
            this.connection[key].open(dataRequest.method, dataRequest.endPoint, async);
                
            this.connection[key].setRequestHeader("X-OpenSocial-Authorization", "OPENSOCIAL opensocial_token=\"" + dataRequest.osToken_ + "\"");
            this.connection[key].setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            this.connection[key].setRequestHeader("Content-length", dataRequest.params.length);
            this.connection[key].setRequestHeader("Connection", "close");
            this.connection[key].onreadystatechange = function () { ptr.readyStateChanged(type, key, opt_key) };

            this.connection[key].send(dataRequest.params);
                
        }
        catch(err) {
            // Permission denied, not implemented, etc
            this.Errored[key]({"errorCode":opensocial.ResponseItem.Error.INTERNAL_ERROR,"errorMessage":err}, opt_key);//TODO: or is this BAD_REQUEST?
        }
    },
    
    /**
     * sendContentRequest will dispatch a request to a third party server using XmlHttpRequest and execute the completion or error callback upon return.
     * @param {String} url The URL to retrieve content from
     * @param {Function} callback The function to call with the data from the URL once it is fetched
     * @param {Map.<String, Object>} params Additional parameters to pass to the request
     *
     * @internal
     */
    sendContentRequest : function (url, onComplete, onError, params) { 
        var block = false;

        for (var i=0; i<this.activeContentConnections.length; i++){
            if (this.activeContentConnections[i].params.pollingKey !== null && params.pollingKey === this.activeContentConnections[i].params.pollingKey) {
                block = true;
                break;
            }
            if (this.activeContentConnections[i].url === url){
                if (this.activeContentConnections[i].params.authType !== params.authType) break;
                if (this.activeContentConnections[i].params.contentType !== params.contentType) break;
                if (this.activeContentConnections[i].params.method !== params.method) break;
                if (this.activeContentConnections[i].params.postData !== params.postData) break;
                if (this.activeContentConnections[i].params.postDataLength !== params.postDataLength) break;
                if (this.activeContentConnections[i].params.headers !== params.headers) break;
                if (this.activeContentConnections[i].params.numEntries !== params.numEntries) break;
                if (this.activeContentConnections[i].params.summariesOnly !== params.summariesOnly) break;

                block = true;
            }
        }
        if (!!block) return;
        
        this.activeContentConnections.push({ url : url, params : params });

        var keyDate = new Date();
        var contentConnectionID = keyDate.getTime(); 

        while (this.contentConnection[contentConnectionID] != null) { contentConnectionID = contentConnectionID + "_"; }
        this.contentConnection[contentConnectionID] = this.getConnection();

        this.content_Completed[contentConnectionID] = onComplete;
        this.content_Errored[contentConnectionID] = onError;
        var ptr = this;
        try {
            this.contentConnection[contentConnectionID].open(params.method, url, true);

            if (params.headers != null){
                for (var i in params.headers){
                    this.contentConnection[contentConnectionID].setRequestHeader(i,params.headers[i]);
                }   
            }

            this.contentConnection[contentConnectionID].setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            this.contentConnection[contentConnectionID].setRequestHeader("Content-Length", params.postDataLength);
            this.contentConnection[contentConnectionID].setRequestHeader("Connection", "close");
            this.contentConnection[contentConnectionID].onreadystatechange = function () { ptr.content_readyStateChanged(url, params, contentConnectionID) };
            this.contentConnection[contentConnectionID].send(params.postData);
        }
        catch(err){
            this.contentConnection[contentConnectionID] = null;
            delete this.contentConnection[contentConnectionID];
            // Permission denied, not implemented, etc
            this.content_Errored({"errorCode":opensocial.ResponseItem.Error.INTERNAL_ERROR,"errorMessage":err});//TODO: or is this BAD_REQUEST?
        }
    },
    
    /**
     * content_readyStateChanged is the event handler for when the XmlHttpRequest's state changes when requesting outside the container API endpoints.
     * @param {String} url The url associated with the content 
     * @internal
     */
    content_readyStateChanged: function(url, params, contentConnectionID) {
        myOsTrace("Ajax.content_readyStateChanged()");
        if (this.contentConnection[contentConnectionID].readyState === 4) {
            var status, statusText;
            try{
                statusText = this.contentConnection[contentConnectionID].statusText;
                status = this.contentConnection[contentConnectionID].status;
            }
            catch(err){
                statusText = "An error occurred.";
                status = 500;
            }
            if (status === 200) {
                for (var i=0; i<this.activeContentConnections.length; i++){
                    if (this.activeContentConnections[i].url === url) {
                        if (this.activeContentConnections[i].params.authType !== params.authType) break;
                        if (this.activeContentConnections[i].params.contentType !== params.contentType) break;
                        if (this.activeContentConnections[i].params.method !== params.method) break;
                        if (this.activeContentConnections[i].params.postData !== params.postData) break;
                        if (this.activeContentConnections[i].params.postDataLength !== params.postDataLength) break;
                        if (this.activeContentConnections[i].params.headers !== params.headers) break;
                        if (this.activeContentConnections[i].params.numEntries !== params.numEntries) break;
                        if (this.activeContentConnections[i].params.summariesOnly !== params.summariesOnly) break;

                        this.activeContentConnections.splice(i,1);
                    }
                }
                      
                var response = {};
                response.responseXML = this.contentConnection[contentConnectionID].responseXML;
                response.responseText = this.contentConnection[contentConnectionID].responseText;
                response.readyState = this.contentConnection[contentConnectionID].readyState;
                response.status = status;
                response.connectionID = contentConnectionID;
                this.contentConnection[contentConnectionID] = null;
                delete this.contentConnection[contentConnectionID];
                this.content_Completed[contentConnectionID](response, url, params);
            }
            else {
                if (status === 401)
                    var error = {"errorCode":opensocial.ResponseItem.Error.UNAUTHORIZED,"errorMessage":statusText}; //TODO: check responseText xml for better error message
                else
                    var error = {"errorCode":opensocial.ResponseItem.Error.INTERNAL_ERROR,"errorMessage":statusText};//TODO: is 404 a BAD_REQUEST?

                this.contentConnection[contentConnectionID] = null;
                delete this.contentConnection[contentConnectionID];
                this.content_Errored[contentConnectionID](error);
            }
        }
    },
    /**
     * readyStateChanged is the event handler for when the XmlHttpRequest's state changes.
     * @param {String} key The unique key name to associate the DataRequest and DataResponse.
     * @internal
     */
      readyStateChanged: function(type, key, opt_key) {
        myOsTrace("Ajax.readyStateChanged()");
        if (this.connection[key].readyState === 4){
            var status, statusText;
            try{
                statusText = this.connection[key].statusText;
                status = this.connection[key].status;
            }
            catch(err){
                statusText = "An error occurred.";
                status = 500;
            }
            if (status === 200){
                var response = {};
                response.responseXML = this.connection[key].responseXML;
                response.responseText = this.connection[key].responseText;

                this.connection[key] = null;
                delete this.connection[key];
                this.Completed[key](response, type, opt_key);
            }
            else{
                if (status === 401)
                    var error = {"errorCode":opensocial.ResponseItem.Error.UNAUTHORIZED,"errorMessage":statusText}; //TODO: check responseText xml for better error message
                else
                    var error = {"errorCode":opensocial.ResponseItem.Error.INTERNAL_ERROR,"errorMessage":statusText};//TODO: is 404 a BAD_REQUEST?
                    
                this.Errored[key](error, opt_key);
            }
        }
    }
};


