//
//  Sitelife
//
//  Created by Cannon, Ryan on 2009-06-18.
//  Copyright (c) 2009 NFL Enterprises, LLC. All rights reserved.
//
nfl.namespace("sitelife");
if ( Object.isUndefined(RequestBatch) ) {
	throw new Error("This script requires Pluck");
}

/** 
 * isTrue
 * 
 * Pluck sends boolean values as "True" and "False" this is a conveniance menthod
 * for checking boolean values. This method operates correctly if an actual boolean
 * value is passed to it as well.
 * 
 * @param {String value} The string to test.
 */
nfl.sitelife.isTrue = function( value ) {
	return Object.isString(value) ? value === "True" : value;
};
/**
 * Keyify
 * 
 * A static method that returns an iterator to transform arrays
 * into arrays of Pluck classes. For example:
 *
 * (['Trusted', 'Editor']).map(nfl.sitelife.Keyify(UserTier)) -> Array
 *
 * @param {Class className} a Class that takes single argument as a constructor
 * @returns {Function} an iterator that can map an array to an array of Pluck objects
 */
nfl.sitelife.Keyify = function ( className ) {
	return function( el ) { return new className( el ); };
};
nfl.sitelife.MESSAGING = {
   	TOO_LONG: new Template('Your #{type} is #{length} characters long. Please limit your #{type} to #{maxLength} characters.'),
   	BLANK: new Template('Please enter a #{type}.'),
   	ABUSIVE: new Template('This #{type} is abusive and has been removed.'),
	NO_ARTICLE: new Template("A #{type} must be associated with an article.")
};
nfl.sitelife.PLUCK_COMMENT_KEY_PARAM = /[\?\&]plckFindCommentKey=CommentKey:[^\&]*/,
/**
 * BatchManager
 *
 * Handles several Pluck requests on a single page. Pluck allows a maximum
 * of 20 batch calls, only one of which may be a paginated list, this class
 * handles this logic, optimizing the number of requests.
 */
nfl.sitelife.BatchManager = (function () {
	var SITELIFE_URL = nfl.global.SITELIFE_URL + '/ver1.0/Direct/Process',
	    BATCH_TO_DUR = 1000,
	    LIST_ACTIONS = [ 'CommentPage', 'DiscoverContentAction', 'SearchAction', 'ReviewPage', 'RecentUserActivity', 'FriendPage', 'PersonaMessagePage', 'ForumDiscussionsPage', 'ForumsPage' ],
	    MAX_BATCHES  = 20,
	    callbacks    = new Hash(),
	    request,
	    batchTO,
	    hasList      = false,
	    BM;
	
	//setInterval(function() {console.log('callbacks monitor', callbacks.toArray())}, 10000);
	
	function getCallback (_uid) {
		return function( _response ) {
			if (!BM.silent) {
				try { console.log('Pluck response!', _response); } catch(e) {}
			}
			callbacks.get(_uid).each(function(_cb) { if (_cb) { _cb(_response); } });
			callbacks.unset(_uid);
		};
	}

	function createRequest () {
		request = new RequestBatch();
		hasList = false;
		callbacks.set(request.UniqueId, []);
		return request;
	}

	function sendRequest () {
		if (!BM.silent) {
			try { console.log('Pluck request!', request); } catch(e) {}
		}
		request.BeginRequest( SITELIFE_URL, getCallback(request.UniqueId) );
		createRequest();
	}
	
	function hasCommentPage(_r) { return _r.CommentPage; }
	function hasDiscoveredContent(_r) { return _r.DiscoverContentAction; }
	function requestHasAction( _action) { return ! Object.isUndefined(this[_action]); }

	BM = {
		/**
		 * Adds a new _request to the global Pluck batch, if one exists. Will
		 * execute the current request if one exists and is full, otherwise
		 * it will batch all requests. After _to milliseconds (or the default)
		 * it will send the request to pluck, calling _callback with the
		 * response object as its first argument. If the _request returns a
		 * paginated list, the batch will be sent immediately.
		 *
		 * Note that each _callback should loop through response.Responses to
		 * find the correct response.
		 *
		 * Example usage:
		 *
		 *    nfl.sitelife.BatchManager.AddToRequest( new ArticleKey('myArticle'), myCallBack );
		 *
		 * @param   {Object _request} a Pluck request object
		 * @param   {Function _callback} a callback to take action on a successful request.
		 * @param   {Number _to} the number of milliseconds to wait before making the request. Default 1000.
		 * @param   {Boolean _noDupe} if true, will not add the callback to they stack if it is already there.
		 * @returns {BatchManager} a reference to BatchManager, to allow chaining
		 */
		AddToRequest: function( _request, _callback, _to, _noDupe ) {
			var newRequestHasList = false;

			// If we have a timeout to send the request, clear it
			if ( batchTO ) { clearTimeout( batchTO );}
			
			// If we don't have a request, create it
			if ( Object.isUndefined(request) ) { createRequest(); }

			// If the new request is a paginated request and we already have one, send the request now.
			newRequestHasList = LIST_ACTIONS.any(requestHasAction, _request);
			if (hasList && newRequestHasList) {
				sendRequest();
			}
			// If the new request has a list, remember that
			hasList = newRequestHasList || hasList;
			
			// If we have a callback, add it to the callback stack
			if ( _callback && ! (_noDupe && callbacks.values().indexOf(_callback) > -1)) {
				callbacks.get(request.UniqueId).push(_callback);
			}
			
			// Otherwise, add the request. If we've reached our batch limit, fire it off.
			request.AddToRequest( _request );
			if ( (request && request.Requests.length === MAX_BATCHES) ) {
				sendRequest();
			}
			
			// Otherwise, set a timeout to send the request
			else {
				batchTO = setTimeout( sendRequest, Object.isUndefined(_to) ? BATCH_TO_DUR : _to );
			}
			
			// allows for chaining
			return BM;
		},
		/**
		 * Adds an array of new requests to the global Pluck batch, if one exists. If
		 * the batch fills up, or if a paginated request is made, the batch will be fired
		 * and a new batch will be created. The _callback will only be called one per batch.
		 *
		 * Note that each _callback should loop through response.Responses to
		 * find the correct responses.
		 *
		 * Example usage:
		 *
		 *    nfl.sitelife.BatchManager.AddRequests(
		 *      [
		 *        new ArticleKey('article1'),
		 *        new AritcleKey('article2')
		 *      ], myCallBack
		 *    );
		 *
		 * @param   {Array _requests} an array of Pluck request objects
		 * @param   {Function _callback} a callback to take action on a successful request.
		 * @param   {Number _to} the number of milliseconds to wait before making the request. Default 1000.
		 * @returns {BatchManager} a reference to BatchManager, to allow chaining
		 */
		AddRequests: function( _requests, _callback, _to) {
			var i         = 0,
			    l         = _requests.length;
			
			for (i, l; i < l; i++) {
				// Add the request to the batch. If the requestIndex is less than the
				// previous index, add the callback again.
				requestId = BM.getRequest().UniqueId;
				BM.AddToRequest(_requests[i], _callback, _to, true);
			}
		},
		/**
		 * Given a Pluck _response and an article _id, returns the last
		 * Article contained in the responses, or undefined if none.
		 */
		findArticlePage: function( _response, _id ) {
			var desiredResponse = _response.Responses.findAll(function(_r) { return _r.Article && _r.Article.ArticleKey.Key === _id; }).last();
			return desiredResponse ? desiredResponse.Article : undefined;
		},
		
		/**
		 * Given a Pluck _response, returns the _type contained in the
		 * responses, or undefined if none.
		 */
		find: function( _response, _type ) {
			var desiredResponse = _response.Responses.find(function(_r) { return _r[_type]; });
			return desiredResponse ? desiredResponse[_type] : undefined;
		},

		/**
		 * Wraps the DiscoverContent action to make it more sane and
		 * user-friendly.
		 *
		 *   var BM = nfl.sitelife.BatchManager;
		 *   BM.discoverContent(options, function (response) {
		 *     var dca = BM.find(response, 'DiscoverContentAction');
		 *   });
		 *
		 * @param   {Object _config} options for the discover content action.
		 * @param   {Function _callback} a callback to take action on a successful request.
		 * @param   {Number _to} the number of milliseconds to wait before making the request. Default 1000.
		 * @returns {BatchManager} a reference to BatchManager, to allow chaining
		 */
		discoverContent: (function () {
			var DEFAULT_MAX        = 10;
			var DEFAULT_AGE        = 15;
			var DEFAULT_SECTIONS   = [ 'All' ]; // e.g. News, Videos
			var DEFAULT_CATEGORIES = [ 'All' ];
			var DEFAULT_USER_TIERS = [ 'Standard', 'Trusted', 'Featured', 'Editor' ];
			var DEFAULT_ACTIVITY   = 'Recent'; // Commented, Reviewed, Recommended, Rated
			var DEFAULT_CONTENT_TYPE = 'Article';
			
			return function( _options, _callback, _to ) {
				var keyify = nfl.sitelife.Keyify;
				
				BM.AddToRequest(
					new DiscoverContentAction(
						( _options.sections || DEFAULT_SECTIONS ).map( keyify( Section ) ),
						( _options.categories || DEFAULT_CATEGORIES ).map( keyify( Category ) ),
						( _options.userTiers || DEFAULT_USER_TIERS ).map( keyify( UserTier ) ),
						new Activity( _options.activity || DEFAULT_ACTIVITY ),
						new ContentType( _options.contentType || DEFAULT_CONTENT_TYPE ),
						_options.age || DEFAULT_AGE,
						_options.max || DEFAULT_MAX
					), _callback, _to
				);
				// allows for chaining
				return BM;
			};
		})(),
		
		/**
		 * getRequest
		 *
		 * Returns the current request. Calling this cancels sending
		 * the request.
		 *
		 * @returns {RequestBatch} the currently queued Pluck request.
		 */
		getRequest: function() {
			var r = request ? request : createRequest();
			if ( batchTO ) {
				clearTimeout( batchTO );
				batchTO = null;
			}
			return r;
		},
		
		/**
		 * updateUserProfile
		 *
		 * Updates a user profile with the supplied values.
		 *
		 * @param {String _oldUser} the user object to update
		 * @param {Object _values} the values to update
		 * @param {Function _callback} a callback to take action on a successful request.
		 * @param {Number _to} the number of milliseconds to wait before making the request. Default 1000.
		 *
		 * @returns {Object} the updated user object.
		 */
		updateUserProfile: (function () {
			
			var DEFAULT_CUSTOM_ANSWERS  = $H({
				"Greatest NFL memory": "",
				"College": "",
				"Favorite player": "",
				"Read messages": "0"
			});
			
			function getCustomAnswers (_acc, _pair) {
				_acc[_pair.key] = this.get(_pair.key) || _pair.value; 
				return _acc;
			}
			
			return function(_oldUser, _values, _callback, _to) {
				
				var newCustomAnswers  = DEFAULT_CUSTOM_ANSWERS.inject({}, getCustomAnswers, $H(_oldUser.CustomAnswers).merge(_values) ),
				    newUser           = $H(_oldUser).merge(_values).toObject();
				
				nfl.sitelife.BatchManager.AddToRequest(
					new UpdateUserProfileAction(
						new UserKey(_oldUser.UserKey.Key),
						newUser.AboutMe,
						newUser.Location,
						newUser.Signature || "",
						newUser.DateOfBirth,
						newUser.Sex || "None",
						newUser.PersonaPrivacyMode,
						newUser.CommentsTabVisible,
						newUser.PhotosTabVisible,
						nfl.sitelife.isTrue(newUser.MessagesOpenToEveryone),
						nfl.sitelife.isTrue(newUser.IsEmailNotificationsEnabled),
						newUser.SelectedStyleId,
						newCustomAnswers,
						newUser.ExtendedProfile
					),
					_callback,
					_to
				);
				
				return newUser;
			};
		}()),
		/**
		 * syncArticle
		 *
		 * Updates a user profile with the supplied values.
		 *
		 * @param {String _oldUser} the user object to update
		 * @param {Object _values} the values to update
		 * @param {Function _callback} a callback to take action on a successful request.
		 * @param {Number _to} the number of milliseconds to wait before making the request. Default 1000.
		 *
		 * @returns {Object} the updated user object.
		 */
		syncArticle: (function () {
			
			function getCategory (name) {
				return new Category(name);
			}
			
			return function (pluckArticle, data) {
				if (! BM.silent) {
					try { console.log('[BatchManager] syncArticle()', pluckArticle, data); }
					catch(e) {}
				}

				if (
					pluckArticle && 
					pluckArticle.Section &&
					data.headline                                   === pluckArticle.PageTitle &&
					data.url                                        === pluckArticle.PageUrl &&
					data.section.toLowerCase()                      === pluckArticle.Section.Name &&
					data.categories.invoke('toLowerCase').join('|') === pluckArticle.Categories.pluck('Name').join('|')
				) { return; }
				
				if (! BM.silent) {
					try {
						console.log('old', pluckArticle.PageTitle, pluckArticle.PageUrl, pluckArticle.Section ? pluckArticle.Section.Name : '', pluckArticle.Categories.pluck('Name'));
						console.log('new', data.headline, data.url, data.section.toLowerCase(), data.categories.invoke('toLowerCase'));
						console.log(
							!!pluckArticle,
							!!pluckArticle.Section,
							data.headline                                   === pluckArticle.PageTitle,
							data.url                                        === pluckArticle.PageUrl,
							data.section.toLowerCase()                      === pluckArticle.Section.Name,
							data.categories.invoke('toLowerCase').join('|') === pluckArticle.Categories.pluck('Name').join('|')
						);
					}
					catch(e) {}
				}
				
				BM.AddToRequest(new UpdateArticleAction(
					new ArticleKey(pluckArticle.ArticleKey.Key),
					data.url,
					data.headline,
					new Section(data.section),
					data.categories.map(getCategory)
				));
			}
		}()),
		silent: false
	};
	return BM;
}());
nfl.sitelife.Paginator = (function() {
	var MAX_PAGES_DISPLAYED = 5,
	    PAGINATION_LINK     = /#page:(\d+)$/,
	    PER_PAGE            = 10,
	    SPREAD              = 2;

	function onPageChange(_e) {
		var a = _e.findElement('a');
		if ( a === document || (!a) || (! PAGINATION_LINK.test(a.href)) ) { return; }
		_e.stop();
		this.callback.call(this, a.href.match(PAGINATION_LINK)[1]);
	}

	function getLocationHash(_page) {
		return window.location.toString().replace(/(?:#\S*)?$/, '#page:' + _page);
	}
	
	function getPaginatedHTML(_acc, _page) {
		return _acc + this.template.evaluate({
			className: _page === this.page ? 'current page' : 'page',
			pageURL: getLocationHash(_page),
			pageName: _page
		});
	}
	
	return Class.create({
		element: function() { return $(this.el); },
		initialize: function(_config) {
			var el        = $(_config.el);
			this.el       = el.identify();
			this.template = el.templatize();
			this.spread   = _config.spread || SPREAD;
			this.maxPages = _config.max || MAX_PAGES_DISPLAYED;
			this.callback = _config.callback || Prototype.emptyFunction;
			this.perPage  = _config.perPage || PER_PAGE;
			el.observe('click', onPageChange.bindAsEventListener(this));
		},
		update: function(_config) {
			var page       = parseInt(_config.page, 10),
			    perPage    = parseInt(_config.perPage, 10),
			    total      = parseInt(_config.total, 10),
			    pages      = Math.ceil(total / perPage),
			    pagination = this.element(),
			    html       = "",
			    start, end;
			
			// if there's only one page, hide pagination and you're done
			if (total <= perPage) {
				return pagination.hide().update('');
			}

			// if the current page isn't 1, add previous
			if ( page > 1 ) {
				html += this.template.evaluate({ className: 'previous', pageURL: getLocationHash(page - 1), pageName: 'Previous' });
			}
			
			// figure out the first page to show
			start = page - this.spread;
			if (pages - page < this.spread && page - this.spread > 0) {
				start -= this.spread - (pages - page);
			}
			if (start < 1) { start = 1; }

			// figure out the last page to show
			end = page + this.spread;
			if (end > pages || pages < this.max) { end = pages; }
			else if (end < this.max && pages >= this.max) {
				end = this.max;
			}
			
			// for each of the pages to show, add them
			html += $R(start, end).inject('', getPaginatedHTML, { page: page, template: this.template });
			
			// if we're not on the last page, add next
			if ( page < pages ) {
				html += this.template.evaluate({ className: 'next', pageURL: getLocationHash(page + 1), pageName: 'Next' });
			}
			
			// update and show the result
			pagination.update(html).show();
		}
	});
})();
nfl.sitelife.Reporter = (function (BM) {
	
	var KEYS = [ 'ArticleKey', 'BlogPostKey', 'CommentKey',
	             'CommunityGroupKey', 'CustomItemKey', 'EventKey',
	             'ForumPostKey', 'PhotoKey', 'PollKey', 'ReviewKey',
	             'UserKey', 'VideoKey' ],
	    MAX_DESC_LENGTH = 3000;
	
	function onFormSubmit (_e) {
		var data     = _e.element().serialize({ hash: true }),
		    keyClass = window[data.keyType],
		    length   = data.abuseDescription.length;
		
		_e.stop();
		
		if (! KEYS.include(data.keyType)) { // dev error
			throw new Error('Reporter: Could not submit form, invalid key type.');
		}
		else if (data.abuseReason.blank()) { // user error
			return alert(nfl.sitelife.MESSAGING.BLANK.evaluate({ type: 'reason' }));
		}
		else if (length > MAX_DESC_LENGTH) { // user error
			return alert(nfl.sitelife.MESSAGING.TOO_LONG.evaluate({ type: 'description', length: length, maxLength: MAX_DESC_LENGTH }));
		}

		BM.AddToRequest(
			new ReportAbuseAction(
				new keyClass(data.keyValue),
				data.abuseReason,
				data.abuseDescription
			),
			this._onPluck
		);
	}
	
	function onPluckResponse (_response) {
		var message = _response.Messages[0].Message,
		    el      = this.element();

		el.elements['abuseReason'].selectedIndex = 0;
		el.elements['abuseDescription'].value = '';
		this.onReport('ok' === message, message);
	}
	
	return Class.create({
		element: function() {
			return $(this.id)
		},
		initialize: function(_config) {
			var el        = $(_config.id);
			
			this.id       = el.identify();
			this._onPluck = onPluckResponse.bind(this);
			this.onReport = _config.onReport || Prototype.emptyFunction;
			this.onToggle = _config.onToggle || Prototype.emptyFunction;
			this.showing  = false;
			
			el.
				observe('submit', onFormSubmit.bindAsEventListener(this)).
				hide().
				down('div.close').observe('click', this.onToggle.bindAsEventListener(this));

			return this;
		},
		setKeyType: function(_value) {
			this.element().elements['keyType'].value = _value;
			return this;
		},
		setKeyValue: function(_value) {
			this.element().elements['keyValue'].value = _value;
			return this;
		},
		setParent: function(_el) {
			$(_el).appendChild(this.element());
			return this;
		},
		hide: function() {
			this.showing = false;
			this.element().hide();
			return this;
		},
		show: function() {
			this.showing = true;
			this.element().show();
			return this;
		}
	});
}(nfl.sitelife.BatchManager));

