/**
 * @class HD.CommunityDAO
 * @description Handles data access requests and distributing the responses for the Community platform 
 * @constructor
 * @property {object}	config
 *						The configuration for the object.
 * @property {string}	config.baseUrl
 *						The base service URL for Community requests
 * @property {string}	config.siteName
 *						The site name key for the active site
 * @property {string}	config.contentType
 *						The default type of content to retrieve
 * @property {array}	config.categories
 *						The categories for the active site to retrieve data from
 * @property {number}	config.startIndex
 *						The initial page of data
 * @property {number}	config.pageSize
 *						The number of items included in a page of data
 * @property {number}	config.thumbWidth
 *						The width in pixels to return item thumbnails in
 * @property {number}	config.mediaWidth
 *						The width in pixels to return item full size pictures in
 * @property {object}	[config.preloadedMedia]
 *						A preloaded getMedia response so that an initial getMedia call doesn't need to be requested
 * @property {boolean}	[config.secure=false]
 *						Whether to force all requests through a secure gateway
 */
HD.CommunityDAO = function(config) {
	this.config = config || {};

	// {boolean/string} secure: Converts KickApps URL's to
	// https://www.harley-davidson.com/ redirect (or custom URL if specified).
	this.config.secure = this.config.secure || false;
	if (this.config.secure && typeof this.config.secure == 'boolean') {
		this.config.secure = 'https://www.harley-davidson.com/';
	}

	// 87949232233 is for Dark Custom
	// 100000099251972 is Burntcow test account
	this.config.facebookUID = this.config.facebookUID || '87949232233';

	this.config.uploadUrl = config.uploadUrl || '/fileUpload';
	
	/** The base KickApps media server */
	this.mediaBaseUrl = 'http://media.kickstatic.com/';
	/** The URL feeds within KickApps media */
	this.mediaUrlFields = [ 'thumbUrl', 'url' ];
	/** The active page index */
	this.pageNumber = config.startIndex;
	/** The initial page index */
	this.initialPageIndex = config.startIndex;
	/** The total number of pages in the active dataset */
	this.totalPages = config.startIndex;
	/** The total number of media in the active dataset */
	this.totalMedia = 0;
	/** The current active item within the dataset */
	this.activeItem = null;
	/** The collection of active dataset filters */
	this.filters = config.filters || {};
	/** The index on a specific page of the active item */
	this.activeItemIndexOnPage = 0;
	/** The page of the active item */
	this.activeItemPage = config.startIndex;
	/** An array of DAO observers */
	this.observers = [];
	/** A cache of media for rating purposes */
	this.ratings = [];
	
	/** REST client (if available) */
	/** If not running under next gen, override to true **/
	this.rest = HD.util.REST || null;
	if (this.rest && !HD.util.Common.getServerUrl().match(/^https?:\/\/[^/]*?\.harley-davidson\.com.*/)) {
		this.rest.useProxy = false;
	}
	this.restAuth = {};

	if (!!this.config.restoreState) {
		this.restoreState(typeof this.config.restoreState == 'string' ? this.config.restoreState : null, true);
	}
};

HD.CommunityDAO.prototype = {
	/**
	 * Abstracted media request calls
	 * This is used so that other services can override Community platform calls
	 */
	adapter: {
		/**
		 * Build an endpoint path for a REST call to the CommunityService
		 * 
		 * @param operation tail-end of path to append to common prefix
		 * 
		 * @return concatenation of common prefix and specified operation
		 */
		getEndpoint: function( operation ) {
			return '/Community/service/rest/' + operation;
		},
		
		/**
		 * This method wraps an object inside another object with the given top-level
		 * property name and then returns the whole thing converted to a JSON string.
		 * 
		 * @param propertyName  name of nested property to put the object under
		 * @param obj           the actual data object to wrap
		 * 
		 * @return string version of constructed object
		 */
		prepareData: function( propertyName, obj ) {
			try {
				var data = obj;
				
				if ( propertyName && /\S/.test( propertyName ) ) {
					data = {};
					
					data[ propertyName ] = obj;
				}
				
				return YAHOO.lang.JSON.stringify( data );
			}
			catch ( ex ) {
				return '{}';
			}
		},

		/**
		 * Construct an anonymous function as a callback. The old DWR code specified the
		 * callback as a nested property of the cb parameter. So this method checks to see
		 * if the cb object itself is a function. If so, it uses that. If not, but the
		 * cb.callback property is a function, it uses that. Otherwise, it constructs a
		 * dummy no-op function to use. The constructed callback then uses the provided
		 * responseProperty to pull out the correct returned data and send it to the
		 * callback.
		 * 
		 * @param cb  object holding callback function
		 * @param responseProperty name of property in returned data that contains desired info
		 * 
		 * @return anonymous function to use as callback
		 */
		prepareCallback: function( cb, responseProperty ) {
			var callback = function() {};
			
			if ( YAHOO.lang.isFunction( cb ) ) {
				callback = cb;
			}
			else if ( YAHOO.lang.isFunction( cb.callback ) ) {
				callback = cb.callback;
			}
			
			return function( response ) {
				if ( response ) {
					var data = response;
					
					if ( responseProperty ) {
						var props = responseProperty.split( /\./ );

						try {
							// traverse any nested properties (eg, data.items.stuff)
							for ( var i in props ) {
								data = data[ props[ i ] ];
							}
						}
						catch ( ex ) {
						}
						
						callback( data );
					}
				}
				else {
					callback( {} );
				}
			}
		},
		
		/**
		 * Make a REST call to a CommunityService endpoint.
		 * 
		 * @param request          the request data to send
		 * @param requestProperty  name of property to wrap request data as
		 * @param cb               callback function info
		 * @param responseProperty name of property in response holding info
		 * @param operation        name of service method to call
		 * @param method           type of request to make (POST or GET) 
		 */
		runRequest: function( request, requestProperty, cb, responseProperty, operation, method ) {
			//console.log( 'runRequest( ${reqProp}, ${resProp}, ${op} )'.process( {
			//		reqProp: requestProperty,
			//		resProp: responseProperty,
			//		op:      operation
			//	}
			//) );
			
			var data     = this.prepareData( requestProperty, request );
			var callback = this.prepareCallback( cb, responseProperty );
			
			HD.util.form.processRequest( method || 'POST', data, this.getEndpoint( operation ), callback );
		},
		
		// Media requests.
		getMedia: function( req, cb ) {
			//CommunityService.getMedia(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getGallery', 'POST' );
		},

		getPopularMedia: function( req, cb ) {
			// CommunityService.getPopularMedia(req, cb); },
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getPopularMedia', 'POST' );
		},
		
		getMedium: function( req, cb ) {
			// CommunityService.getMedium(req, cb); },
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getMedium', 'POST' );
		},
				
		getMultipleMedium: function(req, cb) {
			//CommunityService.getMultipleMedium(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getMultipleMedium', 'POST' );
		},
		
		getComments: function( req, cb ) {
			//CommunityService.getComments( req, cb );
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getComments', 'POST' );
		},

		// Content requests.
		getFaceoff: function(req, cb) {
			//CommunityService.getFaceoff(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'faceoff', 'getFaceoff', 'POST' );
		},
		
		getTags: function(req, cb) {
			//CommunityService.getTags(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getTags', 'POST' );
		},
		
		getRatings: function(req, cb) {
			//CommunityService.getRatings(req, cb);
			this.runRequest( req, 'ratingRequest', cb, 'ratingResponse', 'getRatings', 'POST' );
		},
		
		getMonthly: function(req, cb) {
			//CommunityService.getMonthly(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getMonthly', 'POST' );
		},
		
		getAllBlogAuthors: function(cb) {
			//CommunityService.getAllBlogAuthors(cb);
			this.runRequest( null, null, cb, 'blogAuthorResponse.blogAuthors', 'getAllBlogAuthors', 'GET' );
		},
		
		getMemberProfile: function(req, cb) {
			//CommunityService.getMemberProfile(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'getMemberProfile', 'POST' );
		},

		// Submission requests.
		submitBlogEntry: function(req, cb) {
			//CommunityService.submitBlogEntry(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'submitBlogEntry', 'POST' );
		},
		
		submitComment: function(req, cb) {
			//CommunityService.submitComment(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'submitComment', 'POST' );
		},
		
		rateMedia: function(req, cb) {
			//CommunityService.rateMedia(req, cb);
			this.runRequest( req, 'contentRequest', cb, 'contentResponse', 'rateMedia', 'POST' );
		},
		
		// Poll requests
		getPollsFeed: function(cb) {
			//CommunityService.getPollsFeed(cb);
			this.runRequest( null, null, cb, 'pollResponse.polls', 'getPollsFeed', 'GET' );
		},

		// Social media requests.
		getFacebookEvents: function(req, cb) {
			//CommunityService.getFacebookEvents(req, cb);
			this.runRequest( req, 'facebookEventsRequest', cb, 'facebookEventsResponse.facebookEvents',
				'getFacebookEvents', 'POST' );
		},
			
		getFacebookAttendeesForEvent: function(req, cb) {
			//CommunityService.getFacebookAttendeesForEvent(req, cb);
			this.runRequest( req, 'facebookUsersRequest', cb, 'facebookUsersResponse.facebookUsers',
				'getFacebookAttendeesForEvent', 'POST' );
		},
		
		getTwitterFeed: function(cb) {
			//CommunityService.getTwitterFeed(cb);
			this.runRequest( null, null, cb, 'twitterResponse.twitterTweets', 'getTwitterFeed', 'GET' );
		},
			
		getYoutubeMedia: function(req, cb) {
			//CommunityService.getYoutubeMedia(req, cb);
			this.runRequest( req, 'youtubeRequest', cb, 'youtubeResponse.youtubeData',
				'getYoutubeMedia', 'POST' );
		}
	},
  	
	/**
	 * Retrieves a page of media given the active config
	 * @param {number} pageNumber The page to retrieve
	 * @param {boolean} [isLastIndexActive] Whether to set the last item as the active item
	 * @param {boolean} [preventSetActiveItem] Whether to prevent the DAO from setting an active item (i.e. to prevent duplicate analytics events)
	 * @param {number} [setThisIndex] The index of the item to set as active
	 */
	getMedia : function(pageNumber, isLastIndexActive, preventSetActiveItem,
			setThisIndex) {

		this.notifyObservers("getMedia_Start");

		this.pageIndex = pageNumber;

		var mediaRequest = {
			pageNumber : pageNumber
		};

		this.setFilters(mediaRequest);

		var cb = this;
		var isLastCb = isLastIndexActive;
		var preventSetActiveItem = preventSetActiveItem || false;

		var callMetadata = {
			callback : function(mediaResponse) {

				var eventName = mediaResponse.success ? "getMedia_Finish"
						: "getMedia_Error";

				var media = mediaResponse.media;

				var filteredMedia = [];

				var prevMedium = null;
				for ( var i = 0, len = media.length; i < len; i++) {
					var medium = media[i];
					if (medium != null) {
						if (prevMedium != null) {
							medium.previous = prevMedium;
						}
						if (i < len - 1) {
							medium.next = media[i + 1];
						}

						medium.uploadedDate = cb._fixJsonDate( medium.uploadedDate );
						
						cb.setFacade.call(cb, medium);

						filteredMedia.push(medium);
					}
					prevMedium = medium;
				}

				var activeIndex = setThisIndex
						|| (isLastCb ? media.length - 1 : 0);

				// TODO
				if (filteredMedia.length > 0 && !preventSetActiveItem) {
					cb.activeItem = filteredMedia[activeIndex];
				}

				cb.totalPages = mediaResponse.totalPages;
				cb.pageNumber = mediaResponse.pageNumber;
				cb.totalMedia = mediaResponse.totalMedia;

				cb.media = filteredMedia;

				mediaResponse.media = filteredMedia;
				cb.notifyObservers.call(cb, eventName, mediaResponse);

				if (filteredMedia.length > 0) {
					// !!! do not remove this,
					// otherwise 'activeItem_Change' event won't fire
					var itemToSetActive = filteredMedia[activeIndex];

					if (!preventSetActiveItem) {
						cb.activeItem = null;
						cb.setActiveItem.call(cb, itemToSetActive);
					}
				}
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		if (this.config.preloadedMedia == null) {
			this.adapter.getMedia(mediaRequest, callMetadata);
		} else {
			callMetadata.callback(this.config.preloadedMedia.getMedia());
			this.config.preloadedMedia = null;
		}
	},

	/**
	 * Convert URL's to secure URL's if needed.
	 * @param {object} medium The medium to update the URLs of
	 * @returns {object} The updated medium
	 */
	applySecureUrl : function(medium) {
		if (this.config.secure) {
			for ( var i = 0; i < this.mediaUrlFields.length; i++) {
				var field = [ this.mediaUrlFields[i] ];
				if (medium[field]) {
					medium[field] = medium[field].replace(this.mediaBaseUrl,
							this.config.secure);
				}
			}
		}
		return medium;
	},
	
	/**
	 * Determines whether there is a next page
	 * @returns {boolean}
	 */
	hasNextPage : function() {
		return this.pageNumber < this.totalPages;
	},
	
	/**
	 * Determines whether there is a previous page
	 * @returns {boolean}
	 */
	hasPreviousPage : function() {
		return this.pageNumber > this.initialPageIndex;
	},

	/**
	 * Requests the next page
	 * @param {boolean} [preventSetActiveItem] Whether to prevent an active item from being set
	 */
	getNextPage : function(preventSetActiveItem) {
		if (this.hasNextPage()) {
			this.getMedia(++this.pageNumber, null, preventSetActiveItem);
		}
	},

	/**
	 * Re-requests the same page
	 */
	getSamePage : function() {
		this.getMedia(this.pageNumber);
	},

	/**
	 * Requests the next page
	 * @param {boolean} [isLastIndexActive] Whether to set the last item as the active item
	 * @param {boolean} [preventSetActiveItem] Whether to prevent the DAO from setting an active item (i.e. to prevent duplicate analytics events)
	 */
	getPreviousPage : function(isLastIndexActive, preventSetActiveItem) {
		if (this.hasPreviousPage()) {
			this.getMedia(--this.pageNumber, isLastIndexActive,
					preventSetActiveItem);
		}
	},
	
	/**
	 * Retrieves the active item
	 * @returns {object}
	 */
	getActiveItem : function() {
		return this.activeItem;
	},

	/**
	 * Determines whether the active item has a next sibling
	 * @returns {boolean}
	 */
	hasNextItem : function() {
		if (this.activeItem && this.activeItem.next != null) {
			return true;
		}
		return this.hasNextPage();
	},
	
	/**
	 * Determines whether the active item has a previous sibling
	 * @returns {boolean}
	 */
	hasPreviousItem : function() {
		if (this.activeItem && this.activeItem.previous != null) {
			return true;
		}
		return this.hasPreviousPage();
	},
	
	/**
	 * Retrieves the active item's next sibling (requesting the next page if necessary)
	 * @returns {object}
	 */
	getNextItem : function() {
		if (this.activeItem.next != null) {
			this.setActiveItem(this.activeItem.next);
		} else if (this.hasNextPage()) {
			this.getNextPage();
			this.notifyObservers("activeItem_PageChange");
		}
	},
	
	/**
	 * Retrieves the active item's previous sibling (requesting the previous page if necessary)
	 * @returns {object}
	 */
	getPreviousItem : function() {
		if (this.activeItem.previous != null) {
			this.setActiveItem(this.activeItem.previous);
		} else if (this.hasPreviousPage()) {
			this.getPreviousPage(true);
			this.notifyObservers("activeItem_PageChange");
		}
	},

	/**
	 * Sets the active item
	 * @param {object} item The item to set as active
	 */
	setActiveItem : function(item) {

		if (this.activeItem != null && this.activeItem.mediaId == item.mediaId) {
			return;
		}
		this.activeItem = item;
		this.notifyObservers("activeItem_Change", item);
	},

	/**
	 * Enhances a KickApps medium object with additional functionality needed for Community
	 * @param {object} medium The medium to enhance
	 */
	setFacade : function(medium) {
		if (medium != null) {
			// Apply secure URL if necessary.
			this.applySecureUrl(medium);

			// Extract meta tags.
			var isMetaTag = /^hd:([a-zA-Z0-9_]+?)(=(.+))?$/;
			if (medium.tags && medium.tags.length > 0) {
				medium.metaTags = {};
				for ( var i = medium.tags.length - 1; i >= 0; i--) {
					var metaTag = medium.tags[i].match(isMetaTag);
					if (metaTag) {
						medium.tags.splice(i, 1);
						medium.metaTags[metaTag[1]] = metaTag[3] || true;
					}
				}
			}

			var contentType = medium.contentType || '';
			
			if (HD.community.isBlog(contentType)) {
				medium.facade = new HD.community.Blog(medium);
			} else if (HD.community.isPhoto(contentType)) {
				medium.facade = new HD.community.Photo(medium);
			} else if (HD.community.isVideo(contentType)) {
				medium.facade = new HD.community.Video(medium);
			} else {
				medium.facade = null;
			}
		}
	},

	/**
	 * Retrieves a specific medium
	 * @param {string|number} mediaId The ID of the medium to retrieve
	 * @param {function} [callback] The callback to pass the medium after it is loaded
	 * @param {stringm} [contentType] The type of medium to retrieve
	 */
	getMedium : function(mediaId, callback, contentType) {
		this.notifyObservers("getMedium_Start");
		var mediaRequest = {
			mediaId : mediaId
		};

		this.setFilters(mediaRequest);

		if (contentType != null) {
			mediaRequest.contentType = contentType;
		}

		var cb = this;
		var cbCb = callback;

		var callMetadata = {
			callback : function(mediaResponse) {
				var media = mediaResponse.media;
				var eventName = mediaResponse.success ? "getMedium_Finish"
						: "getMedium_Error";
				var medium = media[0];
				cb.setFacade.call(cb, medium);

				cb.insertItem.call(cb, medium);

				if (cbCb != null) {
					cbCb(medium);
				}

				cb.addCachedMedium.call(cb, medium);

				cb.setActiveItem.call(cb, medium);
				cb.notifyObservers.call(cb, eventName, medium);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getMedium(mediaRequest, callMetadata);
	},

	/**
	 * Batches requests for a collection of specific media
	 * @param {array} mediaIds The IDs of the media to retrieve
	 * @param {function} [callback] The callback to pass the medium after it is loaded
	 * @param {stringm} [contentType] The type of medium to retrieve
	 */
	getMultipleMedium : function(mediaIds, callback, contentType) {
		this.notifyObservers("getMultipleMedium_Start");
		var mediaRequest = { mediaId: (mediaIds || []).join(','), contentType: contentType || this.config.contentType }; 

		this.setFilters(mediaRequest);

		if (contentType != null) {
			mediaRequest.contentType = contentType;
		}

		var cb = this;
		var cbCb = callback;

		var callMetadata = {
			callback : function(mediaResponse) {
				var media = mediaResponse.media;
				var eventName = mediaResponse.success ? "getMultipleMedium_Finish" : "getMultipleMedium_Error";
				
				for (var i = 0; i < media.length; i++) {
					cb.setFacade.call(cb, media[i]);
					cb.insertItem.call(cb, media[i]);
					cb.addCachedMedium.call(cb, media[i]);
				}

				if (!!cbCb) {
					cbCb(media);
				}
				
				cb.notifyObservers.call(cb, eventName, media);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getMultipleMedium(mediaRequest, callMetadata);
	},
	
	/**
	 * Retrieves a list of tags for the active community
	 * @param {number} itemsLimit The maximum number of tags to return
	 */
	getTags : function(itemsLimit) {
		this.notifyObservers("getTags_Start");

		var mediaRequest = {
			itemsLimit : itemsLimit
		};

		this.setFilters(mediaRequest);
		// Using tags with this call results in bad data coming back.
		if (mediaRequest.tags) {
			delete mediaRequest.tags;
		}

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = mediaResponse.success ? "getTags_Finish"
						: "getTags_Error";

				// Remove meta tags.
				var isMetaTag = /^hd:([a-zA-Z0-9_]+?)(=(.+))?$/;
				for ( var i = mediaResponse.tags.length - 1; i >= 0; i--) {
					if (mediaResponse.tags[i].tagName.match(isMetaTag)) {
						mediaResponse.tags.splice(i, 1);
					}
				}

				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getTags(mediaRequest, callMetadata);
	},

	/**
	 * Retrieves a list of months with content for the active community
	 * @param {number} itemsLimit The maximum number of months to return
	 */
	getMonthly : function(itemsLimit) {
		this.notifyObservers("getMonthly_Start");

		var mediaRequest = {
			itemsLimit : itemsLimit
		};

		this.setFilters(mediaRequest);

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = mediaResponse.success ? "getMonthly_Finish"
						: "getMonthly_Error";
				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getMonthly(mediaRequest, callMetadata);
	},

	/**
	 * Retrieves a list of blog authors with content for the active community
	 * @param {number} itemsLimit The maximum number of authors to return
	 */
	getAllBlogAuthors : function(itemsLimit) {
		this.notifyObservers("getAllBlogAuthors_Start");

		var mediaRequest = {
			itemsLimit : itemsLimit
		};

		this.setFilters(mediaRequest);
		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = "getAllBlogAuthors_Finish";
				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getAllBlogAuthors(callMetadata);
	},

	/**
	 * Retrieves a list of all Facebook events (currently hard-coded for Dark Custom)
	 */
	getFacebookEvents : function() {
		this.notifyObservers("getFacebookEvents_Start");

		var cb = this;
		
		var eventRequest = {
			userId:         this.config.facebookUID,
			eventsToReturn: 8
		};

		var callMetadata = {
			callback : function(mediaResponse) {
				// var eventName = mediaResponse.success ? "getFBEvents_Finish"
				// : "getFBEvents_Error";
				var eventName = "getFacebookEvents_Finish";
				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName,
				userId : this.config.facebookUID, // "87949232233",//Dark Custom
													// is the default
				eventsToReturn: 8
			}
		};

		this.adapter.getFacebookEvents(eventRequest, callMetadata);
	},
	
	/**
	 * Retrieves a list of future Facebook events (currently hard-coded for Dark Custom)
	 */
	getFutureFacebookEvents : function() {
		this.notifyObservers("getFutureFacebookEvents_Start");

		var cb = this;

		var callMetadata = {
				callback : function(mediaResponse) {
			// var eventName = mediaResponse.success ? "getFBEvents_Finish"
			// : "getFBEvents_Error";
			var eventName = "getFutureFacebookEvents_Finish";
			cb.notifyObservers.call(cb, eventName, mediaResponse);
		}
		};

		var facebookEventCall = {
				userId : this.config.facebookUID, // "87949232233",//Dark
													// Custom set as default
				eventsToReturn: 8,
				startDate: "now",
				endDate: ""
		};

		this.adapter.getFacebookEvents(facebookEventCall, callMetadata);
	},
	
	/**
	 * Retrieves a randomized list of attendees for a specific Facebook event
	 * 
	 * @param {string} user id. if blank, will use configured default (dark custom)
	 * @param {string|number} eventId The event to check
	 * @param {number} [limit] The maximum number of attendees to return
	 */
	getFacebookAttendeesForEvent : function( userId, eventId, limit ) {
		this.notifyObservers("getFacebookAttendeesForEvent_Start");

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
			//var eventName = mediaResponse.success ? "getFacebookAttendeesForEvent_Finish" : "getFacebookAttendeesForEvent_Error";
			var eventName = "getFacebookAttendeesForEvent_Finish";
			cb.notifyObservers.call(cb, eventName, mediaResponse);
		},
		parameters : {
			siteName : this.config.siteName
			}
		};

	    var params = {
	      userId:  userId || this.config.facebookUID,
	      eventId: eventId,
	      numberOfUsersToReturn: limit
	    };
	    
	    this.adapter.getFacebookAttendeesForEvent( params, callMetadata );
	},

	/**
	 * Retrieves a list of YouTube videos (currently hard-coded for Dark Custom)
	 * @param {number} [numberToReturn] The maximum number of videos to return
	 */
	getYoutubeMedia : function(numberToReturn) {
		this.notifyObservers("getYoutube_Start");

		var youtubeRequest = {
			numberToReturn : numberToReturn
		};

		// this.setFilters(youtubeRequest);

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = "getYoutube_Finish";
				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};
		this.adapter.getYoutubeMedia(youtubeRequest, callMetadata);
	},

	/**
	 * Retrieves a list of Twitter tweets (currently hard-coded for Dark Custom)
	 */
	getTwitterFeed : function() {
		this.notifyObservers("getTwitterFeed_Start");

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = "getTwitterFeed_Finish";
				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getTwitterFeed(callMetadata);
	},

	/**
	 * Retrieves a list of polls for a given community
	 */
	getPollsFeed : function() {
		this.notifyObservers("getPollsFeed_Start");

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = "getPollsFeed_Finish";
				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getPollsFeed(callMetadata);
	},

	/**
	 * Retrieves a list of popular media for the active community
	 * @param {number} itemsLimit The maximum number of media to return
	 * @param {string} [sortType] The sorting method to use (if not HIGHEST_RATED)
	 */
	getPopular : function(itemsLimit, sortType) {
		this.notifyObservers("getPopular_Start");

		var mediaRequest = {
			itemsLimit : itemsLimit
		};

		this.setFilters(mediaRequest);
		// if sort type wasn't passed in, default to highest rated
		if (!HD.util.Common.hasValue(sortType)) {
			sortType = HD.SORTS.HIGHEST_RATED;
		}

		mediaRequest.sortType = sortType;

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = mediaResponse.success ? "getPopular_Finish"
						: "getPopular_Error";

				var media = mediaResponse.media;

				var prevMedium = null;
				for ( var i = 0, len = media.length; i < len; i++) {
					var medium = media[i];
					if (medium != null) {
						if (prevMedium != null) {
							medium.previous = prevMedium;
						}
						if (i < len - 1) {
							medium.next = media[i + 1];
						}
					}
					cb.setFacade.call(cb, medium);
					prevMedium = medium;
				}

				mediaResponse.media = media;

				cb.notifyObservers.call(cb, eventName, mediaResponse);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getPopularMedia(mediaRequest, callMetadata);
	},

	/**
	 * Retrieves a list of comments for a specific medium
	 * @param {number|string} mediaId The ID of the medium to retrieve comments for
	 * @param {string} [contentType] The type for the medium
	 */
	getComments : function(mediaId, contentType) {

		this.notifyObservers("getComments_Start");
		var mediaRequest = {
			mediaId : mediaId
		};

		this.setFilters(mediaRequest);

		if (HD.util.Common.hasValue(contentType)) {
			mediaRequest.contentType = contentType;
		}

		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = mediaResponse.success ? "getComments_Finish"
						: "getComments_Error";
				var media = mediaResponse.media;				
				var medium = media[0];

				if ( !medium.comments ) {
					medium.comments = [];
				}
				
				// Replace the anonymous submitter label
				for (var i=0; i<medium.comments.length; i++){
					var comment   = medium.comments[ i ];
					var submitter = comment.submittedBy;
					
					if (submitter.match(/^anonymoususer/i)){
						comment.submittedBy = 'anonymous';
					}
					
					comment.createdDate = cb._fixJsonDate( comment.createdDate );
				}

				// TODO insert into cache setting previous and next
				cb.notifyObservers.call(cb, eventName, medium);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};
		this.adapter.getComments(mediaRequest, callMetadata);
	},
	
	/**
	 * Retrieves extended faceoff data for a given medium
	 * @param {number|string} mediaId The ID of the medium to retrieve faceoff data for
	 */
	getFaceoff : function(mediaId) {
		this.notifyObservers("getFaceoff_Start");
		this.notifyObservers("getComments_Start");
		var cb = this;
		var request = { mediaId: mediaId };
		this.setFilters(request);
		
		// Use the back-end implementation of getFaceoff if available.
		var metadata = {
			callback : function(response) {
				// Set media facades.
				if (response.success) {
					cb.setFacade(response.faceoff);
					for (var i = 0; i < response.media.length; i++) {
						cb.setFacade(response.media[i]);	
					}
				}
				
				var eventName = response.success ? "_Finish" : "_Error";
				
				// need to make sure that there is a 'comments' property, even if empty
				// (dwr handled this, JAXB serialization doesn't)
				if ( response.faceoff && !response.faceoff.comments ) {
					response.faceoff.comments = [];
				}
				
				cb.notifyObservers('getComments' + eventName, response.faceoff || null);
				cb.notifyObservers('getFaceoff' + eventName, response);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};
		this.adapter.getFaceoff(request, metadata);
	},
	
	/**
	 * Uploads a user-submitted piece of medium content (photo/video)
	 * @param {HTMLElement} form "formId" or HTML Form element
	 */
	submitMedium : function(form) {
		this.notifyObservers("submitMedium_Start");
		var type = "";
		var formEl = form;

		if (form.type.value != null && form.type.value != "") {
			type = "?type=" + form.type.value;
		}

		var cb = this;

		HD.util.Common.uploadFile(
						form,
//						this.config.baseUrl + "/fileUpload" + type,
						this.config.baseUrl + this.config.uploadUrl + type,
						function(uploadResponse) {
							
							var eventName = (uploadResponse.success) ? "submitMedium_Finish"
									: "submitMedium_Error";
							var eventData = new Array(5);
							eventData[0] = uploadResponse;
							eventData[1] = formEl.title.value;
							eventData[2] = formEl.story.value;
							eventData[3] = formEl.type.value || '';
							eventData[4] = (formEl.category && formEl.category.value) || '';
							eventData[5] = cb.getUserToken();
							clearTimeout(HD.UploadWidget.progressTimer);
							cb.notifyObservers.call(cb, eventName, eventData);
						});
	},
	
	/**
	 * Sends a user's comment for a given medium
	 * @param {number|string} mediaId The ID of the medium to submit a comment for
	 * @param {string} [contentType] The type for the medium
	 * @param {string} comment The user's comment
	 */
	submitComment : function(mediaId, contentType, comment) {
		this.notifyObservers("submitComment_Start", comment);

		var mediaRequest = {
			mediaId : mediaId,
			comment : comment,
			contentType : contentType
		};

		this.setFilters(mediaRequest);
		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = mediaResponse.success ? "submitComment_Finish"
						: "submitComment_Error";
				var response = null;
				if (mediaResponse.success) {
					// TODO insert into cache setting previous and next
					response = mediaResponse.media[0];
					cb.setFacade.call(cb, response);
				} else {
					response = mediaResponse.errors;
				}

				HD.util.Analytics.track(cb.analyticsCommentString.call(cb,
						response));

				cb.notifyObservers.call(cb, eventName, response);
			},
			parameters : {
				siteName : this.config.siteName,
				token : cb.getUserToken()
			}
		};

		this.adapter.submitComment(mediaRequest, callMetadata);
	},
	
	/**
	 * Rates a given medium
	 * @param {number|string} mediaId The ID of the medium to submit a comment for
	 * @param {string} [contentType] The type for the medium
	 * @param {number} rating The user's rating
	 */
	rateMedia : function(mediaId, contentType, rating) {
		this.notifyObservers("rateMedia_Start");

		var mediaRequest = {
			mediaId : mediaId,
			rating : rating,
			contentType : contentType
		};

		this.setFilters(mediaRequest);
		var cb = this;

		var callMetadata = {
			callback : function(mediaResponse) {
				var eventName = mediaResponse.success
						&& mediaResponse.media != null ? "rateMedia_Finish"
						: "rateMedia_Error";

				var medium = null;
				var media = mediaResponse.media;
				if (media != null) {
					medium = media[0];

					cb.setFacade.call(cb, medium);
					cb.insertItem.call(cb, medium);

					cb.addCachedMedium.call(cb, medium);

					HD.util.Analytics.track(cb.analyticsRatingString.call(cb,
							medium));
				}

				// TODO insert into cache setting previous and next
				cb.notifyObservers.call(cb, eventName, medium);
			},
			parameters : {
				siteName : this.config.siteName,
				token : cb.getUserToken()
			}
		};
		this.adapter.rateMedia(mediaRequest, callMetadata);
	},
	
	/**
	 * Enhances a tasklist object for widget usage
	 * @param {object} [tasklist]
	 *				   A tasklist object
	 * @returns {object} The resulting tasklist
	 */
	_parseTasks: function(tasklist) {		
		var steps = tasklist.steps;
		for (var i = 0; i < steps.length; i++) {
			steps[i].createDate = new Date(steps[i].createDate);
			steps[i].modifiedDate = new Date(steps[i].modifiedDate);
			steps[i]._stepIndex = i;
			for (var j = 0; j < steps[i].tasks.length; j++) {
				steps[i].tasks[j]._stepIndex = i;
				steps[i].tasks[j]._taskIndex = j;
				steps[i].tasks[j].createDate = new Date(steps[i].tasks[j].createDate);
				steps[i].tasks[j].modifiedDate = new Date(steps[i].tasks[j].modifiedDate);
			}
		}
		return tasklist;
	},
	
	/**
	 * Retrieves a list of tasks for a given type/name
	 * @param {string} [type]
	 *				   The tasklist type
	 * @param {string} [name]
	 *				   The tasklist collection name
	 */
	getTasks: function(type, name) {
		var self = this, options = {
			type: type || 'roadmap',
			name: name || 'test'
		};
		this.notifyObservers('getTasks_Start', options);
		if (options.type === 'roadmap') {
			this.restAuthenticate(options.type, null, function(auth) {
				// If authenticated, get the user's roadmap, but otherwise get an empty one.
				var url = '/roadmap/services/retrieve/' + options.name + '.json';
				self.rest.request(
					'GET',
					self.rest.useProxy ? '/Community/services/hd/hd_rest/assets/proxy.jsp?call=' + encodeURIComponent('http://hdgisscwasd01:9202'+url) : url,
					auth.success ? { ilticket: auth.ilTicket } : null,
					function(result) {
						var eventName = 'getTasks_' + (result.success ? 'Finish' : 'Error');
						if (result.success) {
							result = self._parseTasks(result.usermap);
						}
						self.notifyObservers(eventName, result);
					}
				);
			});
		}
	},
	
	/**
	 * Updates a task's status
	 * @param {string} [type]
	 *				   The tasklist type
	 * @param {string} [name]
	 *				   The tasklist collection name
	 * @param {number} taskId
	 *				   The ID of the task to update
	 * @param {string} status
	 *				   The new task status ('C' for completed, 'I' for incomplete, 'S' for skipped)
	 */
	updateTask: function(type, name, taskId, status) {
		var self = this, options = {
			type: type || 'roadmap',
			name: name || 'test',
			taskId: taskId || '',
			status: status || 'I'
		};
		this.notifyObservers('updateTask_Start', options);
		if (options.type === 'roadmap') {
			this.restAuthenticate(options.type, null, function(auth) {
				// Require an authenticated user.
				if (auth.success) {
					var url = '/roadmap/services/updateTask/' + options.name + '/' + options.taskId + '/' + options.status + '.json';
					self.rest.request(
						'GET',
						self.rest.useProxy ? '/Community/services/hd/hd_rest/assets/proxy.jsp?call=' + encodeURIComponent('http://hdgisscwasd01:9202'+url) : url,
						{ ilticket: auth.ilTicket },
						function(result) {
							var eventName = 'updateTask_' + (result.success ? 'Finish' : 'Error');
							if (result.success) {
								result = self._parseTasks(result.usermap);
							}
							self.notifyObservers(eventName, result);
						}
					);
				}
				else {
					self.notifyObservers('updateTask_Error', auth);	
				}
			});
		}
	},
	
	/**
	 * Updates a tasklist's fast track state
	 * @param {string} [type]
	 *				   The tasklist type
	 * @param {string} [name]
	 *				   The tasklist collection name
	 * @param {boolean} isFastTrack
	 *				   Whether to fast track a tasklist
	 */
	setTasksFastTrack: function(type, name, isFastTrack) {
		var self = this, options = {
			type: type || 'roadmap',
			name: name || 'test',
			isFastTrack: isFastTrack || false
		};
		this.notifyObservers('setTasksFastTrack_Start', options);
		if (options.type === 'roadmap') {
			this.restAuthenticate(options.type, null, function(auth) {
				// Require an authenticated user.
				if (auth.success) {
					var url = '/roadmap/services/fastTrack/' + options.name + '/' + options.isFastTrack.toString() + '.json';
					self.rest.request(
						'GET',
						self.rest.useProxy ? '/Community/services/hd/hd_rest/assets/proxy.jsp?call=' + encodeURIComponent('http://hdgisscwasd01:9202'+url) : url,
						{ ilticket: auth.ilTicket },
						function(result) {
							var eventName = 'setTasksFastTrack_' + (result.success ? 'Finish' : 'Error');
							if (result.success) {
								result = self._parseTasks(result.usermap);
							}
							self.notifyObservers(eventName, result);
						}
					);
				}
				else {
					self.notifyObservers('setTasksFastTrack_Error', auth);	
				}
			});
		}
	},
	
	/**
	 * Checks authentication for a REST service
	 * @param {string} [type]
	 *				   The REST service type
	 * @param {object} [params]
	 *				   A collection of options to pass to the REST service
	 * @param {function} callback
	 *				   A callback to execute after checking authentication
	 */
	restAuthenticate: function(type, params, callback) {
		var self = this, options = {
			type: type || 'roadmap',
			params: params || {}
		};
		this.notifyObservers('restAuthenticate_Start', options);
		if (type === 'roadmap') {
			
			// TODO: This is for older communities. Once all community sites are converted to HD.Profile
			// this should be changed
			if(!login){
				login = HD.Profile.profile;
			}
			
			if (login.model.ilTicket) {
				var result = { success: true, ilTicket: login.model.ilTicket };
				this.notifyObservers('restAuthenticate_Finish', result);
				callback(result);
			}
			else {
				var result = { success: false, ilTicket: null };
				this.notifyObservers('restAuthenticate_Error', result);
				callback(result);
			}
		}
	},

	/**
	 * Inserts a medium into the local media cache
	 * @param {object} medium The medium to inject
	 */
	insertItem : function(medium) {
		var cachedMedia = this.media;
		if (cachedMedia != null) {
			for ( var i = 0, len = cachedMedia.length; i < len; i++) {
				if (cachedMedia[i] != null
						&& cachedMedia[i].mediaId == medium.mediaId) {
					// delete cachedMedia[i];
					if (i > 0) {
						medium.previous = cachedMedia[i - 1];
					}
					if (i < len - 1) {
						medium.next = cachedMedia[i + 1];
					}
					cachedMedia[i] = medium;
				}
			}
		}
		this.media = cachedMedia;
	},

	/**
	 * Retrieves a member's profile
	 * @param {string|number} memberId The ID of the member to request
	 */
	getMemberProfile : function(memberId) {
		this.notifyObservers("getMember_Start");

		var profileRequest = {
			memberId : memberId
		};

		this.setFilters(profileRequest);

		var cb = this;

		var callMetadata = {
			callback : function(memberResponse) {
				var profile = memberResponse.profile;
				var eventName = memberResponse.success ? "getMember_Finish"
						: "getMember_Error";

				cb.notifyObservers.call(cb, eventName, profile);
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getMemberProfile(profileRequest, callMetadata);
	},

	/**
	 * Retrieves the updated ratings for an array of media
	 * @param {array} [media] The array of media to retrieve ratings for
	 */
	getRatings : function(media) {
		this.notifyObservers("getRatings_Start");

		// if no media explicitly passed in, assume user wants to get
		// ratings for current media items
		if (media == null) {
			media = this.media;
		}

		var siteName = this.config.siteName;
		var ratings = [];
		for ( var i = 0, len = media.length; i < len; i++) {
			var medium = media[i];
			ratings.push( {
				mediaId : medium.mediaId,
				contentType : medium.contentType,
				siteName : siteName
			});
		}

		var ratingRequest = {
			ratings : ratings
		};

		this.setFilters(ratingRequest);

		var cb = this;
		var mediaCb = media;

		var callMetadata = {
			callback : function(ratingResponse) {
				var eventName = ratingResponse.success ? "getRatings_Finish"
						: "getRatings_Error";

				var ratings = ratingResponse.ratings;
				var media = mediaCb;

				for ( var i = 0, len = media.length; i < len; i++) {
					var medium = media[i];
					var rating = ratings[i];

					if (medium != null && rating != null) {
						medium.currentUserRating = rating.rating;
						cb.addCachedMedium(medium);
						medium.facade.updateRatingWidgets(medium);
					}
				}
			},
			parameters : {
				siteName : this.config.siteName
			}
		};

		this.adapter.getRatings(ratingRequest, callMetadata);
	},

	/**
	 * Retrieves a given medium from the local cache
	 * @param {string|number} mediaId The ID of the medium
	 */
	getCachedMedium : function(mediaId) {
		var medium = this.ratings[mediaId];

		if (medium == null && this.media != null) {
			var media = this.media;
			for ( var i = 0, len = media.length; i < len; i++) {
				var mediumTemp = media[i];
				if (mediumTemp != null && mediumTemp.mediaId == mediaId) {
					medium = mediumTemp;
					break;
				}
			}
		}

		return medium;
	},

	/**
	 * Add a medium to the local cache
	 * @param {object} medium The medium object
	 */
	addCachedMedium : function(medium) {
		this.ratings[medium.mediaId] = medium;
	},
	
	/**
	 * Injects the active set of filters into Community service requests
	 * @param {object} request The request config object
	 */
	setFilters : function(request) {
		var filters = this.filters;
		var config = this.config;

		request.siteName = config.siteName;

		if (filters.startDate != null) {
			request.startDate = filters.startDate;
		}
		if (filters.endDate != null) {
			request.endDate = filters.endDate;
		}
		if (filters.sortType != null) {
			request.sortType = filters.sortType;
		}
		if (filters.tags != null) {
			request.tags = filters.tags;
		}
		if (filters.members != null) {
			request.members = filters.members;
		}
		if (filters.metaTags != null) {
			request.tags = (request.tags || []).concat(filters.metaTags);
		}
		if (filters.author != null) {
			request.author = filters.author;
		}
		if (filters.contentSubtype != null) {
			request.contentSubtype = filters.contentSubtype;
		}
		if (filters.numberToReturn != null) {// newksl: youtube related
			request.numberToReturn = filters.numberToReturn;
		}
		if (config.categories != null) {
			request.categories = config.categories;
		}
		if (config.contentType != null && request.contentType == null) {
			request.contentType = config.contentType;
		}
		if (config.pageSize != null) {
			request.pageSize = config.pageSize;
		}
		if (config.thumbWidth != null) {
			request.thumbWidth = config.thumbWidth;
		}
		if (config.mediaWidth != null) {
			request.mediaWidth = config.mediaWidth;
		}

	},
	
	/**
	 * Sets the active date range filter
	 * @param {object} dateRange The date range to set, i.e. { startDate: new Date(), endDate: new Date() }
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	setDateRange : function(dateRange, skipUpdate, setActive) {
		this.notifyObservers("dateRange_Set", dateRange);
		this.filters.startDate = dateRange.startDate;
		this.filters.endDate = dateRange.endDate;
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Clears the active date range filter
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	clearDateRange : function(skipUpdate, setActive) {
		this.notifyObservers("dateRange_Clear");
		delete this.filters.startDate;
		delete this.filters.endDate;
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Sets the active meta tag filters
	 * @param {array|string} tags A tag or array of meta tags to set as filters
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	setMetaTags : function(tags, skipUpdate, setActive) {
		this.filters.metaTags = typeof tags == 'string' ? [ tags ] : tags;
		if (this.filters.metaTags.length == 0) {
			return this.clearMetaTags(skipUpdate);
		}
		this.notifyObservers("metaTags_Set", this.filters.metaTags);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Clears the active meta tag filters
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	clearMetaTags : function(skipUpdate, setActive) {
		delete this.filters.metaTags;
		this.notifyObservers("metaTags_Clear", []);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Sets the active tag filters
	 * @param {array|string} tags A tag or array of tags to set as filters
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	setTags : function(tags, skipUpdate, setActive) {
		this.filters.tags = typeof tags == 'string' ? [ tags ] : tags;
		if (this.filters.tags.length == 0) {
			return this.clearTags(skipUpdate);
		}
		this.notifyObservers("tags_Set", this.filters.tags);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},

	/**
	 * Clears the active tag filters
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	clearTags : function(skipUpdate, setActive) {
		delete this.filters.tags;
		this.notifyObservers("tags_Clear", []);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Sets the active author filter
	 * @param {string} author The username of the author to filter with
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	setAuthor : function(author, skipUpdate, setActive) {
		this.filters.author = author.userName;
		if (this.filters.author.length == 0) {
			return this.clearAuthor(skipUpdate);
		}
		this.notifyObservers("author_Set", author);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Clears the active author filter
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	clearAuthor : function(skipUpdate, setActive) {
		delete this.filters.author;
		this.notifyObservers("author_Clear", []);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Sets the active content subtype filter (most notably, for filtering for 'video blog' or 'photo blog' entries)
	 * @param {string} contentSubtype The subtype to filter with
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	setContentSubtype : function(contentSubtype, skipUpdate, setActive) {
		this.filters.contentSubtype = contentSubtype;
		if (this.filters.contentSubtype.length == 0) {
			return this.clearContentSubtype(skipUpdate);
		}
		this.notifyObservers("contentSubtype_Set", this.filters.contentSubtype);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},
	
	/**
	 * Clears the active content subtype filter
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	clearContentSubtype : function(skipUpdate, setActive) {
		delete this.filters.contentSubtype;
		this.notifyObservers("contentSubtype_Clear", []);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},

	/**
	 * Sets the active categories filter
	 * @param {array} categories The categories to filter with
	 * @param {boolean} [skipUpdate] Whether to skip updating the page after setting the filter
	 * @param {boolean} [setActive] Whether to an active item with the update request
	 */
	setCategories : function(categories, skipUpdate, setActive) {
		this.config.categories = categories.slice(0);
		this.notifyObservers("categories_Set", categories);
		if (!skipUpdate) {
			this.getMedia(1, null, !setActive);
		}
	},

	/**
	 * Sets the default sorting method
	 * @param {string} sortType The sorting method to use as the default
	 */
	setSort : function(sortType) {
		this.notifyObservers("sortType_Change", sortType);
		this.filters.sortType = sortType;
		this.pageNumber = this.initialPageIndex;
		this.getSamePage();
	},

	/**
	 * Retrieves the default sorting method
	 * @return {string} The active sorting method
	 */
	getSort : function(sortType) {
		return this.filters.sortType;
	},

	/**
	 * Determine whether a given item is in the active page
	 * @param {string|number} itemId The ID of the item to check for
	 */
	isInCurrentPage : function(itemId) {
		var itemExistsInPage = false;
		var media = this.media;

		for ( var i = 0; i < media.length; i++) {

			if (media[i].mediaId == itemId) {

				itemExistsInPage = true;
				break;
			}

		}
		return itemExistsInPage;
	},

	/**
	 * Generates an object describing the current state of the DAO object
	 * @returns {object}
	 */
	_generateState : function() {
		// Only include properties that are valid.
		var state = {
			categories : this.config.categories.join(';')
		};
		if (this.filters.tags) {
			state.tags = this.filters.tags.join(';');
		}
		if (this.filters.members) {
			state.members = this.filters.members.join(';');
		}
		if (this.filters.author) {
			state.author = this.filters.author.join(';');
		}
		if (this.filters.contentSubtype) {
			state.contentSubtype = this.filters.contentSubtype.join(';');
		}
		if (this.filters.startDate) {
			state.startDate = this.filters.startDate;
		}
		if (this.filters.endDate) {
			state.startDate = this.filters.startDate;
		}
		if (this.filters.sortType) {
			state.sortType = this.filters.sortType;
		}
		return state;
	},

	/**
	 * Applies a state object as the current state of the DAO object
	 * @param {object} state The state to apply
	 * @param {boolean} [skipUpdate] Whether to skip updating the active page
	 * @returns {boolean} Whether the application was successful
	 */
	_applyState : function(state, skipUpdate) {
		if (!state) {
			return false;
		}
		if (typeof state == 'string') {
			state = YAHOO.util.Cookie._parseCookieHash(state);
		}
		if (state.categories) {
			this.setCategories(state.categories.split(';'), true);
		}
		if (state.tags) {
			this.setTags(state.tags.split(';'), true);
		}
		if (state.author) {
			this.setAuthor(state.author);
		}
		if (state.contentSubtype) {
			this.setContentSubtype(state.contentSubtype);
		}
		if (state.startDate) {
			this.filters.startDate = state.startDate;
		}
		if (state.endDate) {
			this.filters.endDate = state.endDate;
		}
		if (state.sortType) {
			this.filters.sortType = state.sortType;
		}
		if (!skipUpdate) {
			this.getMedia(1);
		}
		return true;
	},

	/**
	 * Exports the current state as a serialized string
	 * @returns {string}
	 */
	exportState : function() {
		return YAHOO.util.Cookie._createCookieHashString(this._generateState());
	},

	/**
	 * Imports the current state from a serialized string
	 * @param {string} stateString The serialized state string to import
	 * @param {boolean} [skipUpdate] Whether to skip updating the active page
	 * @returns {boolean} successful
	 */
	importState : function(stateString, skipUpdate) {
		return this._applyState(stateString, skipUpdate);
	},

	/**
	 * Stores the current state to a cookie
	 * @param {string} [name] The name of the cookie to store the state to (or the active siteName)
	 * @returns {boolean} successful
	 */
	storeState : function(name) {
		return YAHOO.util.Cookie.setSubs(name || this.config.siteName, this
				._generateState());
	},

	/**
	 * Restores the current state from a cookie
	 * @param {string} [name] The name of the cookie to store the state to (or the active siteName)
	 * @param {boolean} [skipUpdate] Whether to skip updating the active page
	 * @returns {boolean} successful
	 */
	restoreState : function(name, skipUpdate) {
		return this._applyState(YAHOO.util.Cookie.getSubs(name
				|| this.config.siteName), skipUpdate);
	},

	/**
	 * Clears the current state cookie
	 * @returns {boolean} successful
	 */
	clearState : function(name) {
		return YAHOO.util.Cookie.remove(name || this.config.siteName);
	},

	/** Analytics event when an item is rated */
	analyticsRatingString : function(item) {
		return "default_analyticsRatingString";
	},

	/** Analytics event when a comment is submitted */
	analyticsCommentString : function(item) {
		return "default_analyticsCommentString";
	},
	
	getUserToken : function(){
		var token = '';
		if (HD.Profile){
			token = HD.Profile.token || '';
		}
		 return token;
	},
	
	/**
	 * This method takes some data from the server and, if it's a string, tries to
	 * parse it into a Date object. If anything goes wrong, the original, incoming
	 * data is returned.
	 */
	_fixJsonDate: function( dateIn ) {
		var dateOut = dateIn;
		
		if ( dateIn ) {
			if ( 'string' == typeof( dateIn ) ) {
				try {
					dateOut = new Date( Date.parse( dateIn ) );
				}
				catch ( ex ) {
				}
			}
		}
		
		return dateOut;
	}
};

HD.extend(HD.CommunityDAO, [ HD.util.Observable ]);

HD.register('hd_community_model', 'HD.CommunityDAO', {
	version : "1.0",
	build : "1"
});
