//
//  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");
}

nfl.sitelife = {
	/** 
	 * 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.
	 */
	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
	 */
	Keyify: function ( className ) {
		return function( el ) { return new className( el ); };
	},
	/**
	 * 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.
	 */
	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' ],
		    MAX_BATCHES  = 20,
		    callbacks    = [],
		    request,
		    batchTO;
		
		function getCallback (_uid) {
			return function( _response ) {
				try { console.log('Pluck response!', _response); } catch(e) {}
				callbacks[_uid].each(function(_cb) { if (_cb) { _cb(_response); } });
				// callbacks[_uid] = null;
			};
		}
	
		function createRequest () {
			request = new RequestBatch();
			callbacks[request.UniqueId] = [];
		}
	
		function sendRequest () {
			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; }
	
		return {
			/**
			 * 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.
			 * @returns {BatchManager} a reference to BatchManager, to allow chaining
			 */
			AddToRequest: function( _request, _callback, _to ) {
				var hasList = false;

				if ( batchTO ) { clearTimeout( batchTO );}
				if ( Object.isUndefined(request) ) { createRequest(); }
				if ( _callback ) {
					callbacks[request.UniqueId].push(_callback);
				}
				request.AddToRequest( _request );
				hasList = LIST_ACTIONS.any(function( _action) { return ! Object.isUndefined(_request[_action]); });

				if ( (request && request.Requests.length === MAX_BATCHES) || hasList ) {
					sendRequest();
				}
				else {
					batchTO = setTimeout( sendRequest, Object.isUndefined(_to) ? BATCH_TO_DUR : _to );
				}
				
				// allows for chaining
				return nfl.sitelife.BatchManager;
			},
			/**
			 * 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.
			 *
			 * @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' ];
				var DEFAULT_CATEGORIES = [ 'All' ];
				var DEFAULT_USER_TIERS = [ 'Standard', 'Trusted', 'Featured', 'Editor' ];
				var DEFAULT_ACTIVITY   = [ 'Recent' ];
				var DEFAULT_CONTENT_TYPE = 'Article';
				
				return function( _options, _callback, _to ) {
					var keyify = nfl.sitelife.Keyify,
					    BM     = nfl.sitelife.BatchManager;
					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;
				if ( batchTO ) { clearTimeout( batchTO );}
				batchTO = request = 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;
				};
			}())
		};
	}()),
	PLUCK_COMMENT_KEY_PARAM: /[\?\&]plckFindCommentKey=CommentKey:[^\&]*/,
	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();
			}
		});
	})(),
	Reporter: (function () {
		
		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;
			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;
				
				el.
					observe('submit', onFormSubmit.bindAsEventListener(this)).
					hide();

				return this;
			},
			setKeyType: function() {
				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.element().hide();
				return this;
			},
			show: function() {
				this.element().show();
				return this;
			}
		});
	}()),
	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.")
    }
};
