jQuery.noConflict();

// Prevent IE from breaking w/ console.logs
if (typeof console == 'undefined') {
    console = {
        log: function(){
        }
    };
}


/**
 @overview This file contains all classes/objects/logic/etc for the DMP forum component
 */
if (typeof DMP == 'undefined') {
    DMP = {};
}

(function($jq){
	
    /**
     Helper function for scoping functions
     @param Object obj The scope the function should be executed in
     @param Function fn The function to be scoped.
     */
    function _scope(obj, fn){
        return function(){
            fn.apply(obj, arguments);
        };
    };
    	
	/**
	 * Helper function to populate a HTML template with data
	 * @param {String} template The HTML template to populate / format
	 * @param {Object} data
	 */
	function _renderData(template, data) {
		
		var regex;
		for(var key in data)
		{	
			regex = new RegExp('\{' + key + '\}', 'g');
			template = template.replace(regex, data[key]);
		}
		
		return template;
	};	
	
	/**
		Removes all tags from a request
	*/
	function _cleanStr(str)
	{
		return str.replace(/<(.|\n)*?>/g,'');
	};
    /**
     @classDescription This class is used to create a Discussion Forum
     @constructor
     @param {Object} config The config for a forum
     	@param {String} config.containerId The id of the div container that will contain the forum
     	@param {String} config.createButton The selector for the create button (used to create a new topic)
     	@param {String} config.reportAbuse The selector for the "report abuse" link
     	@param {Integer} [config.maxTopics] The maximum number of topics that can display.  The default is five
     		
     	@param {Object} config.accordion The configuration for the accordion
     		@param {String} config.accordion.id The id of the accordion for all the different topics
     		@param {String}	current The current element to display
			@param {String}	header Selector for the header element.
			@param {String}	selectedClass Class for active header elements.
			@param {String}	alwaysOpen Whether there must be one content element open. Allows collapsing the active section by the triggering event (click is the default).
			@param {String} clearStyle If set, clears height and overflow styles after finishing animations. This enables accordions to work with dynamic content. Won't work together with autoheight.
     		
     	@param {Object} config.topic The configuration for a topic
     		@param {String} config.id The selector for the element that contains a topic id	
     		@param {String} config.newTopicTemplate The selector for the HTML element that has the new topic template and form
	     	@param {String} config.topicHeaderTemplate The selector for the HTML element that has the topic header template
     		@param {String} config.topicPanel The selector for the topic panel
     		@param {String} config.topicBody The selector for the topic body
     		@param {String} config.newTopicSubmitBtn The selector for the new topic submit button
	     	@param {String} config.newTopicCancelBtn The selector for the new topic cancel button
	     	
     	@param {Object} config.post The configuration for a post
     		@param {String} config.id The selector for the element that contains a post id
     		@param {String} config.postTemplate The selector for the HTML element that has the forum post template
     		@param {String} config.createReply The selector for the "reply to" link
		    @param {String} config.replySubmitBtn The selector for the reply submit button
		    @param {String} config.replyCancelBtn The selector for the cancel submit button
		    @param {String} config.replyTemplateSelector The selector for the create reply form
		    		
     	@param {Object} config.pagination The configuration for pagination
     		@param {Integer} total The total number of posts for the topic
			@param {Integer} items_per_page The total number of items to display per page
			@param {String}	link_to The url to next page of data
			@param {String} prev_text The text to display for the "previous" link
			@param {String} next_text The text to display for the "next" link
			@param {Integer} num_display_entries Number of page links to display at a time
			@param {Integer} num_edge_entries Number of page links to display at the edge
			@param {String} [currentCountBox] Selector for the box that will contain the current count.  For example 1 - 30 of 2000
			@param {String} [currentCountText] The template used for the current text.  Three tokens can be used {startIndex} {endIndex} and {total}
     */
    DMP.Forum = function(config){		
	
		/**
		 * 
		 * @property {Object}
		 */
		this._config = config;
		
		/**
		 * 
		 * @property {jQuery}
		 */
        this._$forum = jQuery(config.id);
		
		/**
		 * 
		 * @property {jQuery}
		 */
        this._$forumContainer = jQuery(config.containerId);
        
		/**
		 * 
		 * @property {jQuery}
		 */
        this._$accordion = jQuery(config.accordion.id);

		/**
		 * 
		 * @property {jQuery}
		 */
        this._$createButton = jQuery(config.createButton);
		
		/**
		 * 
		 * @property {Integer}
		 */
        this._maxTopics = config.maxTopics || 5;
				
		/**
		 * 
		 * @property {jQuery}
		 */		
        this._$newTopicForm;
        
		/**
		 * 
		 * @property {jQuery}
		 */		
        this._$newReplyForm;
		
		/**
		 * 
		 * @property {String}
		 */		
        this._config.newTopicTemplate = jQuery(config.topic.newTopicTemplate).html();
		
		/**
		 * 
		 * @property {String}
		 */
		this._config.topicHeaderTemplate = jQuery(config.topic.topicHeaderTemplate).html();
		
		/**
		 * 
		 * @property {jQuery}
		 */
        this._$topics = this._$accordion.children();

		/**
		 * 
		 * @property {jQuery}
		 */
		this._$firstPageTopics = this._$topics;
		
		
		this._config.post.replyFormTemplate = jQuery(config.post.replyFormTemplate).html();				
		 
        this._config.postTemplate = jQuery(config.post.postTemplate).html();
		
		
		// Initialize the Accordion		
        this._initAccordion();
		
		// Add a click delegate on the entire forum
		this._$forumContainer.bind('click.forum', _scope(this, this._clickDelegate));
		
		// Add a click handler to the create topic forum
		this._$createButton.bind('click.forum.createTopic', _scope(this, this._createTopicForm));
		
		// Initialize pagination
		this._createPagination();		
		
		// Initialize the forum list (if present)
		this._initForumList();
		
		// Initialize the global error handler
		var me = this;		
		jQuery(this._config.globalErrors).ajaxError(function(e, req, settings) {			

			var regex = new RegExp('^' + [
				me._config.pagination.link_to,
				me._config.post.action,
				me._config.topic.action,
				me._config.topic.allPostsAction,
				me._config.nickname.action
			].join('|'));
			// Make sure the error came from one of the forum's ajax requests
			if(/^/i.test(settings.url)){
				me._ajaxEnd();
				jQuery(this).html('Sorry, an unexpected error has occurred');
			}
			
		});
		
		// Initialize the nickname form
		if(!config.hasNickname){
			
			this._initNicknameForm();
		}
    };
    
    DMP.Forum.prototype = {
    
        /**
         Main event delegate for the entire forum.  This removes the need to unbind multiple events when new topics are created and removed.  All events that occur within the bounds of the forum will be routed here.
         @param EventObject e The event object returned by jQuery
         */
        _clickDelegate: function(e){
			
            var $target = jQuery(e.target);
			var topicInfo;			

			// Accordion link.  Can't use the default accordion click handler because we only want clicks on certain elements
			// to update an accordion.  To disable the click handler, the "event" config property of the accordion is set to a custom dummy event
			// that is never triggered.
			if ($target.is(this._config.accordion.headerLink)) 
			{
				// Get the header element
				$target = $target.is(this._config.accordion.header) ? $target : $target.parents(this._config.accordion.header + ':first');
				
				var index = 0;
				var parent = $target.parent()[0];
				
				// Get the index of the current target
				this._$accordion.children().each(function(key, val){

					if(this == parent){
						return false;
					}
					index++;
					
				});									
				
				this._$accordion.accordion('activate', index);

                e.stopPropagation();
				e.preventDefault();
            }
			// Only handle events for any of the auth required functionality if the appropriate config values have been set

            // Create Topic Submit Button
            if ($target.is(this._config.topic.newTopicSubmitBtn)) 
			{
				topicInfo = this._getTopicInfo($target, topicInfo);
				this.submitNewTopic(topicInfo);

                e.stopPropagation();
				e.preventDefault();
            }
            // Create Topic Cancel Button
            else if ($target.is(this._config.topic.newTopicCancelBtn)) {
							
				topicInfo = this._getTopicInfo($target, topicInfo);
				var cancel = confirm('Are you sure you want to cancel?');
				if(!cancel){return}
				this._deleteTopicForm();
                e.stopPropagation();
				e.preventDefault();
            }
            // Create Reply			
           else if ($target.is(this._config.post.createReply)) {
		   	
				// Make sure the user is logged in before showing the reply for
			   	if(!this._config.isLoggedIn){					
					location.href = this._config.loginUrl;
					e.preventDefault();
					return;
				}
				
				// Make sure the user has a nickname before shoing the reply form
			   	if(!this._config.hasNickname){
					
					this.nicknameMgr.showOverlay($target);
					e.preventDefault();
					return;
				}
				
				topicInfo = this._getTopicInfo($target);
				this._createReplyForm(topicInfo);
				
                e.stopPropagation();
                e.preventDefault();
            }
			// Reply To Topic Button
			else if($target.is(this._config.topic.replyToTopicBtn)){
				
				// Make sure the user is logged in before showing the reply for
			   	if(!this._config.isLoggedIn){					
					location.href = this._config.loginUrl;
					e.preventDefault();
					return;
				}
				
				// Make sure the user has a nickname before showing the reply form
			   	if(!this._config.hasNickname){
					
					this.nicknameMgr.showOverlay($target);
					e.preventDefault();
					return;
				}
				
				// This creates a reply for the very first post
				topicInfo = this._getTopicInfo($target);
				var $firstPost = topicInfo.$topic.find(this._config.post.postContainer + ':first');
	         	var firstPostId = $firstPost.children(this._config.post.id).text();				
				
				topicInfo.$post = $firstPost;
				topicInfo.postId = firstPostId;				

				this._createReplyForm(topicInfo);
				
                e.stopPropagation();
                e.preventDefault();
			}
            // Reply Submit Button
            else if ($target.is(this._config.post.replySubmitBtn)) {
				
				topicInfo = this._getTopicInfo($target);
				this.submitReplyForm(topicInfo);
                e.stopPropagation();
				e.preventDefault();
            }
            // Reply Cancel Button
            else if ($target.is(this._config.post.replyCancelBtn)) {					
				
				topicInfo = this._getTopicInfo($target);
				var cancel = confirm('Are you sure you want to cancel?');
				if(!cancel){return}
                this._deleteReplyForm(topicInfo);
                e.stopPropagation();
				e.preventDefault();
            }
			// Expand / Collapse Replies
			else if($target.is(this._config.post.replyCounterBtn)){					
				
				$target.parents(this._config.post.childContainer + ':first').toggleClass(this._config.post.collapseClassName);
				e.preventDefault();
				e.preventDefault();
			}
            // Report Abuse
            else if ($target.is(this._config.post.reportAbuse)) {
				topicInfo = this._getTopicInfo($target);
				this.reportAbuse(topicInfo, $target);
                e.stopPropagation();
                e.preventDefault();
            }		
			// Mail to
			else if($target.is(this._config.post.mailto))
			{
				topicInfo = this._getTopicInfo($target);
				var title = encodeURIComponent(topicInfo.$topic.children(this._config.topic.topicPanel).find(this._config.topic.topicTitle).text());
				var emailWin = window.open('mailto:?subject=Check out this forum topic -  ' + title + '&body=' + title + '%0A' + location.href, 'emailWindow');
				if (emailWin != null || emailWin != undefined) {
					emailWin.close();
				}
				e.preventDefault();
			}

        },
        
        /**
         *  Callback for the create topic button.  Creates a new topic form
         */
        _createTopicForm: function(e){			
					
			e.preventDefault();
            // Disable the create topic button until the current one has been created
            if (this._$newTopicForm) {
                return;
            }
			
			// Delete any lingering reply forms
			if(this._$newReplyForm){
				this._deleteReplyForm(this._$newReplyForm.topicInfo);				
			}
			
			// Make sure the user is logged in before showing the reply for
		   	if(!this._config.isLoggedIn){					
				location.href = this._config.loginUrl;
				e.preventDefault();
				return;
			}
			
			// Make sure the user has a nickname before shoing the reply form
		   	if(!this._config.hasNickname){				
				this.nicknameMgr.showOverlay(e.target);				
				e.preventDefault();
				return;
			}
						
			// Remove the current panels
            this._$accordion.children().remove();
			
			/*
			 * Add the first page of topics back (This is due to a UX issue).
			 * All new topics should be in the first page of pagination, so to minimize user confusion
			 * we need to update this
			 */   
            this._$accordion.append(this._$firstPageTopics);			
			var $panels = this._$accordion.children();
            
			// Update the pagination numbers
			var pConfig = this._config.pagination;			
			var $pagination = jQuery(pConfig.id);
						
			if (pConfig.total / pConfig.items_per_page > 1) {
				// Store the last page in the pagination jQuery object if the user cancels the form
				$pagination.data('page', $pagination[0].getCurrentPage());
				$pagination[0].selectPage(0);
			}
            // If there are five topics visible pop the last topic off
            if ($panels.length == this._maxTopics) {
				
                // Remove the last topic element from the dom but keep it in the topics array
                // until after the new topic has been sucessfully submitted
                jQuery($panels[$panels.length - 1]).remove();
				
            }
            
            this._$newTopicForm = jQuery(this._config.newTopicTemplate);
			this._limitTextAreaChars(this._$newTopicForm.find('textArea'), this._config.topic.charLimit, this._config.topic.charLimitElem);
			this._toggleDefaultFieldText(this._$newTopicForm.find(':text'));
			
            this._$accordion.prepend(this._$newTopicForm) 		// Add a new topic item to the DOM
				.accordion('destroy') 							// Destroy the current accordion
				.children().removeClass(this._config.accordion.selectedClass);	// Make sure the accordion collapses				
			
			this._initAccordion();
        },
		
		/**
		 * Called when a user clicks on the cancel button when creating a new topic
		 */
		_deleteTopicForm: function()
		{	
			var $panels = this._$accordion.children();
			
			// Unbind the form field events
			this._$newTopicForm.find('textarea').unbind();
			this._$newTopicForm.find(':text').unbind();
			
			// Remove the current topic form
			this._$newTopicForm.remove();
			this._$newTopicForm = null;
			
			// Load the last topics
			this._$accordion.children().remove();
			this._$accordion.append(this._$topics);
			
			// Update the pagination numbers
			var pConfig = this._config.pagination;			
			var $pagination = jQuery(pConfig.id);
			
			// Update the pagination numbers to wherever the user was before			
			//$pagination[0].selectPage($pagination.data('page'));
			$pagination.removeData('page');
			
			// Update the old panels
			
			this._$accordion.accordion('destroy');				// Destroy the current accordion
			this._initAccordion();
		},
		
		/**
         Submits a new topic
         */
        submitNewTopic: function(topicInfo)
		{	
			
			var $title = topicInfo.$topic.find(':text');
			var $titleError = this._getErrorElem(topicInfo.$topic, $title);
			
			var $message = topicInfo.$topic.find('textarea');
			var $messageError = this._getErrorElem(topicInfo.$topic, $message);
			var validated = true;
			
			// Validate that the subject has text
			if(/^(\s|\n)*$/.test($title.val()) || $title.data('valChanged') == undefined){
				$titleError.html('Please enter a valid topic title.');				
				validated = false;
			}
			// Clear any old errors
			else {				
				$titleError.html('');
			}				
			
			// Validate that the body has text
			if(/^(\s|\n)*$/.test($message.val()) || $message.data('valChanged') == undefined){								
				$messageError.html('Please enter a valid message.');				
				validated = false;
			}
			// Clear any old errors
			else {		
				$messageError.html('');
			}
			
			if(!validated){return}
			
			this._ajaxBegin();
			
			jQuery.post(this._config.topic.action, {				
					queryType: 'Thread',					
					subject: _cleanStr(topicInfo.$topic.find(':text').val()),
					body: _cleanStr(topicInfo.$topic.find('textarea').val()),
					forumId: this._config.forum.id
				}, 
				
				/**
				 * The callback for the post
				 * @param {Object} json
				 * @param {Integer} status The status should be one if no errors occured and 0 if an error occured
				 * @param {String} [error] The error message for the request.  Only set this property if an error occured (status 0)
				 * @param {String} topicId The id of the topic
				 * @param {String} title The title of the topic
				 * @param {String} rssUrl The rss url of the topic
				 * @param {String} postAge The string that shows how much time has passed since the post was created.  For example "35 minutes".
				 * @param {Posts[]} posts Array of posts for the topic			 
				 */
				_scope(this, function(json){				
					
					// An error occured
					if(!json.Success && json.Success == false){
						// Add a general error message
						topicInfo.$topic.find(this._config.localErrors + ':first')
						.html(json.Error);
						
						this._ajaxEnd();
						return;
					}
					
					// Update the first page topics
					this._$firstPageTopics =this._$accordion.children();
					this._config.pagination.total++;
					this._createPagination();
					
					// TBD: Add error checking
					// Copy the new header data
					var $header = topicInfo.$topic.children(this._config.topic.topicPanel);
					var $body = topicInfo.$topic.find(this._config.topic.topicBody);						
					
					// TBD: Don't hardcode
					$header.parent().removeClass('dmp_forum_newtopic');
					
					topicInfo.$topic.children(this._config.topic.id).html(json.ThreadID);
					
					$header.html(_renderData(this._config.topicHeaderTemplate, {
						Title: json.Title,
						RSSURL: json.RSSURL,
						PostAge: json.PostAge,
						Nickname: json.Nickname,
						CreateDate: json.CreateDate						
					}));
	
					// Remove the reference to the new topic form
					this._$newTopicForm = null;
					$body.html('');					

					// Add the post to the body
					$body.append(this._createPostHtml(json.Post));
					
					// Re-enable everything
					this._ajaxEnd()
				}),			

				'json'
			);
        },
		
		/**
			Updates the forum with new html
			@param {String} href The url to get the forum data
			@param {Object} params The params for the request
		*/
		updateForum: function(href, params)
		{
			this._ajaxBegin();			
			params.queryType = 'Threads';
			
			jQuery.get(href, params, 
			_scope(this, function(html){
				
				var config = this._config;
				
				// If a topic form is open, delete it
				if(this._$newTopicForm){
					this._deleteTopicForm();
				}
				
				// If any reply forms are open, delete them
				if(this._$newReplyForm)
				{	
					this._deleteReplyForm(this._$newReplyForm.topicInfo);				
				}
				
				// Destroy the old accordion
				jQuery(config.accordion.id).accordion('destroy');
				
				// Add the new html
				this._$forumContainer.html(html);	
				
				// Reset the accordion
				this._$accordion = this._$forumContainer.children();
				
				this._$accordion.accordion('destroy');				// Destroy the current accordion				
				this._initAccordion();		
				
				// Set the new forum id
				this._config.forum.id = params.forumID;
				
				// Update the current Topics
				this._$topics = this._$accordion.children();
				
				// Reset the pagination
				if(params.total != undefined){										

					config.pagination.total = params.total;
					
					// Set the new forum title
					jQuery(this._config.title).html(params.title);
					
					// Set the new description
					jQuery(this._config.description).html(params.description);
					
					// Destroy and recreate the pagination
					this._createPagination();
					this._$firstPageTopics = this._$topics;			
					
					jQuery(this._config.singleForumId).show();
					
				}				
				
				this._ajaxEnd();			
				
			}), 'html');
		},
		
		/**
	         Reports Abuse on a Topic
         */
		reportAbuse: function(topicInfo, $target)
		{
			this._ajaxBegin();			
			jQuery.post(this._config.topic.action, {				
					queryType: 'Abuse',					
					subject: topicInfo.$topic.find(this._config.topic.topicTitle + ':first').text(),
					body: topicInfo.$post.find(this._config.post.postBody + ':first').text(),
					threadID: topicInfo.topicId,
					postID: topicInfo.postId
				}, 
				
				/**
				 * The callback for the post
				 * @param {Object} json
				 * @param {Integer} status The status should be one if no errors occured and 0 if an error occured
				 * @param {String} [error] The error message for the request.  Only set this property if an error occured (status 0)
				 * @param {String} topicId The id of the topic
				 * @param {String} title The title of the topic
				 * @param {String} rssUrl The rss url of the topic
				 * @param {String} postAge The string that shows how much time has passed since the post was created.  For example "35 minutes".
				 * @param {Posts[]} posts Array of posts for the topic			 
				 */
				_scope(this, function(json){				
					
					// Remove the reported link.  
					$target.replaceWith('<span>reported</span>');
					
					// Re-enable everything
					this._ajaxEnd()
				}),			

				'json'
			);
		},
		
		/**
		 * Used to return the topic information and post information for a particular event
		 * @param {jQuery} $target The target of the event in question
		 * @return {Object}
		 */
		_getTopicInfo: function($target)
		{
			// Find the topic id based on the ancestor of the current target
            // The topic id should be a child of the element that contains the topic
            var $topic = $target.parents('li:has(' + this._config.topic.id + '):first');
            var topicId = $topic.children(this._config.topic.id).text();  
			
			var $post = $target.parents(this._config.post.postContainer + ':first');
         	var postId = $post.children(this._config.post.id).text();
			
			return {
				$topic: $topic,
				topicId: topicId,
				$post: $post,
				postId: postId				
			};
		},
		
		/**
		 * Retrieves all the posts for a topic.
		 */
		_getTopicPosts: function(e, ui)
		{	
			// Cancel old ajax requests.  This shouldn't happen unless the user clicks the accordion panels quickly
			if(ui.oldHeader.data('ajaxObj') != undefined){
				ui.oldHeader.data('ajaxObj').abort();	
			}			

			// Don't make multiple requests to get the data if it has been loaded
			if(ui.newHeader.data('loadedState') != undefined 
			// See if any children have been loaded
			|| ui.newContent.children(this._config.topic.topicBody).children().length > 0){return}			
			
			ui.newHeader.data('loadedState', 'loading');
			
			var topicInfo = this._getTopicInfo(ui.newHeader);
			
			this._ajaxBegin();	
			var ajaxObj = jQuery.get(this._config.topic.allPostsAction, 
				{
					queryType: 'Posts',
					threadID: topicInfo.topicId
				},
				 _scope(this, function(html){							
					
					// Set the data loaded value so that we don't try to retrieve this information again
					ui.newHeader.data('loadedState', 'loaded');					
					
					// Remove the reference to the ajax object
					ui.newHeader.removeData('ajaxObj');
					
					// Add the html to the topic body					
					ui.newContent.children(this._config.topic.topicBody).html(html);
					
					this._ajaxEnd();
				}), 
				
				'html'
			);			
			
			ui.newHeader.data('ajaxObj', ajaxObj);
		},
        
		/**
			Creates the new reply form html
		*/
		_createReplyForm: function(topicInfo)
		{	
			if(this._$newReplyForm)
			{	
				this._deleteReplyForm(this._$newReplyForm.topicInfo);				
			}
			
			var $post = topicInfo.$post;			
				
			var html = this._config.post.replyFormTemplate;
			var $replyForm = jQuery(_renderData(html, {
				// Get the current post nickname
				Nickname: topicInfo.$post.find(this._config.post.nickname + ':first').text()
			}));
			
			var $postChildContainer = $post.children(this._config.post.childContainer);
			var $firstPost = $postChildContainer.children(this._config.post.postContainer + ':first');

			// Insert the form before the first reply
			if ($firstPost.length > 0) {
				$replyForm.insertBefore($firstPost);
			}
			// No replies have been made
			else
			{
				$postChildContainer.append($replyForm);
			}
			
			// scroll to the new post
			var $postBody = $post.parents(this._config.topic.topicBody + ':first');
			var scrollOffset = ($replyForm.offset().top - $postBody.offset().top) 
								+ $postBody.scrollTop();
								
			$postBody.animate({scrollTop: scrollOffset - 30}, 500);
			
			this._limitTextAreaChars($replyForm.find('textArea'), this._config.post.charLimit, this._config.post.charLimitElem);
			
			this._$newReplyForm = {
				replyForm: $replyForm,
				topicInfo: topicInfo				
				
			}
		},
		
		/**
		 * Submit a new reply
		 * @param {Object} topicInfo
		 */
		submitReplyForm: function(topicInfo)
		{	
			// Validate that the body has text.  Note, check for the default text
			var $message = topicInfo.$topic.find('textarea');	
			var $messageError = this._getErrorElem(topicInfo.$post, $message);			
			if(/^(\s|\n)*$/.test($message.val()) || $message.data('valChanged') == undefined){
				$messageError.html('Please enter a valid reply.');
				return;
			}
			// Clear previous errors
			else {
				$messageError.html('');
			}
			
			this._ajaxBegin();
			
			jQuery.post(this._config.post.action, {
					queryType: 'Post',
					threadID: topicInfo.topicId,
					parentID: topicInfo.postId,
					subject: _cleanStr(topicInfo.$topic.children(this._config.topic.topicPanel).find(this._config.topic.topicTitle).text()),					
					body: _cleanStr(topicInfo.$topic.find('textarea').val())	
				}, 

				/**
				 * The callback for the post
				 * @param {Object} json
				 * @param {Integer} status The status should be one if no errors occured and 0 if an error occured
				 * @param {String} [error] The error message for the request.  Only set this property if an error occured (status 0)
				 * @param {String} id The id of the reply
				 * @param {String} message The message of the reply
				 * @param {String} nickname The nickname of the user who posted the reply
				 * @param {Dae} postDate The date the post was made
				 */
				_scope(this, function(json){				
					
					// An error occured
					if(!json.Status || json.Status != 1){
						
						$messageError.html(json.ErrorMsg);
						this._ajaxEnd();
						return;
					}
					
					// TBD: Add error checking					
					var $body = topicInfo.$post;
					var $newpost = jQuery(this._createPostHtml(json));
					this._deleteReplyForm(topicInfo);
					$body.children(this._config.post.childContainer).show().append($newpost);

					var $post = topicInfo.$post;
			
					// scroll to the new post
					var $postBody = $post.parents(this._config.topic.topicBody + ':first');
					var scrollOffset = ($newpost.offset().top - $postBody.offset().top) 
										+ $postBody.scrollTop();
										
					$postBody.animate({scrollTop: scrollOffset}, 500);
					
					// Update the number of replies for the post
					var $replyCounter = $body.children(this._config.post.childContainer)
					.find(this._config.post.replyCounter + ':first');
					
					// Make sure that the parent containing the reply counter is visible (if there are 0 replies to start it is not visible)
					$replyCounter.parent().show();
					
					$replyCounter.html(parseInt($replyCounter.html()) + 1);
					
					// Update the number of posts for the thread
					var $postCounter = topicInfo.$topic.children(this._config.topic.topicPanel)
					.find(this._config.topic.postsCount);
					
					$postCounter.html(parseInt($postCounter.html()) + 1);
					
					this._ajaxEnd();
				}),			

				'json'
			);
		},
		
		/**
			Removes the reply form
		*/
		_deleteReplyForm: function(topicInfo)
		{
			var $post = topicInfo.$post;
			
			// Unbind the character limit event
			$post.find('textarea').unbind();
			
			// Remove the post			
			this._$newReplyForm.replyForm.remove();
			this._$newReplyForm = null;			
		},		
		
		/**
		 * Creates the html for a forum post
		 * @param {Object} postData The date used to populate the post HTML template
		 * @param {String} id The id of the post
		 * @param {String} message The message of the post
		 * @param {String} nickname The nickname of the post
		 * @param {Date} postDate The date time of the post
		 */
		_createPostHtml: function(postData)
		{
			return  _renderData(this._config.postTemplate, postData);
		},
		
		_createPagination: function()
		{			
			var pConfig = this._config.pagination;
			// If there are only one or more posts don't create pagination
			if(pConfig.total / pConfig.items_per_page <= 1){
				jQuery(pConfig.id).html('');
				this._updatePaginationCounter(0,0);;
				return;
			}
			
			pConfig.callback = _scope(this, function(page_id, elem)
			{
				var href = jQuery(elem).attr('href');
				this.updatePage(page_id, href);
				
				return false;
			});			
			
			this._updatePaginationCounter(0, pConfig.total);
			jQuery(pConfig.id).pagination(pConfig.total, pConfig);

		},
		
		/**
		 * Updates the current page of results
		 * @param {Integer} page_id The id of the page
		 * @param {String} href The url to get the paged data
		 */
		updatePage: function(page_id, href)
		{		
			this._updatePaginationCounter(page_id, this._config.pagination.total);				

			this.updateForum(href, {
				pageNum: page_id,
				forumID: this._config.forum.id	
			});					
			
			return false;
		},
		
		_updatePaginationCounter: function(index, total)
		{
			var pConfig = this._config.pagination;
			
			if(pConfig.currentCountBox != undefined)
			{
				// Clear out the viewing box
				if(index == 0 && total == 0){
					jQuery(pConfig.currentCountBox).html('');
					return;
				}
				
				jQuery(pConfig.currentCountBox).html(_renderData(pConfig.currentCountText, {
					startIndex: (index * pConfig.items_per_page) + 1,
					endIndex: Math.min((index * pConfig.items_per_page) + pConfig.items_per_page, total),
					total: total
				}));
			}
		},
		
		/**
		 * Helper for initializing the forum accordion
		 */
		_initAccordion: function(index)
		{
			var config = this._config;
			jQuery(config.accordion.id)
			.accordion(config.accordion)
			.bind('accordionchange', _scope(this, this._getTopicPosts));			
		},
		
		/**
		 * Initializes the Forums List
		 */
		_initForumList: function()
		{
			var config = this._config;
			var $forumList = jQuery(config.forum.forumList);
			var me = this;
			
			if($forumList.length > 0){				

				$forumList.children().click(function(e)
				{										
					// Get the forum id
					var $li = jQuery(this);
					
					// Don't load the same forum twice
					if($li.hasClass(config.forum.toggleClass)){return}
					
					var id = $li.find(config.forum.forumId).text();										
					var total = $li.find(config.forum.totalThreads).text();					
					var title = $li.find(config.forum.title).text();
					var description = $li.find(config.forum.description).text();
					
					// Hide the previous forum viewing text
					$li.parent().children('.' + config.forum.toggleClass).removeClass(config.forum.toggleClass);
					
					$li.addClass(config.forum.toggleClass);
					
					me.updateForum($li.find('a').attr('href'), {
						pageNum: 0,
						forumID: id,
						total: total,
						title: title,
						description: description
					});
					
					e.preventDefault();
					
				});	
			}
		},
		
		/**
		 * Initializes the nickname forum
		 */
		_initNicknameForm: function()
		{	
			this.nicknameMgr = new DMP.NicknameMgr(this._config.nickname);
			
			// Find out when the nickname has been created			
			this.nicknameMgr.addEvent('nicknamecreated', _scope(this, function(){				
				this._config.hasNickname = true;
			}));			
		},
		
		/**
		 * Limit the number of characters for a textarea
		 * @param {String | HtmlElement | jQuery} tArea The textare with the limit
		 * @param {Integer} limit The number of characters to limit
		 * @param {String | HtmlElement | jQuery} [charsToShowElem] The element that will display the characters remaining
		 */
		_limitTextAreaChars: function(tArea, limit, charsToShowElem)
		{			
			var $tArea = jQuery(tArea).bind('keyup', function(e){
				var text = jQuery(tArea).val();
			  	var textlength = text.length;			
				if(textlength > limit)			
				{			
					jQuery(charsToShowElem).html('0');				
					jQuery(tArea).val(text.substr(0,limit));			
					return false;			
				}			
				else		
				{			
					jQuery(charsToShowElem).html(limit - textlength);			
					return true;			
				}				
			});	

			this._toggleDefaultFieldText($tArea);
		},
		
		_toggleDefaultFieldText: function(elem)
		{
			jQuery(elem)
			// When the textarea gets focus get rid of the default text
			.bind('focus.defaultText', function(e){
				if(jQuery(this).data('valChanged') == undefined){
					
					var val = jQuery(this).val();
					jQuery(this).data('valChanged', val);
					jQuery(this).val('');	
				}
				
			})
			// Add the default text back if no text has been entered
			.bind('blur.defaultText', function(e){				
					
				var val = jQuery(this).val();
				if(val == ''){
					jQuery(this).val(jQuery(this).data('valChanged'));						
					jQuery(this).removeData('valChanged');
				}				
			})
		},
		
		/**
		 * Updates the forum content using AJAX
		 */
		_ajaxBegin: function()
		{								
			this._$forum.addClass(this._config.forum.loadingClass);
			this._sizeLoadingMask();			
			var $mask = jQuery(this._config.forum.loadingMask);	
			$mask.css('opacity', 0);
			$mask.show();			
			$mask.fadeTo('fast', 0.4);
			
			var timeouts;
			var me = this;
			jQuery(window).bind('resize.dmp.forum', function(){
				clearTimeout(timeouts);
				
				// Throttle the amount of times the loading mask is fired because of the way
				// resize events work
				setTimeout(function(){me._sizeLoadingMask()}, 100);
				
			});			
			
		},
		
		_sizeLoadingMask: function()
		{
			var offset = this._$forum.offset();
			var $mask = jQuery(this._config.forum.loadingMask);	
			$mask.width(this._$forum.width())
			$mask.height(this._$forum.height())
			$mask.css('top', offset.top);
			$mask.css('left', offset.left);
		},
		
		/**
		 * Happens after an arajx request completes
		 */
		_ajaxEnd: function()
		{
			this._$forum.removeClass(this._config.forum.loadingClass);
			var $mask = jQuery(this._config.forum.loadingMask);
			$mask.stop();
			$mask.hide();
			jQuery(window).unbind('resize.dmp.forum');
			// Remove error messages
			jQuery(this._config.globalErrors).html('');			
		},
		
		/**
			Helper method used to get the error div for a particular element
			@param {jQuery} $parent The parent element with the error class
			@param {jQuery} $field  The field element to get the error for
		*/
		_getErrorElem: function($parent, $field)
		{
			var fieldClass = '.' + $field.attr('name') + '_error';
			return $parent.find(fieldClass);
		}
    };
    
    
    /**
     Initializes all components onDOMready for the DMP.Forum component
     */
    DMP.Forum.Init = function(config){

        jQuery(document).ready(function(){
			
			new DMP.Forum(config);
			
        });
    };
	
	/**
	The mechanism for managing log in / nickname creating functionality prior to rating or posting a comment
	@class
	@param {Object} config The configuration for the nickname manager		
 		@param {String | jQuery} config.overlay The element that contains the nickname overlay
 		@param {String | jQuery} config.closeBtn The element that triggers the overlay to close
 		@param {String | jQuery} config.formId The id of the nickname "form"
 		@param {String | jQuery} config.nicknameField The name of the nickname field
 		@param {String | jQuery} config.messages Messages to display when the nickname has been created
 		@param {String | jQuery} config.nicknameBtn The button that triggers a nickname submit
		@param {String} config.action The url to the nickname form service
	*/
	DMP.NicknameMgr = function(config)
	{
		this.overlay = jQuery(config.overlay);
		this.closeBtn = jQuery(config.closeBtn).bind('click.nickname', _scope(this, this.hideOverlay));
		this._config = config;
		
		this.nicknameProps = {
			form: config.formId,
			nicknameField: config.nicknameField,                
			messages: config.messages
		};
		
		this._initNicknameProps();
	};
	
	DMP.NicknameMgr.prototype = {
		
		addEvent: function(eventName, callback)
		{
			this.overlay.bind(eventName, callback);			
		},
		
		_triggerEvent: function(eventName)
		{
			this.overlay.trigger(eventName);		
		},
		
		_initNicknameProps: function(e)
		{
			var props = this.nicknameProps;
			
			// Reference to the form element
			this._nicknameForm = jQuery(props.form);
			
			// Text field that holds the nickname value
			this._nicknameField = jQuery(props.nicknameField);
			
			// Div that holds all the nicknames messages
			this._nicknameMessages = jQuery(props.messages);
	
			// Init the form
			var me = this;
	
			jQuery(this._config.nicknameBtn).bind('click.nickname', function(e){

				var nName = me._nicknameField.val();
				
				// The nickname cannot be empty
				if(/^(\s|\n)*$/.test(nName))
				{
					me._setNickNameErrorCallback('Nickname Error');
				}
				else
				{
					jQuery.post(me._config.action,
						{
							queryType: 'Nickname',
							nickname: nName 
						},
						_scope(me, me._setNickNameSuccessCallback),
						'json'
					);
				}
				e.preventDefault();
				return false;
			});		
		},
				
		_setNickNameSuccessCallback: function(json)
		{
			var messageStr = '';
			this._nicknameMessages.html('');
			
			// The nickname is good
			if(json.Success === true)
			{
				this.overlay.hide();
				this._triggerEvent('nicknamecreated');			
				// Dispose of this control so the user can't reset their nickname
				this._dispose();
				return;
			}
			else {								
				messageStr += '<p>' + json.ErrorMsg + '</p>';										
			}			
			
			/*
			if(json.Suggestions != null && json.Suggestions.length > 0)
			{
				messageStr += '<ul>';
				for(var i=0, sug=json.suggestions, len=sug.length; i < len; i++)
				{
					messageStr += '<li class="dmp_nickname_suggestion"><input type="radio" name="nickname_suggestion" value="' + sug[i] + '" /><label for="nickname_suggestion">' + sug[i] + '</label>';
				}				
			}			
			*/
			
			this._nicknameMessages.html( messageStr);
			//this._setSuggestionHandlers();
			
		},
		
		_setSuggestionHandlers: function()
		{
			var $radios = this._nicknameMessages.find('li');
			var me = this;
			
			$radios.bind('click.nickname', function(e){
				var radio = jQuery(':radio', this)[0];					
				radio.checked = true;
				me._nicknameField.val(radio.value);
				
			});
		},
		
		_removeSuggestionHandlers: function()
		{
			var $radios = this._nicknameMessages.find('li');
			var me = this;
			
			$radios.unbind('click.nickname');
		},
		
		_dispose: function()
		{	
			// Clear the event handlers
			jQuery(this._config.closeBtn).unbind('click.nickname');
			jQuery(this._config.nicknameBtn).unbind('click.nickname');
			this._removeSuggestionHandlers();
			this.overlay.remove();
		},
		
		_setNickNameErrorCallback: function(err)
		{
			var messageStr = '';
			if(typeof err == 'string')
			{
				messageStr = err;
			}
			else
			{
				if(err.Error != null && err.Error != '')
				{
					messageStr += '<p>' + err.Error + '</p>';
				}
			}
			
			this._nicknameMessages.html(messageStr);
		},		
	
		showOverlay: function(target)
		{
			var $target = jQuery(target);			
			var $overlay = this.overlay;			
			var bounds = $target.offset();	
			var offset = {x: 0, y: 15};		

			$overlay.css('left', bounds.left + offset.x);
			$overlay.css('top', bounds.top + offset.y);
			$overlay.show();
		},
		
		hideOverlay: function()
		{	
			var $overlay = this.overlay;	
			$overlay.hide();
			
			// Reset the form
			this._removeSuggestionHandlers();
			this._nicknameField.val('');
			this._nicknameMessages.html('');		
		}
	};

})(jQuery);