﻿/**
 * 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@myspace.com (Max Newbould)
 * @author crussell@myspace.com (Chad Russell)
 */
 
if (typeof(MyOpenSpace) === "undefined") MyOpenSpace = { Version: "0.7" };

/**
 * @class
 * @name MyOpenSpace.RequestProcessor_
 * @private
 * @internal
 * MySpace's implementation of a data request manager; linking security, queueing, callbacks, responses, and request actions all together.
 * Created to abstract away from OpenSocial's base objects so MySpace can link in EXTENDED entities/methods more easily.
 * 
 */
if (typeof(MyOpenSpace.RequestProcessor_) == "undefined") MyOpenSpace.RequestProcessor_ = {};

/**
 * Do not instantiate.  Each DataRequest will create its' own RequestProcessor_ and direct work items through it automatically.
 * @private
 * @internal
 * @constructor MyOpenSpace.RequestProcessor_
 */
MyOpenSpace.RequestProcessor_ = function(){
    //this._resultCache = new QuickHash();
    this.executionInterval_ = 200;
    //this.executionModel_ = MyOpenSpace.RequestProcessor_.ExecutionModel_.ASYNC;
    this.executionModel_ = MyOpenSpace.RequestProcessor_.ExecutionModel_.SERIAL;
    //this.executionModel_ = MyOpenSpace.RequestProcessor_.ExecutionModel_.THROTTLED
    this.requestActions_ = null;
    this.TotalWorkItems;
    this.WorkItemsToProcess;
    this.WorkItemsProcessed;
    //this._readyToSend = false;
    this.paused_ = false;
    this.aborted_ = false;
    this.authorizationSchemaSet_ = false;
    
	    
    this.init();
};

/**
 * Specifies how the RequestProcessor will process the queue of work items.
 * @static
 * @class
 * @name MyOpenSpace.RequestProcessor_.ExecutionModel_.
 * @private
 * @internal
 */
MyOpenSpace.RequestProcessor_.ExecutionModel_ = { 
    /**
     * @property {int} SERIAL Executes the queue async one after each other.  Essentially a blocking async.
     * @internal
     */
    SERIAL: "SERIAL",
    /**
     * Not currently supported.
     * @property {int} ASYNC Executes the queue async one after each other, without blocking.
     * @internal
     */
    ASYNC: "ASYNC",
    /**
     * Not currently supported.
     * @property {int} THROTTLED Same as SERIAL, but with a delay between each work item processed.  Use throttleProcessingSpeed(msDelay) to set delay time.
     * @internal
     */
    THROTTLED: "THROTTLED"
}; 

/**
 * @class
 * @name MyOpenSpace.RequestProcessor_
 * @private
 * @internal
 */
MyOpenSpace.RequestProcessor_.prototype = {
     /**
     * Init handles all initialization for the RequestProcessor_.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    init: function() {
        myOsTrace("RequestProcessor_.init()");
        this.workItemPool_ = new this.delayShiftQueue();
    },
    
    /**
     * prepareForSend will setup the queue in preparation for processing the work items.
     * @param {opensocial.DataRequest} ptr Pointer to DataRequest
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    prepareForSend : function(ptr) {
        myOsTrace("RequestProcessor_.prepareForSend()");
        //this._readyToSend = true;
        this.requestActions_ = new opensocial.DataRequest.RequestActions_(ptr);
        //startProcessing();
        //this.workItemPool_.prioritize();
    },
    
     /**
     * setAuthorization sets the authorization template used to verify/set authorizations on each DataRequest's request.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @param {Object} template The authorization template to apply to this DataRequest.
     * @internal
     */
    setAuthorization: function(template) {
        myOsTrace("RequestProcessor_.setAuthorization()");
        this.authTemplate_ = template;
	    this.authorizationSchemaSet_ = true;
	},
	
	 /**
     * addWorkItem will add a work item to the RequestProcessor's queue.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @param {MyOpenSpace.RequestProcessor_.WorkItem} workItem The work item to add to the queue.
     * @internal
     */
    addWorkItem: function(workItem) { // was:addWorkItem: function(workItem, priority) {
        myOsTrace("RequestProcessor_.addWorkItem()");
        //if (!this._readyToSend){
            //TODO: put hasPermission/requestPermission here
            //workItem.priority = priority;
            this.workItemPool_.push(workItem);
            
        //}
    },
    
     /**
     * startProcessing will being executing work items from the queue, as defined by the ExecutionModel_.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    startProcessing: function() {
        myOsTrace("RequestProcessor_.startProcessing()");
        switch (this.executionModel_) {
            case MyOpenSpace.RequestProcessor_.ExecutionModel_.SERIAL: //one after the other
                this.process();
                break;
            case MyOpenSpace.RequestProcessor_.ExecutionModel_.ASYNC: // all async at once
                for (var i=0;i<=this.workItemPool_.size();i++)
                {
                    var workItem = this.workItemPool_.pop();
                    this.process(workItem);
                }
                break;
            case MyOpenSpace.RequestProcessor_.ExecutionModel_.THROTTLED: // one after the other + delay
                this.processWithDelay();
                break;
        }
    },
    
    /**
     * process will execute a workItem
     * @param {MyOpenSpace.RequestProcessor_.WorkItem} workItem to process; if omitted - pop the next work item from queue.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    process: function(workItem) {
        myOsTrace("RequestProcessor_.process()");
        
        if (workItem) {
            this.requestActions_[workItem.type](workItem);
            return;
        }
        
        workItem = this.workItemPool_.pop();
        if(workItem)
            this.requestActions_[workItem.type](workItem);
        
        if(this.executionModel_ === MyOpenSpace.RequestProcessor_.ExecutionModel_.THROTTLED)
            this.processWithDelay();
        //else if (this.workItemPool_.size() > 0 && !this.paused_ && !this.aborted_)
            //this.process(); // queue up next work item immediately
    },
    
    /**
     * processWithDelay will execute a workItem, then set an interval of executionInterval before executing the next work item; so long as pauseProcessing() or abortProcessing() has not be invoked.
     * @param {MyOpenSpace.RequestProcessor_.WorkItem} workItem to process; if omitted - pop the next work item from queue.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    processWithDelay: function() {
        myOsTrace("RequestProcessor_.processWithDelay()");
        //var workItem = this.workItemPool_.pop();
        var pointer = this;
        if (pointer.workItemPool_.size() > 0 && !pointer.paused_ && !pointer.aborted_)
            setTimeout(function () { pointer.process(); }, pointer.executionInterval_); // queue up next work item after executionInterval ms
        //this.requestActions_[workItem.type](workItem);
    },
    
    /**
     * pauseProcessing will pause the execution of SERIAL and THROTTLED execution models, resumeProcessing() will continue execution.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    pauseProcessing: function() {
        myOsTrace("RequestProcessor_.pauseProcessing()");
        this.paused_ = true;
    },
    
    /**
     * abortProcessing will stop the execution of SERIAL and THROTTLED execution models, with no option to recover.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    abortProcessing: function() {
        myOsTrace("RequestProcessor_.abortProcessing()");
        this.aborted_ = true;
    },
    
    /**
     * resumeProcessing will unpause the execution of SERIAL and THROTTLED execution models.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    resumeProcessing: function() {
        myOsTrace("RequestProcessor_.resumeProcessing()");
        this.paused_ = false;
    },
    
    /**
     * throttleProcessingSpeed sets the delay between work item processing in THROTTLED execution model.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    throttleProcessingSpeed: function(msDelay) {
        myOsTrace("RequestProcessor_.throttleProcessingSpeed()");
        this.executionInterval_ = msDelay;
    },
    
    /**
     * delayShiftQueue is a delay shift queue implementation.  Only shifts queue after (len/2) pops to save cycles.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    delayShiftQueue: function(){ 
        var q=new Array();
        var qSpace=0;
        
        /**
         * push will add a work item to the queue
         * @param {MyOpenSpace.RequestProcessor_.WorkItem} element Work item to add to the queue. 
         * @internal
         */
        this.push=function(element){
            q.push(element);
        }
        
        /**
         * pop will return the next work item in the queue.  Queue will be shifted once length/2 calls have been made to this function.
         * @internal
         */
        this.pop=function(){
            if (q.length){
                var element=q[qSpace];
                if (++qSpace*2 >= q.length){
                    for (var i=qSpace;i<q.length;i++) q[i-qSpace]=q[i];
                    q.length-=qSpace;
                    qSpace=0;
                }
                return element;
            } else {
                return undefined;
            }
        }
        
        /**
         * size will return the number of work items in the queue.
         * @internal
         */
        this.size=function(){
            return q.length;
        }
        
        /**
         * prioritize will sort the queue based on priority (critical, high, normal, low)
         * @internal
         */
        this.prioritize=function(){
            q.sort(this.prioritySort_);
        }
        
        /**
         * prioritySort_ is an internal comparer for ordering work items based on priority.
         * @internal
         */
        this.prioritySort_=function(a,b){
            if (a.priority > b.priority) return -1;
            if (a.priority == b.priority) return 0;
            if (a.priority < b.priority) return 1;
        }
   }
}

/**
 * @class
 * @name MyOpenSpace.RequestProcessor_.WorkItem
 * Adds work item properties to a DataRequest for processing within the queue.
 * @internal
 */
if (typeof(MyOpenSpace.RequestProcessor_.WorkItem) == "undefined") MyOpenSpace.RequestProcessor_.WorkItem = {};

/**
 * @constructor MyOpenSpace.RequestProcessor_.WorkItem
 * @param {MyOpenSpace._DataRequest} request The datarequest associated with the work item.
 * @internal
 */
MyOpenSpace.RequestProcessor_.WorkItem = new function(request){
    myOsTrace("RequestProcessor_.WorkItem()");
    this.Request = request;
    this.CreationTime = new Date().getTime(); //ms in epoch time
    this.QueueTime = null;
    this.WorkItemType = null;
    this.ProcessStartTime = null;
    this.ProcessEndTime = null;
    this.Priority = MyOpenSpace.RequestProcessor_.WorkItem.NORMAL;
};

/**
 * Defines level of priority for work items.  Processed in this order: CRITICAL -> HIGH -> NORMAL -> LOW
 * Allows for a queue's priority to be calculated as a sum of all work item's priorities.
 * We may want to add in a dependency object for work items, so one may process first if another depends on it
 * Note: Currently we don't use priorities for anything.
 * @static
 * @class
 * @name Priority
 * @internal
 */
MyOpenSpace.RequestProcessor_.WorkItem.Priority = { 
    /**
     * The integer weight for a low priority item.  Decreases queue priority.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "LOW": -1,
    
    /**
     * The integer weight for a normal priority item.  Does not affect queue priority.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "NORMAL": 0,
    
    /**
     * The integer weight for a high priority item.  Increases queue's priority.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "HIGH": 1,
    
    /**
     * The integer weight for a critical priority item.  Increases queue's priority by twice magnitude of high.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "CRITICAL": 2
};

/**
 * @constructor MyOpenSpace.EndPoint - namespace definition only
 */
if (typeof(MyOpenSpace.EndPoint) == "undefined") MyOpenSpace.EndPoint = {};

/**
 * @class
 * @name MyOpenSpace.EndPoint
 * @internal
 * Represents myspace's api endpoints, which are secured outside and around the container.
 * Tokenization is to be removed once tokens are implemented.
 */
MyOpenSpace.EndPoint = {
    Version: { 
        VERSION1 : "v1" 
    },
    /**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * Top level Domain for api endpoints.
     */
	Server: {
	    /**
	     * @internal
	     * @memberOf MyOpenSpace.EndPoint.Server
	     * Domain used for testing locally.
	     */
		Localhost: "http://localhost/OpenSocial",
		
		/**
	     * @internal
	     * @memberOf MyOpenSpace.EndPoint.Server
	     * Not exactly sure what this is.
	     */
		Local: "http://local-api.myspace.cn",
		
		/**
	     * @internal
	     * @memberOf MyOpenSpace.EndPoint.Server
	     * Domain used for running apps on stage.
	     */
		Stage: "http://stage-api.msappspace.cn",
		
		/**
	     * @internal
	     * @memberOf MyOpenSpace.EndPoint.Server
	     * Domain used for running apps on production.
	     */
		Production: "http://api.msappspace.cn",
		
		/**
	     * @internal
	     * @memberOf MyOpenSpace.EndPoint.Server
	     * Domain used for running apps on the development environment.
	     */
		Development: "http://dev-api.msappspace.cn"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Person
     */
	Person: {
		Viewer: "/opensocial/{VERSION}/{VIEWER}.{FORMAT}", // viewer is viewer id - format is XML/JSON  // new format is /users/viewer.{FORMAT}
		Owner: "/opensocial/{VERSION}/{OWNER}.{FORMAT}" // new format is /users/owner.{FORMAT}
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Profile
     */
	Profile: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/profile.{FORMAT}",
		Owner: "/opensocial/{VERSION}/{OWNER}/profile.{FORMAT}"//,
		//User: "/opensocial/{VERSION}/users/{ID}.{FORMAT}"
    },
    /**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Friends
     */
	Friends: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/friends.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/friends.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for TopFriends
     */
	TopFriends: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/friends.{FORMAT}",
		Owner: "/opensocial/{VERSION}/{OWNER}/friends.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Albums
     */
	Albums: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/albums.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/albums.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Album
     */
	Album: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/albums/{ALBUM_ID}.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/albums/{ALBUM_ID}.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Videos
     */
	Videos: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/videos.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/videos.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Video
     */
	Video: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/videos/{VIDEO_ID}.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/videos/{VIDEO_ID}.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Photos
     */
	Photos: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/photos.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/photos.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Photos from a particular album
     */
	AlbumPhotos: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/albums/{ALBUM_ID}/photos.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/albums/{ALBUM_ID}/photos.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Photo
     */
	Photo: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/photos/{PHOTO_ID}.{FORMAT}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/photos/{PHOTO_ID}.{FORMAT}"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Activities, not in use yet
     */
	Activities: {
		Photos: "/some/path/activities/photos",
		BlogUpdate: "/some/path/activities/blogupdate"
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * RESTFUL endpoint for Data Persistence
     */
	PersonAppData: {
	    Viewer: "/opensocial/{VERSION}/{VIEWER}/appdata{KEYS}",
	    ViewerFriends: "/opensocial/{VERSION}/{VIEWER}/friends/appdata{KEYS}",
	    Owner: "/opensocial/{VERSION}/{OWNER}/appdata{KEYS}",
	    OwnerFriends: "/opensocial/{VERSION}/{OWNER}/friends/appdata{KEYS}",
	    Global: "/opensocial/{VERSION}/appdata/global{KEYS}"	    
	},
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * Tokenized query string containing the token and mode
     */
	AuthorizationQueryString: "opensocial_token={OS_TOKEN}&opensocial_surface={OS_MODE}",
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * Tokenized query string containing detail type used in Person/Profile endpoint
     */
	DetailQueryString: "&detailtype={DETAIL_TYPE}",
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * Tokenized query string containing paging parameters
     */
	PagingQueryString: "&page={PAGE}&page_size={SIZE}",
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * Tokenized query string containing filters: top, all, app, online -- used for Friends and TopFriends endpoints
     */
	FilterQueryString: "&list={FILTER}",
	/**
     * @internal
     * @memberOf MyOpenSpace.EndPoint
     * Tokenized query string containing flags to retrieve more data: online, status, mood
     */
	ShowQueryString: "&show={SHOW}",

    /**
     * @internal
     * @param {MyOpenSpace.EndPoint} endPoint The MyOpenSpace.EndPoint object to populate
     * @param {opensocial.DataRequest.PersonId} userInfo Should be an enum containing VIEWER and OWNER
     * @param {MyOpenSpace.Formats} format Currently being forced to JSON, as opposed to XML
     * @param {MyOpenSpace.OperationModes} operationMode The domain thats being used in this app
     * @param {String} osToken The authorization token for this request
     * @param {opensocial.Surface} osMode The surface the app is being rendered on
     * Inserts tokens into the various tokenized endpoint URIs
     */
	Tokenized: function(endPoint, userInfo, version, format, operationMode, osToken, osMode) {
	    myOsTrace("EndPoint.Tokenized()");
        var server;
	    
	    // if AUTO mode, select actual mode depending on hostname
	    if (operationMode == MyOpenSpace.OperationModes.AUTO) {
	        if (location.hostname.match(/^localhost/)) {
	            operationMode = MyOpenSpace.OperationModes.LOCALHOST;
	       } else if (location.hostname.match(/^local-/)) {
	            operationMode = MyOpenSpace.OperationModes.LOCAL;
	       } else if (location.hostname.match(/^dev-/)) {
	            operationMode = MyOpenSpace.OperationModes.DEVELOPMENT;
	       } else if (location.hostname.match(/^stage-/)) {
	            operationMode = MyOpenSpace.OperationModes.STAGE;
	       } else if (location.hostname.match(/^api./)) {
	            operationMode = MyOpenSpace.OperationModes.PRODUCTION;
	       } else {
	            return null;
	       } 	        
	    }
	    switch (operationMode) {
	        case MyOpenSpace.OperationModes.STAGE:
	            server = endPoint.Server.Stage;
	            break;
	        case MyOpenSpace.OperationModes.PRODUCTION:
	            server = endPoint.Server.Production;
	            break;
	        case MyOpenSpace.OperationModes.LOCALHOST:
	            server = endPoint.Server.Localhost;
	            break;
	        case MyOpenSpace.OperationModes.LOCAL:
	            server = endPoint.Server.Local;
	            break;
	        case MyOpenSpace.OperationModes.DEVELOPMENT:
	            server = endPoint.Server.Development;
	            break;
	    }
	    endPoint.AuthorizationQueryString = endPoint.AuthorizationQueryString.replace("{OS_TOKEN}", osToken).replace("{OS_MODE}", osMode.getName());
		endPoint.Person.Viewer = server + endPoint.Person.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Person.Owner = server + endPoint.Person.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Profile.Viewer = server + endPoint.Profile.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.DetailQueryString;
		endPoint.Profile.Owner = server + endPoint.Profile.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.DetailQueryString;
		//endPoint.Profile.User = server + endPoint.Profile.User.replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Friends.Viewer = server + endPoint.Friends.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString + endPoint.FilterQueryString;
		endPoint.Friends.Owner = server + endPoint.Friends.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString + endPoint.FilterQueryString;
		endPoint.TopFriends.Viewer = server + endPoint.TopFriends.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.FilterQueryString;
		endPoint.TopFriends.Owner = server + endPoint.TopFriends.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.FilterQueryString;
		endPoint.Albums.Viewer = server + endPoint.Albums.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.Albums.Owner = server + endPoint.Albums.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.Album.Viewer = server + endPoint.Album.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Album.Owner = server + endPoint.Album.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Videos.Viewer = server + endPoint.Videos.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.Videos.Owner = server + endPoint.Videos.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.Video.Viewer = server + endPoint.Video.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Video.Owner = server + endPoint.Video.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Photos.Viewer = server + endPoint.Photos.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.Photos.Owner = server + endPoint.Photos.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.Photo.Viewer = server + endPoint.Photo.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.Photo.Owner = server + endPoint.Photo.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.AlbumPhotos.Viewer = server + endPoint.AlbumPhotos.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
		endPoint.AlbumPhotos.Owner = server + endPoint.AlbumPhotos.Owner.replace("{OWNER}", userInfo.OWNER).replace("{FORMAT}", format).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString + endPoint.PagingQueryString;
	    endPoint.PersonAppData.Viewer = server + endPoint.PersonAppData.Viewer.replace("{VIEWER}", userInfo.VIEWER).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.PersonAppData.ViewerFriends = server + endPoint.PersonAppData.ViewerFriends.replace("{VIEWER}", userInfo.VIEWER).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.PersonAppData.Owner = server + endPoint.PersonAppData.Owner.replace("{OWNER}", userInfo.OWNER).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.PersonAppData.OwnerFriends = server + endPoint.PersonAppData.OwnerFriends.replace("{OWNER}", userInfo.OWNER).replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
		endPoint.PersonAppData.Global = server + endPoint.PersonAppData.Global.replace("{VERSION}", version) + "?" + endPoint.AuthorizationQueryString;
	   
	    return endPoint;
	}
};

MyOpenSpace.Name = function (opt_params) {
    opensocial.Name.call(this, opt_params);
    this["setField_"] = function(key,val) { this.fields_[key] = val; }; //TODO: find a better way to extend
}
MyOpenSpace.Name.inherits(opensocial.Name);
/**
 * An extension of opensocial.Person
 * @constructor
 * @private
 * @param {Array&lt;opensocial.Person.Field || MyOpenSpace.Person.Field&gt;} opt_params Contains an Array with opensocial.Person.Field or MyOpenSpace.Person.Field values with which to populate the object
 * @param {Boolean} opt_isOwner Sets the person as the owner
 * @param {Boolean} opt_isOwner Sets the person as the viewer
 */
MyOpenSpace.Person = function(opt_params, opt_isOwner, opt_isViewer) {
    myOsTrace("MyOpenSpace.Person()");
    opensocial.Person.call(this, opt_params, opt_isOwner, opt_isViewer);
    this["setField_"] = function(key,val) { this.fields_[key] = val; }; //TODO: find a better way to extend
    this._type = 0;
}
/**
 * The fields for the extended MyOpenSpace.Person
 * @class
 * @name MyOpenSpace.Person.Field
 * @static
 */
MyOpenSpace.Person.Field = {
    /**
     * A string representing a Person's city of residence.
     * @memberOf MyOpenSpace.Person.Field
     */
    CITY:"CITY",
    /**
     * A string representing a Person's state/province/region of residence.
     * @memberOf MyOpenSpace.Person.Field
     */
    REGION:"REGION",
    /**
     * A string representing a Person's zip/postal code.
     * @memberOf MyOpenSpace.Person.Field
     */
    POSTALCODE:"POSTALCODE",
    /**
     * A string representing a Person's country of residence.
     * @memberOf MyOpenSpace.Person.Field
     */
    COUNTRY:"COUNTRY",
    /**
     * A string representing a Person's city of birth.
     * @memberOf MyOpenSpace.Person.Field
     */
    HOMETOWN:"HOMETOWN",
    /**
     * A number representing a Person's age.
     * @memberOf MyOpenSpace.Person.Field
     */
    //AGE:"AGE",
    /**
     * A string representing a Person's gender.
     * @memberOf MyOpenSpace.Person.Field
     */
    //GENDER:"GENDER",
    /**
     * A string representing non-specific details about a Person.
     * @memberOf MyOpenSpace.Person.Field
     */
    //ABOUT:"ABOUT",
    /**
     * A string representing a Person's locale.
     * @memberOf MyOpenSpace.Person.Field
     */
    CULTURE:"CULTURE",
    /**
     * A string representing a Person's marital status.
     * @memberOf MyOpenSpace.Person.Field
     */
    //MARITAL_STATUS:"MARITAL_STATUS",
    
    //extended
    /**
     * A string representing a Person's non-specific interests.
     * @memberOf MyOpenSpace.Person.Field
     */
    //INTERESTS:"INTERESTS",
    /**
     * A string representing a Person's interests in music.
     * @memberOf MyOpenSpace.Person.Field
     */
    //MUSIC:"MUSIC",
    /**
     * A string representing a Person's interests in movies.
     * @memberOf MyOpenSpace.Person.Field
     */
    //MOVIES:"MOVIES",
    /**
     * A string representing a Person's interests in television.
     * @memberOf MyOpenSpace.Person.Field
     */
    //TELEVISION:"TELEVISION",
    /**
     * A string representing a Person's interests in books.
     * @memberOf MyOpenSpace.Person.Field
     */
    //BOOKS:"BOOKS",
    /**
     * A string representing a Person's heroes.
     * @memberOf MyOpenSpace.Person.Field
     */
    //HEROES:"HEROES",
    /**
     * A string representing a Person's headline or shoutout.
     * @memberOf MyOpenSpace.Person.Field
     */
    HEADLINE:"HEADLINE",
    /**
     * A string representing a Person's current job and job history.
     * @memberOf MyOpenSpace.Person.Field
     */
    OCCUPATION:"OCCUPATION",
    /**
     * A string representing a Person's zodiac sign.
     * @memberOf MyOpenSpace.Person.Field
     */
    ZODIAC_SIGN:"ZODIAC_SIGN",
    /**
     * A string representing a Person's current mood.
     * @memberOf MyOpenSpace.Person.Field
     */
    MOOD:"MOOD",
    /**
     * A string representing a the last tme a Person's mood was updated.
     * @memberOf MyOpenSpace.Person.Field
     */
    MOOD_LAST_UPDATED:"MOOD_LAST_UPDATED",
    /**
     * A string representing a Person's status.
     * @memberOf MyOpenSpace.Person.Field
     */
    //STATUS:"STATUS",
    /**
     * A string representing a list of people the Person would like to meet.
     * @memberOf MyOpenSpace.Person.Field
     */
    DESIRE_TO_MEET:"DESIRE_TO_MEET",
    /**
     * A string representing whether a person is currently online.
     * @memberOf MyOpenSpace.Person.Field
     */
    ONLINE_NOW:"ONLINE_NOW"
}
MyOpenSpace.Person.inherits(opensocial.Person);
MyOpenSpace.Person.Field.AGE = opensocial.Person.Field.AGE;
MyOpenSpace.Person.Field.GENDER = opensocial.Person.Field.GENDER;
MyOpenSpace.Person.Field.ABOUT = opensocial.Person.Field.ABOUT_ME;
MyOpenSpace.Person.Field.MARITAL_STATUS = opensocial.Person.Field.RELATIONSHIP_STATUS;
MyOpenSpace.Person.Field.INTERESTS = opensocial.Person.Field.INTERESTS;
MyOpenSpace.Person.Field.MUSIC = opensocial.Person.Field.MUSIC;
MyOpenSpace.Person.Field.MOVIES = opensocial.Person.Field.MOVIES;
MyOpenSpace.Person.Field.TELEVISION = opensocial.Person.Field.TV_SHOWS;
MyOpenSpace.Person.Field.BOOKS = opensocial.Person.Field.BOOKS;
MyOpenSpace.Person.Field.HEROES = opensocial.Person.Field.HEROES;
MyOpenSpace.Person.Field.STATUS = opensocial.Person.Field.STATUS;

/**
 * Enum for groups of items, extends opensocial.DataRequest.Group
 * @class
 * @name MyOpenSpace.Group
 * @static
 */
MyOpenSpace.Group = {
    /**
     * The idSpec for a viewer album.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_ALBUM:"VIEWER_ALBUM",
    
    /**
     * The idSpec for an owner album.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_ALBUM:"OWNER_ALBUM",
    
    /**
     * The idSpec for viewer albums.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_ALBUMS:"VIEWER_ALBUMS",
    
    /**
     * The idSpec for owner albums.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_ALBUMS:"OWNER_ALBUMS",
    
    /**
     * The idSpec for a viewer video.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_VIDEO:"VIEWER_VIDEO",
    
    /**
     * The idSpec for an owner video.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_VIDEO:"OWNER_VIDEO",
    
    /**
     * The idSpec for viewer videos.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_VIDEOS:"VIEWER_VIDEOS",
    
    /**
     * The idSpec for owner videos.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_VIDEOS:"OWNER_VIDEOS",
    
    /**
     * The idSpec for a viewer photo.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_PHOTO:"VIEWER_PHOTO",
    
    /**
     * The idSpec for an owner photo.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_PHOTO:"OWNER_PHOTO",
    
    /**
     * The idSpec for viewer photos.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_PHOTOS:"VIEWER_PHOTOS",
    
    /**
     * The idSpec for owner photos.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_PHOTOS:"OWNER_PHOTOS",
    
    /**
     * The idSpec for viewer app data.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_APP_DATA:"VIEWER_APP_DATA",
    
    /**
     * The idSpec for owner app data.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_APP_DATA:"OWNER_APP_DATA",
    
    /**
     * The idSpec for viewer friends app data.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_FRIENDS_APP_DATA:"VIEWER_FRIENDS_APP_DATA",
    
    /**
     * The idSpec for owner friends app data.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_FRIENDS_APP_DATA:"OWNER_FRIENDS_APP_DATA",
    
    /**
     * The idSpec for viewer app data.
     * @memberOf MyOpenSpace.Group
     */
    VIEWER_UPDATE_APP_DATA:"VIEWER_UPDATE_APP_DATA",
    
    /**
     * The idSpec for owner app data.
     * @memberOf MyOpenSpace.Group
     */
    OWNER_UPDATE_APP_DATA:"OWNER_UPDATE_APP_DATA",
    
    /**
     * The idSpec for global app data.
     * @memberOf MyOpenSpace.Group
     */
    GLOBAL_APP_DATA:"GLOBAL",
    
    /**
     * The idSpec for global app data.
     * @memberOf MyOpenSpace.Group
     */
    GLOBAL_UPDATE_APP_DATA:"GLOBAL_UPDATE_APP_DATA"
};

/**  
 * @constructor Creates a new DataRequest factory and execution model
 * @param {String} osToken OpenSocial token
 * @param {MyOpenSpace.EndPoint} endPoint MyOpenSpace.EndPoint object for service endpoints
 * @class
 * @name opensocial.DataRequest
 * @private
 * @internal
 * DataRequest is the internal MySpace implementation of an OpenSocial DataRequest
 */
opensocial.DataRequest = function(osToken, endPoint){
	if(osToken!=null){
	myOsTrace("opensocial.DataRequest()");
    //this.init.apply(this, arguments);
	//this.requestProcessor_ = new MyOpenSpace.RequestProcessor_();
	this.osToken_ = osToken;
	this.endPoint_ = endPoint;
	
	this.requestProcessor_ = new MyOpenSpace.RequestProcessor_();	
	this.requestObjects_ = new MyOpenSpace.Hash();
	this.requestObjectCount_ = 0;
	this.busy_ = false;
	this.authTemplate_ = null;
	}
	else
		return opensocial.newDataRequest();
	
};
opensocial.DataRequest.prototype = {
    //maxRequestObjects: 40,
    //setAuthorization: function(template) {
	    //myOsTrace("opensocial.DataRequest.setAuthorization()");
        //opensocial.Container.get().getRequestProcessor().setAuthorization(template);
	//},

	allRequestsCompleteCallback_: function() {},

	getRequestObjects: function() {
		return this.requestObjects_;
	},
	
	add: function(request, opt_key) { // was: add: function(request, priority) {
        myOsTrace("opensocial.DataRequest.add()");
        
        if(!this.busy_){
            //priority = priority || MyOpenSpace.RequestProcessor_.WorkItem.Priority.NORMAL;
            //if(!opt_key) opt_key = request.type + "-" + request.parameters.id;
            opt_key ? request.key = opt_key : request.key = null;
            if("undefined" === typeof(opt_key) || !this.requestObjects_.has(opt_key)){ // check for duplicate entry
                //opensocial.Container.get().getRequestProcessor().addWorkItem(request);
                this.requestProcessor_.addWorkItem(request);
                var id = request.type;
                if(request.parameters){
                    if(request.parameters.id) id += "-" + request.parameters.id;
                    else if(request.parameters.idSpec) id += "-" + request.parameters.idSpec;
                }
                opt_key ? this.requestObjects_.add(opt_key, request) : this.requestObjects_.add(id, request);
                this.requestObjectCount_++;
            }
        }
    },
	send: function(opt_callback) { //TODO: is callback optional? why send a request if there's no callback?
		myOsTrace("opensocial.DataRequest.send()");
		if(!this.busy_ && this.requestObjectCount_ > 0){
            this.busy_ = true;
		    if(opt_callback) this.allRequestsCompleteCallback_ = opt_callback;
            
		    opensocial.Container.get().requestData(this, opt_callback);
		}
	},
	newFetchPersonRequest: function(id, opt_params) {
	    return opensocial.Container.get().newFetchPersonRequest(id, opt_params);
	},
	newFetchPeopleRequest: function(id, opt_params) {
	    return opensocial.Container.get().newFetchPeopleRequest(id, opt_params);
	},
	newFetchGlobalAppDataRequest: function() {
	    return opensocial.Container.get().newFetchGlobalAppDataRequest();
	},
	newFetchInstanceAppDataRequest: function() {
	    return opensocial.Container.get().newFetchInstanceAppDataRequest();
	},
	newUpdateInstanceAppDataRequest: function() {
	    return opensocial.Container.get().newUpdateInstanceAppDataRequest();
	},
	newFetchPersonAppDataRequest: function(id, keys) {
	    return opensocial.Container.get().newFetchPersonAppDataRequest(id, keys);
	},
	newUpdatePersonAppDataRequest: function(id, key, value) {
	    return opensocial.Container.get().newUpdatePersonAppDataRequest(id, key, value);
	},
	newRemovePersonAppDataRequest: function(id, key) {
	    return opensocial.Container.get().newUpdatePersonAppDataRequest(id, key,"");
	},
	newFetchActivitiesRequest: function() {
	    return opensocial.Container.get().newFetchActivitiesRequest();
	}
}

/**
 * @memberOf opensocial.DataRequest
 * @constructor
 * @private
 * @internal
 */
opensocial.DataRequest.RequestActions_ = function(dataRequest){
	this.dataRequest_ = dataRequest;
    
	this.itemsProcessed_ = 0;
	this.dataResponseValues_ = {};
}

/**
 * Does the logic for the requests and sets up callbacks
 * @memberOf opensocial.DataRequest
 * @private
 * @internal
 */
opensocial.DataRequest.RequestActions_.prototype = {
	itemsProcessed_ : 0,
	dataResponseValues_ : {},
	errored_:false,
	"FETCH_PERSON":function FETCH_PERSON(workitem){
		myOsTrace("DataRequest.RequestActions_.FETCH_PERSON");
        var personId = workitem.parameters.id;
		var params;
		var pointer = this;
				
		if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
			    pointer.addResponseItem_(user, opensocial.DataRequest.PersonId.VIEWER, errored, opt_key);
			}
    	    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Profile.Viewer.replace("{DETAIL_TYPE}",workitem.parameters.profileDetail), "FETCH_PROFILE-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, opensocial.DataRequest.PersonId.OWNER, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Profile.Owner.replace("{DETAIL_TYPE}",workitem.parameters.profileDetail), "FETCH_PROFILE-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
		/*else{
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, personId, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Profile.User.replace("{ID}", personId), "FETCH_PROFILE-" + personId, workitem.key);
		}*/
	},
	"FETCH_PHOTO":function FETCH_PHOTO(workitem){
	    myOsTrace("DataRequest.RequestActions_.FETCH_PHOTO");
        var personId = workitem.parameters.id;
	    var pointer = this;
	    
	    if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
				pointer.addResponseItem_(user, MyOpenSpace.Group.VIEWER_PHOTO+"_"+workitem.parameters.photo_id, errored, opt_key);
			}
			this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Photo.Viewer.replace("{PHOTO_ID}", workitem.parameters.photo_id), "FETCH_PHOTO-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, MyOpenSpace.Group.OWNER_PHOTO+"_"+workitem.parameters.photo_id, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Photo.Owner.replace("{PHOTO_ID}", workitem.parameters.photo_id), "FETCH_PHOTO-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
	},
	"FETCH_PHOTOS":function FETCH_PHOTOS(workitem){
	    myOsTrace("DataRequest.RequestActions_.FETCH_PHOTOS");
        var personId = workitem.parameters.id;
        var albumId = workitem.parameters.album_id;
	    var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
	    var pointer = this;
	    
	    if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
				pointer.addResponseItem_(user, MyOpenSpace.Group.VIEWER_PHOTOS, errored, opt_key);
			}
			if(null !== albumId)
			    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.AlbumPhotos.Viewer.replace("{ALBUM_ID}", albumId).replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_PHOTOS-VIEWER", workitem.key);
			else
			    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Photos.Viewer.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_PHOTOS-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, MyOpenSpace.Group.OWNER_PHOTOS, errored, opt_key);
		    }
		    if(null !== albumId)
		        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.AlbumPhotos.Owner.replace("{ALBUM_ID}", albumId).replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_PHOTOS-OWNER", workitem.key);
		    else
		        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Photos.Owner.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_PHOTOS-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
	},
	"FETCH_ALBUM":function FETCH_ALBUM(workitem){
	    myOsTrace("DataRequest.RequestActions_.FETCH_ALBUM");
        var personId = workitem.parameters.id;
	    var pointer = this;
	    
	    if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
				pointer.addResponseItem_(user, MyOpenSpace.Group.VIEWER_ALBUM+"_"+workitem.parameters.album_id, errored, opt_key);
			}
			this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Album.Viewer.replace("{ALBUM_ID}", workitem.parameters.album_id), "FETCH_ALBUM-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, MyOpenSpace.Group.OWNER_ALBUM+"_"+workitem.parameters.album_id, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Album.Owner.replace("{ALBUM_ID}", workitem.parameters.album_id), "FETCH_ALBUM-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
	},
	"FETCH_ALBUMS":function FETCH_ALBUMS(workitem){
	    myOsTrace("DataRequest.RequestActions_.FETCH_ALBUMS");
        var personId = workitem.parameters.id;
	    var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
	    var pointer = this;
	    
	    if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
				pointer.addResponseItem_(user, MyOpenSpace.Group.VIEWER_ALBUMS, errored, opt_key);
			}
			this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Albums.Viewer.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_ALBUMS-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, MyOpenSpace.Group.OWNER_ALBUMS, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Albums.Owner.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_ALBUMS-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
	},
	"FETCH_VIDEO":function FETCH_VIDEO(workitem){
	    myOsTrace("DataRequest.RequestActions_.FETCH_VIDEO");
        var personId = workitem.parameters.id;
	    var pointer = this;
	    
	    if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
				pointer.addResponseItem_(user, MyOpenSpace.Group.VIEWER_VIDEO+"_"+workitem.parameters.video_id, errored, opt_key);
			}
			this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Video.Viewer.replace("{VIDEO_ID}", workitem.parameters.video_id), "FETCH_VIDEO-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, MyOpenSpace.Group.OWNER_VIDEO+"_"+workitem.parameters.video_id, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Video.Owner.replace("{VIDEO_ID}", workitem.parameters.video_id), "FETCH_VIDEO-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
	},
	"FETCH_VIDEOS":function FETCH_VIDEOS(workitem){
	    myOsTrace("DataRequest.RequestActions_.FETCH_VIDEOS");
        var personId = workitem.parameters.id;
	    var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
	    var pointer = this;
	    
	    if (personId === opensocial.DataRequest.PersonId.VIEWER){
			var cb = function(user, errored, opt_key){
				pointer.addResponseItem_(user, MyOpenSpace.Group.VIEWER_VIDEOS, errored, opt_key);
			}
			this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Videos.Viewer.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_VIDEOS-VIEWER", workitem.key);
		}
		else if (personId === opensocial.DataRequest.PersonId.OWNER){
		    var cb = function(user, errored, opt_key){
		        pointer.addResponseItem_(user, MyOpenSpace.Group.OWNER_VIDEOS, errored, opt_key);
		    }
		    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Videos.Owner.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]), "FETCH_VIDEOS-OWNER", workitem.key);
		}
		else
	    {
	        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
	    }
	},
	"FETCH_PEOPLE":function FETCH_PEOPLE(workitem){	  
	    myOsTrace("DataRequest.RequestActions_.FETCH_PEOPLE");
	    
        var personId = ""+ workitem.parameters.idSpec;
		var pointer = this;
		var first = workitem.parameters.first;
		var max = workitem.parameters.max;
		var filter = workitem.parameters.filter;
		var sort = workitem.parameters.sortOrder;
		
		var more_info_token = "";
		if(workitem.parameters.online)
		    more_info_token += MyOpenSpace.EndPoint.ShowQueryString.replace("{SHOW}","online");
        if(workitem.parameters.status)
		    more_info_token += MyOpenSpace.EndPoint.ShowQueryString.replace("{SHOW}","status");
		if(workitem.parameters.mood)
		    more_info_token += MyOpenSpace.EndPoint.ShowQueryString.replace("{SHOW}","mood");
		
		if(opensocial.DataRequest.SortOrder.NAME === sort) {
		    this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":""}, personId, true);
		}
		else{
		    var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
		    var filter_token = "";
		    
		    // we can only have one filter at a time, so if more than one are specified we need to create an order of precedence
		    if(sort){ // take the "top" filter first if it's there
		        if(opensocial.DataRequest.SortOrder.TOP_FRIENDS === sort) filter_token = "top";
		    }
		    else if(filter){
		        if(opensocial.DataRequest.FilterType.HAS_APP === filter) filter_token = "app"; // look for "app" next
		        else if(MyOpenSpace.DataRequest.FilterType.ONLINE_FRIENDS === filter) filter_token = "online"; // look for "online" last
            }
            else
                filter_token = "all";
                		
		    if (personId === opensocial.DataRequest.Group.VIEWER_FRIENDS){
		        var cb = function(user, errored, opt_key){
			        pointer.addResponseItem_(user, opensocial.DataRequest.Group.VIEWER_FRIENDS, errored, opt_key);
		        }
		        if("top" === filter_token)
                    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.TopFriends.Viewer.replace("{FILTER}",filter_token) + more_info_token,"FETCH_PEOPLE-VIEWER", workitem.key);
                else
                    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Friends.Viewer.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]).replace("{FILTER}",filter_token) + more_info_token,"FETCH_PEOPLE-VIEWER", workitem.key);
		    }
		    else if (personId === opensocial.DataRequest.Group.OWNER_FRIENDS){
		        var cb = function(user, errored, opt_key){
		            pointer.addResponseItem_(user, opensocial.DataRequest.Group.OWNER_FRIENDS, errored, opt_key);
		        }
		        if("top" === filter_token)
		            this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.TopFriends.Owner.replace("{FILTER}",filter_token) + more_info_token,"FETCH_PEOPLE-OWNER", workitem.key);
		        else
                    this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.Friends.Owner.replace("{PAGE}",paging[0]).replace("{SIZE}",paging[1]).replace("{FILTER}",filter_token) + more_info_token,"FETCH_PEOPLE-OWNER", workitem.key);
		    }
		    else
		    {
		        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"}, workitem.key, true);
		    }
		}
	},
	"FETCH_GLOBAL_DATA":function FETCH_GLOBAL_DATA(){
		myOsTrace("DataRequest.RequestActions_.FETCH_GLOBAL_DATA");
        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":""},"FETCH_GLOBAL_DATA",true);
	},
	"FETCH_INSTANCE_DATA":function FETCH_INSTANCE_DATA(){
		myOsTrace("DataRequest.RequestActions_.FETCH_INSTANCE_DATA");
        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":""},"FETCH_INSTANCE_DATA",true);
	},
	"UPDATE_INSTANCE_DATA":function UPDATE_INSTANCE_DATA(){
		myOsTrace("DataRequest.RequestActions_.UPDATE_INSTANCE_DATA");
        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":""},"UPDATE_INSTANCE_DATA",true);
	},
	"FETCH_PERSON_DATA":function FETCH_PERSON_DATA(workitem){
		myOsTrace("DataRequest.RequestActions_.FETCH_PERSON_DATA");
		var idSpec = workitem.parameters.idSpec;
		var keys = workitem.parameters.keys || "";
		if (keys === "*") keys = "";
		if (keys.length>0) keys="/"+keys;
		var pointer = this;

		if (idSpec === opensocial.DataRequest.PersonId.VIEWER){
            var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.VIEWER_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.Viewer.replace("{KEYS}", keys), "FETCH_PERSON_DATA-VIEWER", workitem.key);
		}
		else if (idSpec === opensocial.DataRequest.Group.VIEWER_FRIENDS){
	        var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.VIEWER_FRIENDS_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.ViewerFriends.replace("{KEYS}", keys), "FETCH_PERSON_DATA-VIEWER_FRIENDS", workitem.key);
		}
		else if (idSpec === opensocial.DataRequest.PersonId.OWNER){
	        var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.OWNER_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.Owner.replace("{KEYS}", keys), "FETCH_PERSON_DATA-OWNER", workitem.key);
		}
		else if (idSpec === opensocial.DataRequest.Group.OWNER_FRIENDS){
	        var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.OWNER_FRIENDS_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.OwnerFriends.replace("{KEYS}", keys), "FETCH_PERSON_DATA-OWNER_FRIENDS", workitem.key);
		}
		else if (idSpec === MyOpenSpace.Group.GLOBAL_APP_DATA){
	        var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.GLOBAL_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.Global.replace("{KEYS}", keys), "FETCH_GLOBAL_APP_DATA", workitem.key);
		}
		else{
		    this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"Unsupported idSpec"},workitem.key,true);
		}
	},
	"UPDATE_PERSON_DATA":function UPDATE_PERSON_DATA(workitem){
		myOsTrace("DataRequest.RequestActions_.UPDATE_PERSON_DATA");
        if (!validateAppDataKeyName(workitem.parameters.key)) { // bad key name
		    this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.BAD_REQUEST,"errorMessage":"AppData key names can only consist of alphanumerics, dots, dashes and underscores."},workitem.key,true);
		    return;
		}
		
        var idSpec = workitem.parameters.id;
		var pointer = this;
		
		this.dataRequest_.params = workitem.parameters.key + "=" + workitem.parameters.value;
		if (idSpec === opensocial.DataRequest.PersonId.VIEWER){
	        var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.VIEWER_UPDATE_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.Viewer.replace("{KEYS}", ""), "UPDATE_PERSON_DATA-VIEWER", workitem.key);
		} 
		else if (idSpec === MyOpenSpace.Group.GLOBAL_APP_DATA){
	        var cb = function(personData, errored, opt_key){
		        pointer.addResponseItem_(personData, MyOpenSpace.Group.GLOBAL_UPDATE_APP_DATA, errored, opt_key);
	        }
	        this.invoke_(this.dataRequest_, cb, this.dataRequest_.endPoint_.PersonAppData.Global.replace("{KEYS}", ""), "UPDATE_PERSON_DATA-GLOBAL", workitem.key);
		}
		else {
		    this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":"Unsupported idSpec"},workitem.key,true);
		}
		
	},
	"FETCH_ACTIVITIES":function FETCH_ACTIVITIES(){
		myOsTrace("DataRequest.RequestActions_.FETCH_ACTIVITIES");
        this.addResponseItem_({"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":""},"FETCH_ACTIVITIES",true);
	},
	mapPagingParams_: function(first, max){
	    myOsTrace("DataRequest.RequestActions_.mapPagingParams_");
        if(first === null || typeof(first) === "undefined" || first < 1) first = 1;
	    if(max === null || typeof(max) === "undefined" || max < 1 || max > MyOpenSpace.DefaultPageSize) max = MyOpenSpace.DefaultPageSize;
	    
	    var page_size = max;
	    var page = Math.floor(first / page_size) + 1; // page is not 0 indexed, it starts at 1
	    
	    return [page,page_size];
	},
	invoke_: function(dataRequestAction, callback, url, type, opt_key){//params is optional
        myOsTrace("DataRequest.RequestActions_.invoke_");
        var method = (0===type.indexOf("UPDATE")) ? "PUT" : "GET";
        dataRequestAction.method = method;
        dataRequestAction.endPoint = url;
        if (method !== "PUT") {
            dataRequestAction.params = {};
        }
        MyOpenSpace.Ajax.sendRequest(dataRequestAction, type, completed_, errored_, true, opt_key);
		function completed_(response, type, opt_key){
			var error = null;
			var result = null;
			if (0===type.indexOf("UPDATE_PERSON_DATA")) {
			    callback(null, false, opt_key);
			    return;
			}
			
			var map = new MyOpenSpace.DataMapper_();
			if(0===type.indexOf("FETCH_GLOBAL_APP_DATA"))
	            result = map.mapData[MyOpenSpace.DataMapper_.Field.GLOBAL_APP_DATA](response);
			else if(0===type.indexOf("FETCH_PERSON_DATA"))
	            result = map.mapData[MyOpenSpace.DataMapper_.Field.PERSON_APP_DATA](response);
            else if(0===type.indexOf("FETCH_PERSON"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.PERSON](response);
            else if(0===type.indexOf("FETCH_PEOPLE"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.FRIENDS](response);
            else if(0===type.indexOf("FETCH_PROFILE")){
                if(type.indexOf("VIEWER") >= 0) result = map.mapData[MyOpenSpace.DataMapper_.Field.PERSON](response, false, true);
                else if(type.indexOf("OWNER") >= 0) result = map.mapData[MyOpenSpace.DataMapper_.Field.PERSON](response, true, false);
                else result = map.mapData[MyOpenSpace.DataMapper_.Field.PERSON](response, false, false);
            }
            else if(0===type.indexOf("FETCH_ALBUMS"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.ALBUMS](response);
            else if(0===type.indexOf("FETCH_ALBUM"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.ALBUM](response);
            else if(0===type.indexOf("FETCH_VIDEOS"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.VIDEOS](response);
            else if(0===type.indexOf("FETCH_VIDEO"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.VIDEO](response);
            else if(0===type.indexOf("FETCH_PHOTOS"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.PHOTOS](response);
            else if(0===type.indexOf("FETCH_PHOTO"))
                result = map.mapData[MyOpenSpace.DataMapper_.Field.PHOTO](response);
	        
	        if(null === result) {
	            callback({"errorCode":opensocial.ResponseItem.Error.INTERNAL_ERROR, "errorMessage":"Unable to map entity"}, true, opt_key);
	        } else {
			    callback(result, false, opt_key); //callback:pointer.addResponseItem_(user, 'VIEWER_FRIENDS');
			}
		}
		
		function errored_(response){
		    callback(response, true);
		}
		
	},
	addResponseItem_: function(item, type, errored, opt_key){
	    myOsTrace("DataRequest.RequestActions_.addResponseItem_");
        var ri;
		if(errored) {
		    //this._addError(item,key);
		    ri = opensocial.Container.get().newResponseItem(this.dataRequest_, null, item.errorCode, item.errorMessage);// TODO: what to send in for data?
		    this.errored_ = true;
		}
		else
		    ri = opensocial.Container.get().newResponseItem(this.dataRequest_, item, "", "");
		
		opt_key ? this.dataResponseValues_[opt_key] = ri : this.dataResponseValues_[type] = ri;
		this.itemsProcessed_++;

		//check to see if we're all done		
		if(this.itemsProcessed_ === this.dataRequest_.requestObjectCount_){

		    this.dataRequest_.requestObjectCount_ = 0;
		    this.dataRequest_.requestObjects_ = new MyOpenSpace.Hash()
		    this.dataRequest_.busy_ = false;
		    this.dataRequest_.allRequestsCompleteCallback_(opensocial.Container.get().newDataResponse(this.dataResponseValues_, this.errored_));
		}
		else if(this.dataRequest_.requestProcessor_.executionModel_ === MyOpenSpace.RequestProcessor_.ExecutionModel_.SERIAL)
		    this.dataRequest_.requestProcessor_.startProcessing();
	}
};

/**
 * Enumerates the types of requests that can be made
 * @static
 * @class
 * @name MyOpenSpace.RequestType
 */
MyOpenSpace.RequestType = {
    /**
     * Defines an enum for a fetch person request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_PERSON:"FETCH_PERSON",
    /**
     * Defines an enum for a fetch people request.
     * @memberOf MyOpenSpace.RequestType
     */
	FETCH_PEOPLE:"FETCH_PEOPLE",
	/**
     * Defines an enum for a fetch global data request.
     * @memberOf MyOpenSpace.RequestType
     */
	FETCH_GLOBAL_DATA:"FETCH_GLOBAL_DATA",
	/**
     * Defines an enum for a fetch instance data request.
     * @memberOf MyOpenSpace.RequestType
     */
	FETCH_INSTANCE_DATA:"FETCH_INSTANCE_DATA",
	/**
     * Defines an enum for an update instance data request.
     * @memberOf MyOpenSpace.RequestType
     */
	UPDATE_INSTANCE_DATA:"UPDATE_INSTANCE_DATA",
	/**
     * Defines an enum for a fetch person data request.
     * @memberOf MyOpenSpace.RequestType
     */
	FETCH_PERSON_DATA:"FETCH_PERSON_DATA",
	/**
     * Defines an enum for an update person data request.
     * @memberOf MyOpenSpace.RequestType
     */
	UPDATE_PERSON_DATA:"UPDATE_PERSON_DATA",
	/**
     * Defines an enum for a fetch activities request.
     * @memberOf MyOpenSpace.RequestType
     */
	FETCH_ACTIVITIES:"FETCH_ACTIVITIES",
	/**
     * Defines an enum for a fetch albums request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_ALBUMS:"FETCH_ALBUMS",
    /**
     * Defines an enum for a fetch album request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_ALBUM:"FETCH_ALBUM",
    /**
     * Defines an enum for a fetch videos request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_VIDEOS:"FETCH_VIDEOS",
    /**
     * Defines an enum for a fetch video request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_VIDEO:"FETCH_VIDEO",
    /**
     * Defines an enum for a fetch photos request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_PHOTOS:"FETCH_PHOTOS",
    /**
     * Defines an enum for a fetch photo request.
     * @memberOf MyOpenSpace.RequestType
     */
    FETCH_PHOTO:"FETCH_PHOTO"
};

opensocial.DataRequest.PersonId = {
	OWNER:"OWNER",
	VIEWER:"VIEWER"
};

opensocial.DataRequest.Group = {
	OWNER_FRIENDS:"OWNER_FRIENDS",
	VIEWER_FRIENDS:"VIEWER_FRIENDS"
};

opensocial.DataRequest.SortOrder = {
	TOP_FRIENDS:"TOP_FRIENDS",
	NAME:"NAME"
};
opensocial.DataRequest.FilterType = {

	ALL:"ALL",
	HAS_APP:"HAS_APP"
};

opensocial.DataRequest.PeopleRequestFields = {
	PROFILE_DETAILS:"PROFILE_DETAILS",
	SORT_ORDER:"SORT_ORDER",
	FILTER:"FILTER",
	FIRST:"FIRST", //OpenSocial paging implementation - we might want to use a generator object instead? (index of first item when paginated)
	MAX:"MAX" //OpenSocial paging implementation (maximum items per page)
};

opensocial.DataRequest.ActivityRequestFields = {
	APP_ID:"APP_ID",
	FOLDER_ID:"FOLDER_ID"
};

/**
 * Just a place-holder in order to extend MyOpenSpace.View.Field
 * @ignore
 */
MyOpenSpace.View = {};

/**
 * Enumerates the types of surfaces supported
 * @static
 * @class
 * @name MyOpenSpace.View.Field
 */
MyOpenSpace.View.Field = {
    /**
     * The canvas.
     * @memberOf MyOpenSpace.View.Field
     */
    CANVAS:"canvas",
    /**
     * The left column on a profile page.
     * @memberOf MyOpenSpace.View.Field
     */
    PROFILE_LEFT:"profile.left",
    /**
     * The right column on a profile page.
     * @memberOf MyOpenSpace.View.Field
     */
    PROFILE_RIGHT:"profile.right",
    /**
     * Used for passing into requestNavigateTo
     * @memberOf MyOpenSpace.View.Field
     * @internal
     */
    PROFILE:"profile",
    /**
     * The home page.
     * @memberOf MyOpenSpace.View.Field
     */
    HOME:"home",
    /**
     * The default view.
     * @memberOf MyOpenSpace.View.Field
     */
    DEFAULT:"canvas"
};

/**
 * MySpace extension of the opensocial.Environment object.
 * @constructor
 * @param {Array<string>} supportedPostToTargets A string array of supported PostTo targets
 * @class
 * @name MyOpenSpace.Environment
 * @private
 * @internal
 */
MyOpenSpace.Environment = function(supportedPostToTargets){
    this.supportedPostToTargets = supportedPostToTargets;
};

/**
 * Accessor for the supportedPostToTargets array
 * @memberOf MyOpenSpace.Environment
 * @private
 */
MyOpenSpace.Environment.prototype.getSupportedPostToTargets = function(){
    return this.supportedPostToTargets;
};

/**
 * @static
 * @class
 * @name MyOpenSpace.Environment.ObjectType
 * The types of extended objects in this container.
 */
MyOpenSpace.Environment.ObjectType = {
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    VIDEO : "VIDEO",
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    PHOTO : "PHOTO",
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    ALBUM : "ALBUM",
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    PERSON : "PERSON"
};

/**
 * The MySpace container class, extends opensocial.Container.  Use opensocial.Container.get() to retrieve this singleton.
 * @constructor
 */
MyOpenSpace.MySpaceContainer = function() {
    myOsTrace("MyOpenSpace.MySpaceContainer");
    gadgets.util.getUrlParameters().views = gadgets.util.getUrlParameters().opensocial_surface;//HACK ALERT
    var config = {};
    var supported_views = {};
    supported_views["default"] = new gadgets.views.View(MyOpenSpace.View.Field.DEFAULT, true);
    supported_views[MyOpenSpace.View.Field.CANVAS] = new gadgets.views.View(MyOpenSpace.View.Field.CANVAS, true);
    supported_views[MyOpenSpace.View.Field.PROFILE_LEFT] = new gadgets.views.View(MyOpenSpace.View.Field.PROFILE_LEFT, false);
    supported_views[MyOpenSpace.View.Field.PROFILE_RIGHT] = new gadgets.views.View(MyOpenSpace.View.Field.PROFILE_RIGHT, false);
    supported_views[MyOpenSpace.View.Field.HOME] = new gadgets.views.View(MyOpenSpace.View.Field.HOME, false);
    config["views"] = supported_views;
    gadgets.config.init(config);
    
    this.osMode_ = gadgets.views.getCurrentView();
    this.osToken_ = gadgets.util.getUrlParameters().opensocial_token;
    this.params_ = {};
    
    var supportedPostToTargets = "";
    if(gadgets.util.getUrlParameters() && gadgets.util.getUrlParameters().pto)
        supportedPostToTargets = gadgets.util.getUrlParameters().pto.split(",");
    
    var supportedPersonFields = MyOpenSpace.Person.Field;
    supportedPersonFields[opensocial.Person.Field.ID] = true;
    supportedPersonFields[opensocial.Person.Field.NAME] = true;
    supportedPersonFields[opensocial.Person.Field.THUMBNAIL_URL] = true;
    supportedPersonFields[opensocial.Person.Field.PROFILE_URL] = true;
    supportedPersonFields[opensocial.Person.Field.ABOUT_ME] = true;
    supportedPersonFields[opensocial.Person.Field.AGE] = true;
    supportedPersonFields[opensocial.Person.Field.BOOKS] = true;
    supportedPersonFields[opensocial.Person.Field.GENDER] = true;
    supportedPersonFields[opensocial.Person.Field.HEROES] = true;
    supportedPersonFields[opensocial.Person.Field.INTERESTS] = true;
    supportedPersonFields[opensocial.Person.Field.RELATIONSHIP_STATUS] = true;
    supportedPersonFields[opensocial.Person.Field.MOVIES] = true;
    supportedPersonFields[opensocial.Person.Field.MUSIC] = true;
    supportedPersonFields[opensocial.Person.Field.STATUS] = true;
    supportedPersonFields[opensocial.Person.Field.TV_SHOWS] = true;
    
    var supportedFields = {};
    supportedFields[opensocial.Environment.ObjectType.PERSON] = supportedPersonFields;
    supportedFields[MyOpenSpace.Environment.ObjectType.PERSON] = supportedPersonFields;
    supportedFields[MyOpenSpace.Environment.ObjectType.VIDEO] = MyOpenSpace.Video.Field;
    supportedFields[MyOpenSpace.Environment.ObjectType.ALBUM] = MyOpenSpace.Album.Field;
    supportedFields[MyOpenSpace.Environment.ObjectType.PHOTO] = MyOpenSpace.Photo.Field;
        
    this.environment_ = this.newEnvironment("myspace.cn", supportedFields);
    this.myspaceenvironment_ = this.newMySpaceEnvironment(supportedPostToTargets);
    this.endPoint_ = MyOpenSpace.EndPoint.Tokenized(MyOpenSpace.EndPoint, opensocial.DataRequest.PersonId, MyOpenSpace.EndPoint.Version.VERSION1, MyOpenSpace.Formats.JSON, MyOpenSpace.OperationModes.AUTO, this.osToken_, this.osMode_);
//    this.requestProcessor_ = new MyOpenSpace.RequestProcessor_();
    /* Expose as property for now */
    MyOpenSpace.MySpaceContainer.OSToken = this.osToken_;
    
    opensocial.Container.call(this, false);
    opensocial.Container.setContainer(this);
    //opensocial.Container.get().enableCaja(); // this is called via the AppRenderer now.
}

MyOpenSpace.MySpaceContainer.inherits(opensocial.Container);


/**
 * makeRequest implementation using proxy
 * Fetches content from the provided URL and feeds that content into the callback function
 * @param {String} url The URL where content is located
 * @param {Function} callback The function to call with the data returned from url
 * @param {gadgets.io.RequestParameters} Additional parameters to pass to the request
 * @internal
 */
gadgets.io.makeRequest = function(url, callback, opt_params) {
    opt_params = opt_params || {};
    var params = {};
    
    
    params.authType = opt_params[gadgets.io.RequestParameters.AUTHORIZATION] || gadgets.io.AuthorizationType.NONE;
    params.contentType = opt_params[gadgets.io.RequestParameters.CONTENT_TYPE] || gadgets.io.ContentType.TEXT;
    params.method = opt_params[gadgets.io.RequestParameters.METHOD] || gadgets.io.MethodType.GET;
    params.postData = opt_params[gadgets.io.RequestParameters.POST_DATA] || null;
    
    if (typeof(params.postData) != "string") params.postData = encodeValues(params.postData); 
    
    params.postDataLength = (params.postData != null && params.postData.length > 0) ? params.postData.length : 0;
    params.headers = opt_params[gadgets.io.RequestParameters.HEADERS] || null;
    params.numEntries = opt_params[gadgets.io.RequestParameters.NUM_ENTRIES] || 3;
    params.summariesOnly = opt_params[gadgets.io.RequestParameters.GET_SUMMARIES] || false;
    params.pollingKey = opt_params[MyOpenSpace.RequestParameters.USE_KEY_FOR_POLLING] || null;
    
    
    var relay_url = "/proxy/relay.proxy?opensocial_token=" + MyOpenSpace.MySpaceContainer.OSToken + "&opensocial_url=" + escape(url);
    if (params.authType === gadgets.io.AuthorizationType.SIGNED) relay_url = relay_url.replace("relay.proxy?", "relay.proxy?opensocial_authtype=SIGNED&");
    
    MyOpenSpace.Ajax.sendContentRequest(relay_url, content_completed, content_errored, params);
    
    // FOR LOCAL TESTING ONLY
    //MyOpenSpace.Ajax.sendContentRequest("/sample.xml", completed_, errored_, opt_params);
    
    function content_completed(xobj, url, params){
        opt_params = opt_params || {};
         
    if (xobj.readyState !== 4) {
      return;
    }
    if (xobj.status !== 200) {
      // TODO Need to work on standardizing errors
      callback({errors : ["Error " + xobj.status] });
      return;
    }
    var txt = xobj.responseText;
    // remove unparseable cruft.
    // TODO: really remove this by eliminating it. It's not any real security
    //    to begin with, and we can solve this problem by using post requests
    //    and / or passing the url in the http headers.
   // txt = txt.substr(UNPARSEABLE_CRUFT.length);
    var data = {};    
    data.body = xobj.responseText;
    var resp = {
     text: data.body,
     errors: []
    };
    switch (params.contentType) {
      case "JSON":
        resp.data = gadgets.json.parse(resp.text);
        if (!resp.data) {
          resp.errors.push("failed to parse JSON");
          resp.data = null;
        }
        break;
      case "FEED":
        var dom;
        if (window.ActiveXObject) {
          dom = new ActiveXObject("Microsoft.XMLDOM");
          dom.async = false;
          dom.validateOnParse = false;
          dom.resolveExternals = false;
          if (!dom.loadXML(resp.text)) {
            resp.errors.push("failed to parse XML");
          } else {
            resp.data = dom;
          }
        } else {
          var parser = new DOMParser();
          dom = parser.parseFromString(resp.text, "text/xml");
          if ("parsererror" === dom.documentElement.nodeName) {
            resp.errors.push("failed to parse XML");
          } else {
            resp.data = dom;
          }
        }
          if (params.summariesOnly){
            resp.data = new MyOpenSpace.Feed.RSS2.Channel(resp.data, true, params.numEntries);
          }
          else{
            resp.data = new MyOpenSpace.Feed.RSS2.Channel(resp.data, false, params.numEntries);
          }
        break;
      case "DOM":
        var dom;
        if (window.ActiveXObject) {
          dom = new ActiveXObject("Microsoft.XMLDOM");
          dom.async = false;
          dom.validateOnParse = false;
          dom.resolveExternals = false;
          if (!dom.loadXML(resp.text)) {
            resp.errors.push("failed to parse XML");
          } else {
            resp.data = dom;
          }
        } else {
          var parser = new DOMParser();
          dom = parser.parseFromString(resp.text, "text/xml");
          if ("parsererror" === dom.documentElement.nodeName) {
            resp.errors.push("failed to parse XML");
          } else {
            resp.data = dom;
          }
        }
        break;
      default:
        resp.data = resp.text;
        break;
    }
    
//    var cloned = resp;
//    
//    for (var prop in cloned)
//    {
//        resp[prop] = cloned[prop];
//    }
    var errored = (resp.errors.length >= 1) ? true : false;
    if (!errored) {
        if (opt_params["LEGACY"])
        {
            resp = resp.data;
        }
    }
    callback(resp, url , errored);
  //}
        //var contentType = params[gadgets.io.RequestParameters.CONTENT_TYPE] || gadgets.io.ContentType.TEXT;
//        var content = {};
//        switch (params.contentType) {
//            case gadgets.io.ContentType.TEXT:
//                content.data = response.responseText;
//                break;
//            case gadgets.io.ContentType.DOM:
//                content.data = response.responseXML;
//                break;
//            case gadgets.io.ContentType.JSON:
//                //var summariesOnly = opt_params[gadgets.io.RequestParameters.GET_SUMMARIES] || false;
//                
//                //var numEntries = opt_params[gadgets.io.RequestParameters.NUM_ENTRIES] || 3;
//                if (params.summariesOnly){
//                    content = new MyOpenSpace.Feed.RSS2.Channel(response.responseXML, true, params.numEntries);
//                }
//                else{
//                    content = new MyOpenSpace.Feed.RSS2.Channel(response.responseXML, false, params.numEntries);
//                }
//                break;
//        } 
        //callback(content, url, false);
       // gadgets.io.processResponse(url, callback, params, response);
    }
    function content_errored(response, url){
	    callback(response, url, true);
	}
	function encodeValues(fields) {
      var buf = [];
      var first = false;
      for (var i in fields) {
        if (!first) {
          first = true;
        } else {
          buf.push("&");
        }
        buf.push(encodeURIComponent(i));
        buf.push("=");
        buf.push(encodeURIComponent(fields[i]));
      }
      return buf.join("");
    }
 }

/**
 * The parameters that are passed between surfaces.
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.params_ = null;

/**
 * Private instance of the request processor, use MySpaceContainer.getRequestProcessor to access.
 * @type {MyOpenSpace.RequestProcessor_}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.requestProcessor_ = null;

/**
 * A private hash table containing the permissions that have been granted through
 * MySpaceContainer.requestPermission, use MySpaceContainer.hasPermission to access.
 * @type {Hash}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype._userGrantedPermissions = new MyOpenSpace.Hash();

/**
 * Private instance of the environment, use MySpaceContainer.getEnvironment to access.
 * @type {opensocial.Environment}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.environment_ = null;

/**
 * Private instance of the MySpace environment, use MySpaceContainer.getMySpaceEnvironment to access.
 * @type {MyOpenSpace.Environment}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.myspaceenvironment_ = null;

/**
 * Returns the container's MySpace environment object
 * @return {MyOpenSpace.Environment}
 */
MyOpenSpace.MySpaceContainer.prototype.getMySpaceEnvironment = function() { return this.myspaceenvironment_; };

/**
 * Returns the container's request processor
 * @return {MyOpenSpace.RequestProcessor_}
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.getRequestProcessor = function() { return this.requestProcessor_; };

/**
 * Returns the container's environment object.
 * @return {opensocial.Environment}
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.getEnvironment = function() { return this.environment_; };

/**
 * Returns a new MyOpenSpace.Person instance
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields&gt;} opt_params Optional parameters specified when creating the person.
 * @param {Boolean} opt_isViewer True if this is the viewer of the page otherwise, false.
 * @param {Boolean} opt_isOwner True if this is the owner of the page otherwise, false.
 * @return {MyOpenSpace.Person} The person object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newPerson = function(opt_params, opt_isOwner, opt_isViewer){
    myOsTrace("MySpaceContainer.newPerson");
    return new MyOpenSpace.Person(opt_params, opt_isOwner, opt_isViewer);
};

MyOpenSpace.MySpaceContainer.prototype.newName = function(opt_params){
    return new MyOpenSpace.Name(opt_params);
};


/**
 * Returns a new MyOpenSpace.Album instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Album} The album object
 */
MyOpenSpace.MySpaceContainer.prototype.newAlbum = function(opt_params){
    myOsTrace("MySpaceContainer.newAlbum");
    return new MyOpenSpace.Album(opt_params);
};

/**
 * Returns a new MyOpenSpace.Video instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Video} The album object
 */
MyOpenSpace.MySpaceContainer.prototype.newVideo = function(opt_params){
    myOsTrace("MySpaceContainer.newVideo");
    return new MyOpenSpace.Video(opt_params);
};


/**
 * Returns a new MyOpenSpace.Photo instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Photo} The photo object
 */
MyOpenSpace.MySpaceContainer.prototype.newPhoto = function(opt_params){
    myOsTrace("MySpaceContainer.newPhoto");
    return new MyOpenSpace.Photo(opt_params);
};

/**
 * Returns a new opensocial.DataRequest instance, which is overridden locally
 * @function
 * @return {opensocial.DataRequest} The data request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newDataRequest = function(){
    myOsTrace("MySpaceContainer.newDataRequest");
    return new opensocial.DataRequest(this.osToken_, this.endPoint_); // returns overridden DataRequest object
};

/**
 * Returns a new opensocial.ResponseItem instance
 * @function
 * @param {opensocial.DataRequest} originalDataRequest The ID of the photo to fetch
 * @param {Object || null} data The object retrieved, can be any entity, or null if nothing was found.
 * @param {String} opt_errorCode A code defining the error that occurred, if any.
 * @param {String} opt_errorMessage A message detailing the error that occurred, if any.
 * @return {opensocial.ResponseItem} The response item object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newResponseItem = function(originalDataRequest, data, opt_errorCode, opt_errorMessage) {
    //myOsTrace("MySpaceContainer.newResponseItem");
    return new opensocial.ResponseItem(originalDataRequest, data, opt_errorCode, opt_errorMessage);
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPersonRequest = function(id, opt_params) {
	myOsTrace("MySpaceContainer.newFetchPersonRequest");
	opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_PERSON,
		parameters: {
			id:id,
			profileDetail: (id === opensocial.DataRequest.PersonId.VIEWER || id === opensocial.DataRequest.PersonId.OWNER) ? this.mapPersonDetails_(opt_params) : MyOpenSpace.DetailType.BASIC
		}
	}
};

/**
 * 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
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPhotoRequest = function(id, photo_id, opt_params){
    myOsTrace("MySpaceContainer.newFetchPhotoRequest");
    return {
		type: MyOpenSpace.RequestType.FETCH_PHOTO,
		parameters: {
			id:id,
			photo_id:photo_id
		}
	}
};

/**
 * 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
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPhotosRequest = function(id, opt_params){
    myOsTrace("MySpaceContainer.newFetchPhotosRequest");
    opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_PHOTOS,
		parameters: {
			id:id,
			album_id: opt_params[MyOpenSpace.DataRequest.PhotoRequestFields.ALBUM_ID] || null,
			first: opt_params[opensocial.DataRequest.PeopleRequestFields.FIRST],
			max: opt_params[opensocial.DataRequest.PeopleRequestFields.MAX]
		}
	}
};

/**
 * 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
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchAlbumRequest = function(id, album_id, opt_params){
    myOsTrace("MySpaceContainer.newFetchAlbumRequest");
    return {
		type: MyOpenSpace.RequestType.FETCH_ALBUM,
		parameters: {
			id:id,
			album_id:album_id
		}
	}
};

/**
 * 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
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchAlbumsRequest = function(id, opt_params){
    myOsTrace("MySpaceContainer.newFetchAlbumsRequest");
    opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_ALBUMS,
		parameters: {
			id:id,
			first: opt_params[opensocial.DataRequest.PeopleRequestFields.FIRST],
			max: opt_params[opensocial.DataRequest.PeopleRequestFields.MAX]
		}
	}
};

/**
 * 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
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchVideoRequest = function(id, video_id, opt_params){
    myOsTrace("MySpaceContainer.newFetchVideoRequest");
    return {
		type: MyOpenSpace.RequestType.FETCH_VIDEO,
		parameters: {
			id:id,
			video_id:video_id
		}
	}
};

/**
 * 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
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchVideosRequest = function(id, opt_params){
    myOsTrace("MySpaceContainer.newFetchVideosRequest");
    opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_VIDEOS,
		parameters: {
			id:id,
			first: opt_params[opensocial.DataRequest.PeopleRequestFields.FIRST],
			max: opt_params[opensocial.DataRequest.PeopleRequestFields.MAX]
		}
	}
};

/**
 * Creates an item to request friends from the server.
 * When processed, returns an opensocial.Collection&lt;MyOpenSpace.Person&gt; object.
 * @param {Array.&lt;String&gt; | String} idSpec An ID reference used to specify which people to fetch; the supported keys VIEWER_FRIENDS or OWNER_FRIENDS.
 * @param {Map.&lt;opensocial.DataRequest.PeopleRequestFields} opt_params Additional opensocial.DataRequest.PeopleRequestFields params to pass to the request.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPeopleRequest = function(idSpec, opt_params) {
	myOsTrace("MySpaceContainer.newFetchPeopleRequest");
    opt_params = opt_params || {};
	var fields = opensocial.DataRequest.PeopleRequestFields;
	var groups = opensocial.DataRequest.Group
	var type = MyOpenSpace.RequestType.FETCH_PEOPLE;
	
	return {
	    type: type,
		parameters: {
		    idSpec:idSpec,
		    profileDetail:this.mapPersonDetails_(opt_params),
		    sortOrder:opt_params[fields.SORT_ORDER],
		    filter:opt_params[fields.FILTER] || opensocial.DataRequest.FilterType.ALL,
		    first:opt_params[fields.FIRST] || 0,
		    max:opt_params[fields.MAX],
		    online:opt_params[MyOpenSpace.DetailType.GET_ONLINE] || false,
			status:opt_params[MyOpenSpace.DetailType.GET_STATUS] || false,
			mood:opt_params[MyOpenSpace.DetailType.GET_MOOD] || false
		}
	}
};

/**
 * Determines if the requested Person will be mapped internally to BASIC, FULL or EXTENDED
 * @function
 * @return {MyOpenSpace.DetailType} The detail type of the requested person
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.mapPersonDetails_ = function(params) {
    myOsTrace("MySpaceContainer.mapPersonDetails_");
    var details = params && params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS];
    var isFull = false;
	if(!details) return MyOpenSpace.DetailType.BASIC;
	
	for(var i = 0; i < details.length; i++){
	    if(details[i] === opensocial.Person.Field.BOOKS) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === MyOpenSpace.Person.Field.DESIRE_TO_MEET) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === MyOpenSpace.Person.Field.HEADLINE) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === opensocial.Person.Field.HEROES) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === opensocial.Person.Field.INTERESTS) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === opensocial.Person.Field.MOVIES) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === opensocial.Person.Field.MUSIC) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === MyOpenSpace.Person.Field.OCCUPATION) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === opensocial.Person.Field.TV_SHOWS) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === MyOpenSpace.Person.Field.ZODIAC_SIGN) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === opensocial.Person.Field.STATUS) return MyOpenSpace.DetailType.EXTENDED;
	    if(details[i] === MyOpenSpace.Person.Field.MOOD) return MyOpenSpace.DetailType.EXTENDED;

	    if(details[i] === opensocial.Person.Field.ABOUT_ME) isFull = true;
	    else if(details[i] === opensocial.Person.Field.AGE) isFull = true;
	    else if(details[i] === MyOpenSpace.Person.Field.CITY) isFull = true;
	    else if(details[i] === MyOpenSpace.Person.Field.COUNTRY) isFull = true;
	    else if(details[i] === opensocial.Person.Field.GENDER) isFull = true;
	    else if(details[i] === MyOpenSpace.Person.Field.HOMETOWN) isFull = true;
	    else if(details[i] === opensocial.Person.Field.RELATIONSHIP_STATUS) isFull = true;
	    else if(details[i] === MyOpenSpace.Person.Field.POSTALCODE) isFull = true;
	    else if(details[i] === MyOpenSpace.Person.Field.REGION) isFull = true;
	}
	
	return (isFull)? MyOpenSpace.DetailType.FULL : MyOpenSpace.DetailType.BASIC;
};

/**
 * Get a new MySpace environment object.
 * @return {MyOpenSpace.Environment} the MySpace environment object
 * @private
 */
MyOpenSpace.MySpaceContainer.prototype.newMySpaceEnvironment = function(supportedPostToTargets) {
  return new MyOpenSpace.Environment(supportedPostToTargets);
};

/**
 * Starts the sending process
 * @param {opensocial.DataRequest} dataRequest The request to get processed and sent
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.requestData = function(dataRequest, callback) {
    myOsTrace("MySpaceContainer.requestData");
    dataRequest.requestProcessor_.prepareForSend(dataRequest);
    dataRequest.requestProcessor_.startProcessing();
};

//TODO: opensocial.Permission only contains VIEWER - do we overload or will google populate??
MyOpenSpace.MySpaceContainer.prototype.hasPermission = function(permission) { //We're not going to lock anything down just yet...
    myOsTrace("MySpaceContainer.hasPermission");
    var allowed = false;
    if (this._userGrantedPermissions.has(permission)) return true; //short circuit for already granted permissions - is this right?
    switch (permission) {
        case opensocial.Permission.VIEWER:
            allowed = true;
            break;
        default:
            allowed = false;
            break;
    }
    return allowed;
};

MyOpenSpace.MySpaceContainer.prototype.enableCaja = function() {
    opensocial.Container.prototype.enableCaja();

    ___.allowCall(MyOpenSpace.Album.prototype, 'getField');
    ___.allowCall(MyOpenSpace.Album.prototype, 'setField_');
    
    ___.allowCall(MyOpenSpace.Video.prototype, 'getField');
    ___.allowCall(MyOpenSpace.Video.prototype, 'setField_');
    
    ___.allowCall(MyOpenSpace.Photo.prototype, 'getField');
    ___.allowCall(MyOpenSpace.Photo.prototype, 'setField_');
};

/**
 * Kicks off the request processor
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.startProcessor_ = function(){ //TODO: rework how we are unspooling requestactions??
    myOsTrace("MySpaceContainer.startProcessor_");
    //if (this.requestProcessor_._readyToSend)
        //this.requestProcessor_.startProcessing();
};

MyOpenSpace.MySpaceContainer.prototype.newFetchGlobalAppDataRequest = function() {
    return {
		type: MyOpenSpace.RequestType.FETCH_GLOBAL_DATA
	};
};

MyOpenSpace.MySpaceContainer.prototype.newFetchInstanceAppDataRequest = function() {
    return {
		type: MyOpenSpace.RequestType.FETCH_INSTANCE_DATA
	};
};

MyOpenSpace.MySpaceContainer.prototype.newUpdateInstanceAppDataRequest = function() {
    return {
		type: MyOpenSpace.RequestType.UPDATE_INSTANCE_DATA
	};
};

MyOpenSpace.MySpaceContainer.prototype.newFetchPersonAppDataRequest = function(idSpec, keys) {
    return {
		type: MyOpenSpace.RequestType.FETCH_PERSON_DATA,
		parameters : {
		    idSpec: idSpec,
		    keys: keys
		}
	};
};

MyOpenSpace.MySpaceContainer.prototype.newUpdatePersonAppDataRequest = function(id, key, value) {
    return {
		type: MyOpenSpace.RequestType.UPDATE_PERSON_DATA,
		parameters : {
		    id: id,
		    key : key,
		    value : value
		}
	};
};

MyOpenSpace.MySpaceContainer.prototype.newRemovePersonAppDataRequest = function(id, key) {
    return {
		type: MyOpenSpace.RequestType.UPDATE_PERSON_DATA,
		parameters : {
		    id: id,
		    key : key,
		    value : ""
		}
	};
};

MyOpenSpace.MySpaceContainer.prototype.newFetchActivitiesRequest = function() {
    return {
		type: MyOpenSpace.RequestType.FETCH_ACTIVITIES
	};
} ;

MyOpenSpace.MySpaceContainer.prototype.requestCreateActivity = function() {
    //do nothing here until this is implemented.
};

/**
 * TODO: comment this once finalized
 * IFPC support inside container
 * @internal
 */
var _IFPC = window["_IFPC"];
MyOpenSpace.MySpaceContainer.prototype.registerParam = function(key, value) {
    if("appParams" === key){
        // unspool the opt_key params and put them in the base params_ list with the others.
        for(var i in value){
            this.params_[i] = value[i];
            gadgets.views.getParams()[i] = value[i];
        }
    }
    else{
        this.params_[key] = value;
        gadgets.views.getParams()[key] = value;
        if("appid" === key){
            gadgets.Prefs.parseUrl(value);
        }
    }
};

gadgets.views.requestNavigateTo = function(view, opt_params) { 
    if(view && view.name_){
        var view_name = view.getName();
        if(0===view_name.indexOf("profile.")) view_name = MyOpenSpace.View.Field.PROFILE;
        var p = opensocial.Container.get().params_;
        _IFPC.call(
            p.panelId,
            "requestNavigateTo",
            [p.appid, p.ownerid, view_name, opt_params],
            p.remoteRelay,
            null,
            p.localRelay,
            null);
    }
};

MyOpenSpace.MySpaceContainer.prototype.requestPermission = function(permissions, reason, opt_callback) {
    myOsTrace("MySpaceContainer.requestPermission");
    if(MyOpenSpace.Surface.Field.CANVAS === gadgets.views.getCurrentView().getName()){
        var validatedPermissions = new Array();
        if(permissions && permissions instanceof Array) {
            for(i=0;i<permissions.length;i++) {
                //TODO: validate this somehow
                validatedPermissions.push(permissions[i]);
            }
        }
        
        var userGrantedPermissions_sync = function (permissionState){
            if(permissionState){
                for(var key in permissionState){
                    //TODO: validate this somehow
                    opensocial.Container.get()._userGrantedPermissions.add(key, permissionState[key]);
                }
                if(opt_callback) opt_callback(permissionState);
            }
            else{
                if(opt_callback) opt_callback(null);
            }
        }
        
        _IFPC.call(
            this.params_.panelId,
            "requestPermission",
            [this.params_.appid, validatedPermissions, reason],
            this.params_.remoteRelay,
            userGrantedPermissions_sync,
            this.params_.localRelay,
            null);
    }
};

/**
 * Takes some content from the app and sends it up to the site to be posted.
 * @param {String} os_token The token, this gets passed up so we can verify who's sending the request.
 * @param {opensocial.Message} message The content to be posted, the message type refers to the target of the post.
 * @param {opensocial.Person} opt_person An optional opensocial.Person object, used when a recipient is required, e.g. when posting a comment, this person will get the comment.
 * @param {Function} opt_callback Callback function, right now just for success/fail.
 */
MyOpenSpace.MySpaceContainer.prototype.postTo = function(os_token, message, opt_person, opt_callback) {
    if(MyOpenSpace.Surface.Field.CANVAS === gadgets.views.getCurrentView().getName()){
        var target_is_supported = false;
        var supported = opensocial.Container.get().getMySpaceEnvironment().getSupportedPostToTargets();
        
        var messageSubject = "", messageBody, messageType = MyOpenSpace.PostTo.Targets.PROFILE;
        if(null !== message && "undefined" !== typeof(message)){
            messageSubject = message.getField(opensocial.Message.Field.TITLE);
            messageBody = message.getField(opensocial.Message.Field.BODY);
            messageType = message.getField(opensocial.Message.Field.TYPE);
        }
        else{
            return {"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":"You must supply a valid opensocial.Message object."}; 
        }
        
        for(var i = 0; i < supported.length; i++){
            if(supported[i] === messageType){
                target_is_supported = true;
                break;
            }
        }
        
        if(target_is_supported){
            // subject and type are optional params for opensocial.newMessage, so define defaults here, i just randomly picked profile.
            var personId, personName, personImage, personProfile;
            if(null !== opt_person && "undefined" !== typeof(opt_person)){
                personId = opt_person.getId();
                personName = opt_person.getDisplayName();
                personImage = opt_person.getField(opensocial.Person.Field.THUMBNAIL_URL);
                personProfile = opt_person.getField(opensocial.Person.Field.PROFILE_URL);
            }

            _IFPC.call(
                    this.params_.panelId,
                    "postTo",
                    [os_token, messageType, messageSubject, messageBody, personId, personImage, personName, personProfile],
                    this.params_.remoteRelay,
                    opt_callback,
                    this.params_.localRelay,
                    null);
        }
        else{
            return {"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":"That PostTo target is not supported."}; 
        }
    }
    else{
        return {"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":"PostTo does not support this view."}; 
    }
};
 
MyOpenSpace.MySpaceContainer.container_ = new MyOpenSpace.MySpaceContainer();


// If a gadget was written for the opensocial 0.6 apis and it wants to run in a
// container that only supports 0.7, including this file should allow the gadget
// to run without making any changes.

//opensocial.requestNavigateTo = function(surface, opt_params) {
  //return gadgets.views.requestNavigateTo(surface, opt_params);
//};
opensocial.requestNavigateTo = gadgets.views.requestNavigateTo;

//MyOpenSpace.MySpaceContainer.prototype.resizePanel = function(height) {
  //return gadgets.window.adjustHeight(height);
//};
MyOpenSpace.MySpaceContainer.prototype.resizePanel = gadgets.window.adjustHeight;

// If using enums, gadgets are ok.
// TODO: Translate hardcoded string params to the new gadgets values
opensocial.makeRequest = function(url, callback, opt_params) {
  opt_params = opt_params || {};
  opt_params["LEGACY"] = true;
  return gadgets.io.makeRequest(url, callback, opt_params);
};
//opensocial.makeRequest = gadgets.io.makeRequest;

opensocial.ContentRequestParameters = {
  METHOD : gadgets.io.RequestParameters.METHOD,
  CONTENT_TYPE : gadgets.io.RequestParameters.CONTENT_TYPE,
  AUTHENTICATION : gadgets.io.RequestParameters.AUTHORIZATION,
  AUTHORIZATION : gadgets.io.RequestParameters.AUTHORIZATION,
  NUM_ENTRIES : gadgets.io.RequestParameters.NUM_ENTRIES,
  GET_SUMMARIES : gadgets.io.RequestParameters.GET_SUMMARIES,
  POST_DATA : gadgets.io.RequestParameters.POST_DATA
};

opensocial.ContentRequestParameters.MethodType = {
  GET : gadgets.io.MethodType.GET,
  POST : gadgets.io.MethodType.POST
};

opensocial.ContentRequestParameters.ContentType = {
  HTML : gadgets.io.ContentType.TEXT,
  XML : gadgets.io.ContentType.DOM,
  FEED : gadgets.io.ContentType.FEED
};


opensocial.ContentRequestParameters.AuthenticationType = {
  NONE : gadgets.io.AuthorizationType.NONE,
  SIGNED : gadgets.io.AuthorizationType.SIGNED,
  AUTHENTICATED : gadgets.io.AuthorizationType.AUTHENTICATED
};

opensocial.ContentRequestParameters.AuthorizationType = opensocial.ContentRequestParameters.AuthenticationType;

opensocial.Person.prototype.getFieldOld = opensocial.Person.prototype.getField;

opensocial.Person.prototype.getField = function(fieldname) {
    if (fieldname == opensocial.Person.Field.NAME) {
        //reflect(this.getFieldOld(opensocial.Person.Field.NAME));
        return this.getFieldOld(opensocial.Person.Field.NAME).getField(opensocial.Name.Field.UNSTRUCTURED);
    } else {
        return this.getFieldOld(fieldname);
    }
}

opensocial.Person.prototype.getDisplayNameOld = opensocial.Person.getDisplayName;
opensocial.Person.prototype.getDisplayName = function() {
    return this.getFieldOld(opensocial.Person.Field.NAME).getField(opensocial.Name.Field.UNSTRUCTURED);
}

opensocial.newActivityOld = opensocial.newActivity;
opensocial.newActivity = function(title, opt_params) {
  opt_params = opt_params || {};
  opt_params['title'] = title;
  return opensocial.newActivityOld(opt_params);
};

opensocial.DataRequest.prototype.newFetchGlobalAppDataRequest = function(keys) {
  // This should be a no-op. This call never fetched anything relavant because
  // you couldn't set any global app data.
  // However, we don't want containers to crash, so we will just fetch person
  // app data. This seems the best we can do for now.
  return this.newFetchPersonAppDataRequest(keys);
};

opensocial.DataRequest.prototype.newFetchInstanceAppDataRequest =
    function(keys) {
//  var moduleId = new gadgets.Prefs().getModuleId();
//  if (opensocial.Container.isArray(keys)) {
//    for (var i = 0; i < keys.length; i++) {
//      keys[i] = moduleId + keys[i];
//    }
//  } else {
//    keys = moduleId + keys;
//  }

  return this.newFetchPersonAppDataRequest('OWNER', keys);
};

opensocial.DataRequest.prototype.newUpdateInstanceAppDataRequest = function(key,
    value) {
//  var moduleId = new gadgets.Prefs().getModuleId();
    return this.newUpdatePersonAppDataRequest('OWNER', key);
    //return {"errorCode":opensocial.ResponseItem.Error.NOT_IMPLEMENTED,"errorMessage":""};

};

// The surface object is gone, but the user never directly knew about the object
// (ie they couldn't construct it) so we will just make the methods look the
// same.
gadgets.views.View.prototype.isPrimaryContent = function() {
  return this.isOnlyVisibleGadget();
}

// Note: The names of views may have changed in a container between 0.6 and 0.7
// but that is container specific

opensocial.Environment.prototype.getSurface = function() {
  return gadgets.views.getCurrentView();
};
opensocial.Environment.prototype.getSupportedSurfaces = function() {
  return gadgets.views.getSupportedViews();
};
opensocial.Environment.prototype.getParams = function() {
  return gadgets.views.getParams();
};
//opensocial.Environment.prototype.hasCapability = function() {
  //return gadgets.util.hasFeature();
//};
opensocial.Environment.prototype.hasCapability = gadgets.util.hasFeature;

MyOpenSpace.MySpaceContainer.prototype.makeRequest = opensocial.makeRequest;
