var doComments = {
	ACTION_GET_POST_COMMENT_FORM: 'getPostCommentForm',
	ACTION_GET_COMMENTS: 'getComments',
	ACTION_POST_COMMENT: 'postComment',
	ACTION_VOTE_COMMENT: 'voteComment',
	DEFAULT_TOPIC_NAME: 'defaultTopic',
	LOAD_ALL_COMMENTS: -1,
	MESSAGE_OP_IN_PROGRESS: 'An operation is currently in progress.  Please wait for a couple seconds, and then try again.',
	MESSAGE_CANT_LOAD_POST_COMMENT_FORM: 'DoComments was unable to load the post comment form.  Please refresh the page to try again.  If the problem persists, contact support@jerrata.com for assistance.',
	MESSAGE_CANT_LOAD_COMMENTS: 'DoComments was unable to load comments.  Please refresh the page to try again.  If the problem persists, contact support@jerrata.com for assistance.',
	MESSAGE_CANT_RECORD_VOTE: 'DoComments was unable to record your vote.  Please try again.',
	MESSAGE_ALREADY_VOTED: "We're sorry, but you already voted on this comment.",
	MESSAGE_CANT_POST_COMMENT: 'DoComments was unable to post your comment.  Please try again.',
	MESSAGE_WONT_POST_AS_REPLY: "Your comment won't be posted as a reply.",
	VOTE_UP: '1',
	VOTE_DOWN: '0',
	VOTE_COOKIE_NAME: 'doCommentsVote',
	FIELD_TYPE_TEXT: 'fieldTypeText',
	FIELD_TYPE_CHECKBOX: 'fieldTypeCheckbox',
	FIELD_TYPE_SELECT: 'fieldTypeSelect',
	topic: null,
	skin: null,
	doCommentsPHPLocation: null,
	pageNumber: 0,
	latestCommentId: null,
	
	mutex: {
		isLoadingData: false,
		
		/*============================================================================*/
		acquire: function(options) {
			if (this.isLoadingData) {
				if (typeof options == 'undefined' ||
					(typeof options != 'undefined' && typeof options.showAlert == 'boolean' && options.showAlert)) {
					alert(doComments.MESSAGE_OP_IN_PROGRESS);
				}
				
				return false;
			} else {
				this.isLoadingData = true;
				return true;
			}
		},
		
		/*============================================================================*/
		release: function() {
			this.isLoadingData = false;
		}
	},
	
	autoUpdater: {
		interval: null,
		INTERVAL_LENGTH: null,
		
		/*============================================================================*/
		start: function() {
			if (this.interval == null && this.INTERVAL_LENGTH != null) {
				this.interval = setInterval(function() {
					doComments.autoUpdater.updateComments();
				}, this.INTERVAL_LENGTH);
			}
		},
		
		/*============================================================================*/
		stop: function() {
			if (this.interval != null) {
				clearInterval(this.interval);
				this.interval = null;
			}
		},
		
		/*============================================================================*/
		updateComments: function() {
			if (!doComments.mutex.acquire({showAlert: false})) {
				return;
			} else {
				doComments.mutex.release();
				doComments.getComments();
			}
		}
	},
	
	textAreaMonitor: {
		interval: null,
		INTERVAL_LENGTH: 1000,
		labelName: 'docomPostFormLabelNumCharsRemaining',
		textAreaName: 'docomPostFormFieldComment',
		commentMaxLength: null,
		
		/*============================================================================*/
		start: function() {
			if (this.interval == null && $(this.labelName) && $(this.textAreaName)) {
				this.interval = setInterval(function() {
					doComments.textAreaMonitor.updateNumCharsRemaining();
				}, this.INTERVAL_LENGTH);
			}
		},
		
		/*============================================================================*/
		stop: function() {
			if (this.interval != null) {
				clearInterval(this.interval)
				this.interval = null;
				doComments.textAreaMonitor.updateNumCharsRemaining();
			}
		},
		
		/*============================================================================*/
		updateNumCharsRemaining: function() {
			if (this.commentMaxLength == null) {
				$(this.labelName).update('Unknown number of characters remaining');
			} else {
				var numCharsRemaining = this.commentMaxLength - $(this.textAreaName).value.length;
				
				switch (numCharsRemaining) {
					case 1:
					case -1:
						$(this.labelName).update(numCharsRemaining + ' character remaining');
						break;
					default:
						$(this.labelName).update(numCharsRemaining + ' characters remaining');
						break;
				}
			}
		}
	},
	
	/*============================================================================*/
	init: function(options) {
		for (option in options) {
			this[option] = options[option];
		}
		
		this.getPostForm({onSuccess: function() {
							doComments.getComments();
						  },
						  onFailure: function() {
							doComments.getComments();
						  }});
	},
	
	/*============================================================================*/
	getPostForm: function(options) {
		if (!$('doCommentsPostFormContainer') || !this.mutex.acquire()) {
			if (typeof options != 'undefined' && typeof options.onFailure == 'function') {
				options.onFailure();
			}
			
			return;
		}
		
		var params = {
			action: this.ACTION_GET_POST_COMMENT_FORM,
			topicName: (this.topic != null)? this.topic : this.DEFAULT_TOPIC_NAME
		};
		
		if (this.skin != null) {
			params.skinName = this.skin;
		}
		
		new Ajax.Request(this.doCommentsPHPLocation, {
						 parameters: params,
						 onSuccess: function(transport) {
							this.mutex.release();
							
							var response = this.decodeResponse(transport.responseText);
							
							if (response.ok) {
								this.textAreaMonitor.commentMaxLength = response.payload.commentMaxLength;
								
								$('doCommentsPostFormContainer').update(response.payload.html);
								
								if (typeof options != 'undefined' && typeof options.onSuccess == 'function') {
									options.onSuccess();
								}
							} else {
								alert(response.error.message);
							}
						 }.bind(this),
						 onFailure: function(transport) {
							this.mutex.release();
							$('doCommentsPostFormContainer').update(this.MESSAGE_CANT_LOAD_POST_COMMENT_FORM);
						 }.bind(this)});
	},
	
	/*============================================================================*/
	getComments: function(options) {
		if (!$('doCommentsCommentsContainer') || !this.mutex.acquire()) {
			return;
		}
		
		var params = {
			action: this.ACTION_GET_COMMENTS,
			topicName: (this.topic != null)? this.topic : this.DEFAULT_TOPIC_NAME
		};
		
		if (this.pageNumber != this.LOAD_ALL_COMMENTS) {
			params.pageNumber = this.pageNumber;
		}
		
		if (this.latestCommentId != null) {
			params.latestCommentId = this.latestCommentId;
		}
		
		if (this.skin != null) {
			params.skinName = this.skin;
		}
		
		new Ajax.Request(this.doCommentsPHPLocation, {
						 parameters: params,
						 onSuccess: function(transport) {
							this.mutex.release();
							
							var response = this.decodeResponse(transport.responseText);
							
							if (response.ok) {
								this.autoUpdater.stop();
								
								if (response.payload.doRefresh) {
									this.autoUpdater.INTERVAL_LENGTH = response.payload.refreshInterval;
									this.autoUpdater.start();
								} else {
									this.autoUpdater.INTERVAL_LENGTH = null;
								}
								
								if (response.payload.needsUpdate) {
									this.latestCommentId = response.payload.latestCommentId;
									$('doCommentsCommentsContainer').update(response.payload.html);
								}
								
								var topicRatingContainer = $('doCommentsTopicRatingContainer');
								
								if (topicRatingContainer) {
									topicRatingContainer.update(response.payload.topic.rating.html);
								}

								if (typeof options != 'undefined' && typeof options.onSuccess == 'function') {
									options.onSuccess();
								}
							} else {
								alert(response.error.message);
							}
						 }.bind(this),
						 onFailure: function(transport) {
							this.mutex.release();
							$('doCommentsCommentsContainer').update(this.MESSAGE_CANT_LOAD_COMMENTS);
						 }.bind(this)});
	},
	
	/*============================================================================*/
	postComment: function() {
		var postFormContainer = $('doCommentsPostFormContainer');
		
		if (!postFormContainer || !this.mutex.acquire()) {
			return;
		}
		
		var params = {
			action: this.ACTION_POST_COMMENT,
			topicName: (this.topic != null)? this.topic : this.DEFAULT_TOPIC_NAME
		};
		
		postFormContainer.select('.docomPostFormField').each(function(id) {
			var field = $(id);
			
			switch (field.getAttribute('data-docom-field-type')) {
				default:
				case this.FIELD_TYPE_TEXT:
				case this.FIELD_TYPE_SELECT:
					params[field.getAttribute('data-docom-field-name')] = field.value;
					break;
				case this.FIELD_TYPE_CHECKBOX:
					params[field.getAttribute('data-docom-field-name')] = field.checked;
					break;
			}
		}.bind(this));
		
		var postCommentButton = $('docomPostCommentButton');
		
		if (postCommentButton) {
			postCommentButton.value = postCommentButton.getAttribute('data-docom-button-label-loading');
			postCommentButton.disabled = true;
		}
		
		var cancelCommentButton = $('docomCancelCommentButton');
		
		if (cancelCommentButton) {
			cancelCommentButton.disabled = true;
		}
		
		new Ajax.Request(this.doCommentsPHPLocation, {
						 parameters: params,
						 onSuccess: function(transport) {
							var postCommentButton = $('docomPostCommentButton');
							
							if (postCommentButton) {
								postCommentButton.value = postCommentButton.getAttribute('data-docom-button-label');
								postCommentButton.disabled = false;
							}
							
							var cancelCommentButton = $('docomCancelCommentButton');
							
							if (cancelCommentButton) {
								cancelCommentButton.disabled = false;
							}
		
							this.mutex.release();
							
							var response = this.decodeResponse(transport.responseText);
							
							if (response.ok) {
								if (!response.payload.isLive) {
									alert(response.payload.message);
								} else {
									this.changePage(0);
								}
								
								setTimeout(function() {
									doComments.hidePostForm({afterFinish: function() {
										doComments.endReply();
										
										var commentField = $('docomPostFormFieldComment');
										
										if (commentField) {
											commentField.value = '';
										}
									}});
								}, 1000);
							} else {
								alert(response.error.message);
								
								if (typeof response.error.field != 'undefined') {
									var postFormContainer = $('doCommentsPostFormContainer');
									
									if (postFormContainer) {
										postFormContainer.select('.docomPostFormField').each(function(id) {
											var field = $(id);
											
											if (field.getAttribute('data-docom-field-name') == response.error.field) {
												if (typeof field.focus == 'function') {
													field.focus();
												}
												
												throw $break;
											}
										});
									}
								}
							}
						 }.bind(this),
						 onFailure: function(transport) {
							var postCommentButton = $('docomPostCommentButton');
							
							if (postCommentButton) {
								postCommentButton.value = postCommentButton.getAttribute('data-docom-button-label');
								postCommentButton.disabled = false;
							}
							
							var cancelCommentButton = $('docomCancelCommentButton');
							
							if (cancelCommentButton) {
								cancelCommentButton.disabled = false;
							}
							
							this.mutex.release();
							
							alert(this.MESSAGE_CANT_POST_COMMENT);
						 }.bind(this)});
	},
	
	/*============================================================================*/
	upvote: function(commentId) {
		this.voteComment(commentId, this.VOTE_UP);
	},
	
	/*============================================================================*/
	downvote: function(commentId) {
		this.voteComment(commentId, this.VOTE_DOWN);
	},
	
	/*============================================================================*/
	voteComment: function(commentId, vote) {
		if (!this.mutex.acquire()) {
			return;
		}
		
		if (document.cookie.indexOf(this.VOTE_COOKIE_NAME + commentId) != -1) {
			this.mutex.release();
			alert(this.MESSAGE_ALREADY_VOTED);
			return;
		}
		
		var savingLabel = $('doCommentsSavingLabel' + commentId);
		
		if (savingLabel) {
			savingLabel.show();
		}
		
		new Ajax.Request(this.doCommentsPHPLocation, {
						 parameters: {
							action: this.ACTION_VOTE_COMMENT,
							commentId: commentId,
							vote: vote
						 },
						 onSuccess: function(transport) {
							if (savingLabel) {
								savingLabel.hide();
							}
							
							this.mutex.release();
							
							var response = this.decodeResponse(transport.responseText);
							
							if (response.ok) {
								document.cookie = this.VOTE_COOKIE_NAME + commentId + '=true; expires=Mon, 1 Jan 2035 12:00:00 UTC; path=/';
								
								var element;
								
								if (vote == this.VOTE_UP) {
									element = $('doCommentsNumUpvotes' + commentId);
								} else {
									element = $('doCommentsNumDownvotes' + commentId);
								}
								
								if (element) {
									element.update((function(value) {
										var numGroups = Math.floor(value.length / 3),
											remainderLength = value.length % 3;
											groupedString = (remainderLength > 0)? value.substr(0, remainderLength) : '',
											caret = remainderLength;

										for (var i = 0; i < numGroups; i++) {
											groupedString += ((groupedString.length > 0)? ',' : '') + value.substr(caret, 3);
											caret += 3;
										}

										return groupedString;
									})('' + (parseInt(element.innerHTML.replace(',', '')) + 1)));
								}
							} else {
								alert(response.error.message);
							}
						 }.bind(this),
						 onFailure: function(transport) {
							if (savingLabel) {
								savingLabel.hide();
							}
							
							this.mutex.release();
							alert(this.MESSAGE_CANT_RECORD_VOTE);
						 }.bind(this)});
	},
	
	/*============================================================================*/
	scrollToComment: function(commentId) {
		if (!this.mutex.acquire()) {
			return;
		}
		
		var elementId = 'doComments' + commentId;
		
		if ($(elementId)) {
			new Effect.ScrollTo(elementId, {afterFinish: function() {
				doComments.mutex.release();
			}});
		} else if (this.pageNumber != this.LOAD_ALL_COMMENTS) {
			this.mutex.release();
			this.changePage(this.LOAD_ALL_COMMENTS, {onSuccess: function() {
				doComments.scrollToComment(commentId);
			}});
		}
	},
	
	/*============================================================================*/
	beginReply: function(commentId) {
		var parentCommentIdField = $('docomPostFormFieldParentCommentId');
		
		if (!parentCommentIdField) {
			return;
		} else {
			parentCommentIdField.value = commentId;
			
			var replyInfoDiv = $('docomPostFormReplyInfo');
			
			if (replyInfoDiv) {
				var replyAuthorName = $('docomPostFormReplyAuthorName'),
					parentCommentAuthorName = $('doCommentsAuthorName' + commentId),
					replyCheckbox = $('docomPostFormReplyCheckbox');
				
				if (replyAuthorName && parentCommentAuthorName) {
					replyAuthorName.update(parentCommentAuthorName.innerHTML);
				}
				
				if (replyCheckbox) {
					replyCheckbox.checked = true;
				}
				
				replyInfoDiv.show();
			}
			
			this.showPostForm();
		}
	},
	
	/*============================================================================*/
	endReply: function() {
		var parentCommentIdField = $('docomPostFormFieldParentCommentId');
		
		if (!parentCommentIdField) {
			return;
		} else {
			parentCommentIdField.value = '';
			
			var replyInfoDiv = $('docomPostFormReplyInfo');
			
			if (replyInfoDiv) {
				replyInfoDiv.hide();
			}
			
			if (this.isPostFormVisible()) {
				alert(this.MESSAGE_WONT_POST_AS_REPLY);
			}
		}
	},
	
	/*============================================================================*/
	showPostForm: function() {
		var postFormId = 'doCommentsPostForm',
			postForm = $(postFormId);
		
		if (postForm) {
			if (postForm.getStyle('display') == 'none') {
				new Effect.SlideDown(postFormId);
			}
			
			setTimeout(function() {
				new Effect.ScrollTo(postFormId, {afterFinish: function() {
					$(postFormId).select('.docomPostFormFieldBeautiful').each(function(id) {
						var element = $(id);
						
						if (typeof element.focus == 'function') {
							element.focus();
							throw $break;
						}
					});
				}});
			}, 100);
		}
	},
	
	/*============================================================================*/
	hidePostForm: function(options) {
		var postFormId = 'doCommentsPostForm',
			postForm = $(postFormId);
		
		if (postForm && postForm.getStyle('display') != 'none') {
			if (typeof options != 'undefined' && typeof options.afterFinish == 'function') {
				new Effect.SlideUp(postFormId, {afterFinish: function() {
					options.afterFinish();
				}});
			} else {
				new Effect.SlideUp(postFormId);
			}
		}
	},
	
	/*============================================================================*/
	isPostFormVisible: function() {
		var postForm = $('doCommentsPostForm');
		
		if (postForm && postForm.getStyle('display') != 'none') {
			return true;
		} else {
			return false;
		}
	},
	
	/*============================================================================*/
	showTopicRating: function(topicRating) {
		var starDisplay = $('docomPostFormStarDisplayContainer');
		
		if (starDisplay) {
			starDisplay.select('.docomPostFormStarDisplay').each(function(id) {
				var element = $(id);
				
				if (element.getAttribute('data-docom-topic-rating') == topicRating) {
					element.show();
				} else {
					element.hide();
				}
			});
		}
	},
	
	/*============================================================================*/
	changePage: function(pageNumber, options) {
		this.pageNumber = pageNumber;
		this.latestCommentId = null;
		this.getComments(options);
	},
	
	/*============================================================================*/
	loadPreviousPage: function(options) {
		if (this.pageNumber >= 1) {
			this.changePage(this.pageNumber - 1, options);
		}
	},
	
	/*============================================================================*/
	loadNextPage: function(options) {
		this.changePage(this.pageNumber + 1, options);
	},
	
	/*============================================================================*/
	decodeResponse: function(data) {
		var decoder = {
			caret: null,
			input: null,
			TOKEN_STRING: 's',
			TOKEN_BOOLEAN: 'b',
			TOKEN_INTEGER: 'i',
			TOKEN_OBJECT: 'o',
			TOKEN_DELIMITER: '|',
			BOOLEAN_TRUE: '1',
			
			process: function(input) {
				this.caret = 0;
				this.input = input;
				
				var strlen = this.input.length,
					key = null,
					result = {};
				
				while (this.caret < strlen) {
					switch (this.input.substr(this.caret, 1)) {
						case this.TOKEN_STRING:
							if (key == null) {
								key = this.decodeString();
							} else {
								result[key] = this.decodeString();
								key = null;
							}
							break;
						case this.TOKEN_BOOLEAN:
							result[key] = this.decodeBoolean();
							key = null;
							break;
						case this.TOKEN_INTEGER:
							result[key] = this.decodeInt();
							key = null;
							break;
						case this.TOKEN_OBJECT:
							result[key] = this.decodeObject();
							key = null;
							break;
						default:
							return {};
							break;
					}
				}
				
				return result;
			},
			decodeString: function() {
				var barPos = this.input.indexOf(this.TOKEN_DELIMITER, ++this.caret),
					strlen = parseInt(this.input.substring(this.caret, barPos));
					
				this.caret = barPos + 1;
				
				var s = this.input.substr(this.caret, strlen);
				
				this.caret += strlen;
				
				return s;
			},
			decodeBoolean: function() {
				if (this.input.substr(++this.caret, 1) == this.BOOLEAN_TRUE) {
					this.caret++;
					return true;
				} else {
					this.caret++;
					return false;
				}
			},
			decodeInt: function() {
				var barPos = this.input.indexOf(this.TOKEN_DELIMITER, ++this.caret),
					i = parseInt(this.input.substring(this.caret, barPos));
					
				this.caret = barPos + 1;
				
				return i;
			},
			decodeObject: function() {
				var objectString = this.decodeString(),
					tempCaret = this.caret,
					tempInput = this.input,
					o = this.process(objectString);
					
				this.caret = tempCaret;
				this.input = tempInput;
				
				return o;
			}
		};
		
		return decoder.process(data);
	}
};
