function comments_init() {
		if (!document.getElementById('comments')) {
		return;
	}

	// load comments
	comments_loadComments();

	// apply the onsubmit functionality
	if (document.getElementById('comments').getElementsByTagName('form').length > 0) {
		var oCommentsForm = document.getElementById('comments').getElementsByTagName('form')[0];
		oCommentsForm.onsubmit = comments_addComment;
	}

	// apply the deselect comment for reply functionality
	var oDeselectForReply = document.getElementById('deselect-for-reply');
	if (oDeselectForReply) {
		oDeselectForReply.style.display = 'none';
		oDeselectForReply.onclick = comments_deselectForReplyClick;
	}

	// apply the force loading comments functionality
	document.getElementById('force-loading-comments').onclick = comments_loadComments;
}

function comments_loadComments() {
	var oCommentsContainer = document.getElementById('comments');
	var itemId = oCommentsContainer.className.match(/c-(\d+)/)[1];

	// show the ajax loading throbber
	var oAjaxLoading = oCommentsContainer.getElementsByClassName('ajax-loading')[0];
	oAjaxLoading.style.display = 'block';

	// send the AJAX request to retrieve the comments of the item
	var xhr = new XMLHttpRequest('MSXML2.XMLHTTP.3.0');
	xhr.open('GET', window.serverConfigs.docRoot + '/comments/' + itemId, true);
	xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
	xhr.onreadystatechange = function () {
		if(xhr.readyState == 4) {
			// create the new comments content element
			var oContent = document.createElement('div');
			oContent.className = 'comments-content';

			// the comments content holds either the comments or an error message
			var sContent = (xhr.status == 200) ? xhr.responseText : window.serverConfigs.xhrError;
			oContent.innerHTML = sContent;

			// replace the existing comments content with the new one
			oCommentsContainer.replaceChild(oContent, oCommentsContainer.getElementsByClassName('comments-content')[0]);

			// apply non w3c fixes to the newly created elements
			if (typeof(nonW3CFixes_init) != 'undefined') {
				oContent.nonW3CFixes_init = nonW3CFixes_init;
				oContent.nonW3CFixes_init();
			}

			// hide the ajax loading throbber
			oAjaxLoading.style.display = 'none';

			// add comments pagination click handlers
			comments_addPaginationItemClick();

			// highlight code bocks
			if (window.codeHighlighter) {
				window.codeHighlighter.highlight();
			}

			// apply the onclick functionality to reply links
			oaSelectForReplyLinks = oCommentsContainer.getElementsByClassName('select-for-reply');
			for (var i = 0; i < oaSelectForReplyLinks.length; i++) {
				oaSelectForReplyLinks[i].onclick = comments_selectForReplyClick;
			}
		}
	};

	xhr.send(null);

	return false;
}

function comments_addComment() {
	var form = this;
	var itemId = this.parentNode.className.match(/(^|\s+)c-(\d+)(\s+|$)/)[2];

	// validate
	if (!comments_isValid(form)) {
		return false;
	}

	// disable the submit button, change the text to 'SUBMITTING COMMENT...' and hide reCAPTCHA error message
	var oSubmitButton = document.getElementById('submit_comment');
	oSubmitButton.disabled = true;
	oSubmitButton.value = 'SUBMITTING COMMENT...';

	var oRecaptchaField = document.getElementById('recaptcha_input');
	if (oRecaptchaField) {
		oRecaptchaField.getElementsByClassName('error')[0].style.display = 'none';
	}

	// build the POST data
	var data = 'item_id=' + itemId +
		'&text=' + encodeURIComponent(this.text.value);


	// add name if exists
	if (this.name) {
		data +='&name=' + encodeURIComponent(this.name.value);
	}

	// and email if exists
	if (this.email) {
		data += '&email=' + encodeURIComponent(this.email.value);
	}

	if (typeof(Recaptcha) != 'undefined') {
		data += '&recaptcha_challenge_field=' + encodeURIComponent(this.recaptcha_challenge_field.value) +
			'&recaptcha_response_field=' + encodeURIComponent(this.recaptcha_response_field.value);
	}

	if(window.parentCommentId) {
		data += '&parent_comment_id='+window.parentCommentId;
	}

	// open the XHR
	var xhr = new XMLHttpRequest('MSXML2.XMLHTTP.3.0');
	xhr.open('POST', window.serverConfigs.docRoot + '/comment_add/', true);

	// set the POST headers
	xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
	xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	xhr.setRequestHeader('Content-length', data.length);
	xhr.setRequestHeader('Connection', 'close');

	// reload comments on success
	xhr.onreadystatechange = function () {
		if (xhr.readyState == 4) {
			if (xhr.status == 200) {
				if (xhr.responseText.indexOf('OK') == 0) {
					comments_loadComments();
					form.reset();
					window.parentCommentId = 0;
				} else {
					if (oRecaptchaField) {
						oRecaptchaField.getElementsByClassName('error')[0].style.display = 'inline';
						oRecaptchaField.getElementsByTagName('input')[0].focus();
					}
				}
			} else{
				alert(window.serverConfigs.xhrError);
			}

			// after the request has finished (with success, invalid captcha or other error) refresh the captcha image
			if (typeof(Recaptcha) != 'undefined') {
				Recaptcha.reload();
			}

			// enable the submit button and change the text to 'SUBMIT COMMENT'
			var oSubmitButton = document.getElementById('submit_comment');
			oSubmitButton.disabled = false;
			oSubmitButton.value = 'SUBMIT COMMENT';
		}
	}
	xhr.send(data);

	return false;
}

function comments_isValid(form) {
	// reset errors
	var oaErrors = form.getElementsByClassName('error');
	for (var i = 0; i < oaErrors.length; i++) {
		oaErrors[i].style.display = 'none';
	}

	// validate name
	if (form.name && !form.name.value.match(/^[\w_-][\s\w_-]*$/)) {
		document.getElementsByClassName('error name')[0].style.display = 'inline';
		return false;
	}

	// validate email
	if (form.email && form.email.value.indexOf('@') <= 0) {
		document.getElementsByClassName('error email')[0].style.display = 'inline';
		return false;
	}

	// validate text
	if (form.text && form.text.value.replace(/(^\s+|\s+$)/, '') == '') {
		document.getElementsByClassName('error text')[0].style.display = 'inline';
		return false;
	}

	return true;
}

function comments_refreshPaginationItems(oPaginationContainer, iCurrentPage, iTotalPages) {
	var oaPaginationItems = oPaginationContainer.getElementsByTagName('li');
	var iStartIndex = iCurrentPage - window.serverConfigs.pagination.threshold > 2 ? iCurrentPage - window.serverConfigs.pagination.threshold : 2;
	var iEndIndex = iCurrentPage + window.serverConfigs.pagination.threshold > iTotalPages - 1 ? iTotalPages - 1 : iCurrentPage + window.serverConfigs.pagination.threshold;

	// tweak start and end indexes to enforce displaying a constant number of items even when current is very close to 1 or total
	if (iCurrentPage - 1 - window.serverConfigs.pagination.threshold < 0) {
		iEndIndex += -(iCurrentPage - 1 - window.serverConfigs.pagination.threshold);
	}
	if (iTotalPages - (iCurrentPage + window.serverConfigs.pagination.threshold) < 0) {
		iStartIndex -=  -(iTotalPages - (iCurrentPage + window.serverConfigs.pagination.threshold));
	}

	// prev arrow
	oaPaginationItems[0].className = oaPaginationItems[0].className.replace(/display-(none|block)/, iCurrentPage == 1 ? 'display-none' : 'display-block');

	// page number 1
	oaPaginationItems[1].className = 'display-' + (iTotalPages > 1 ? 'block' : 'none');
	oaPaginationItems[1].className += iCurrentPage == 1 ? ' button current' : '';

	// prev separator
	oaPaginationItems[2].className = 'separator display-' + (iStartIndex > 2 ? 'block' : 'none');

	// page numbers
	for (var i = 2; i <= iTotalPages - 1; i++) {
		oaPaginationItems[i + 1].className = 'display-' + (i >= iStartIndex && i <= iEndIndex ? 'block' : 'none');
		oaPaginationItems[i + 1].className += i == iCurrentPage ? ' button current' : '';
	}

	// next separator
	oaPaginationItems[oaPaginationItems.length - 3].className = 'separator display-' + (iEndIndex < iTotalPages - 1 ? 'block' : 'none');

	// last page number
	oaPaginationItems[oaPaginationItems.length - 2].className = 'display-block ' + (iCurrentPage == iTotalPages ? 'button current' : '');

	// next arrow
	oaPaginationItems[oaPaginationItems.length - 1].className = oaPaginationItems[oaPaginationItems.length - 1].className.replace(/display-(none|block)/, iCurrentPage == iTotalPages ? 'display-none' : 'display-block');
}

function comments_showCommentsPage(iCurrentPage) {
	var oCommentsContainer = document.getElementById('comments');
	var oaCommentsPages = oCommentsContainer.getElementsByClassName('comments-page');
	for (var i = 0; i < oaCommentsPages.length; i++) {
		oaCommentsPages[i].className = oaCommentsPages[i].className.replace(/display-(none|block)/, i + 1 == iCurrentPage ? 'display-block' : 'display-none');
	}

	var oaCommentsPaginations = oCommentsContainer.getElementsByClassName('pagination');
	for (var i = 0; i < oaCommentsPaginations.length; i++) {
		var iTotalPages = oaCommentsPaginations[i].className.match(/(^|\s+)cpt-(\d+)/)[2];
		comments_refreshPaginationItems(oaCommentsPaginations[i], iCurrentPage, iTotalPages);
	}
}

function comments_paginationItemClick() {
	var iClickedPage = parseInt(this.innerHTML.replace(/^(\s+|\s+)$/g, ''));

	var oaCommentsPaginationItems = this.parentNode.parentNode.getElementsByTagName('li');
	for (var i = 0; i < oaCommentsPaginationItems.length; i++) {
		if (oaCommentsPaginationItems[i].hasClassName('current')) {
			iCurrentPage = parseInt(oaCommentsPaginationItems[i].getElementsByTagName('a')[0].innerHTML);
			break;
		}
	}

	switch(true) {
		case iCurrentPage == iClickedPage:
			return false;
		break;
		case this.innerHTML.replace(/^(\s+|\s+)$/g, '') == window.serverConfigs.pagination.prev:
			comments_showCommentsPage(iCurrentPage - 1);
		break;
		case this.innerHTML.replace(/^(\s+|\s+)$/g, '') == window.serverConfigs.pagination.next:
			comments_showCommentsPage(iCurrentPage + 1);
		break;
		default:
			comments_showCommentsPage(iClickedPage);
		break;
	}

	return false;
}

function comments_addPaginationItemClick() {
	var oCommentsPaginations = document.getElementById('comments').getElementsByClassName('pagination');
	for (var i = 0; i < oCommentsPaginations.length; i++) {
		var oaCommentsPaginationItems = oCommentsPaginations[i].getElementsByTagName('a');
		// set total number of pages
		oCommentsPaginations[i].className += ' cpt-'+(oaCommentsPaginationItems.length - 2);
		for (var j = 0; j < oaCommentsPaginationItems.length; j++) {
			oaCommentsPaginationItems[j].onclick = comments_paginationItemClick;
		}
	}
}

function comments_selectForReplyClick() {
	// get selected comment id
	var commentId = window.parentCommentId = this.parentNode.parentNode.className.match(/(^|\s+)c-(\d+)(\s+|$)/)[2];

	// deselect all comments, except for the one that needs to be selected
	var oaComments = document.getElementById("comments").getElementsByClassName('comment');
	for (var i = 0; i < oaComments.length; i++) {
		if (oaComments[i].hasClassName('c-' + commentId)) {
			if (!oaComments[i].hasClassName('selected-for-reply')) {
				oaComments[i].className += ' selected-for-reply';
			}
		} else {
			oaComments[i].className = oaComments[i].className.replace(' selected-for-reply', '') ;
		}
	}

	// change the submit button text to 'SUBMIT AS REPLY' and show the 'deselect ...' link
	document.getElementById('submit_comment').value = 'SUBMIT AS REPLY';
	var oDeselectForReply = document.getElementById('deselect-for-reply');
	if (oDeselectForReply) {
		oDeselectForReply.style.display = 'inline';
	}

	return false;
}

function comments_deselectForReplyClick() {
	window.parentCommentId = 0;
	var oaComments = document.getElementById('comments').getElementsByClassName('comment');
	for (var i = 0; i < oaComments.length; i++) {
		oaComments[i].className = oaComments[i].className.replace(' selected-for-reply', '') ;
	}

	// change the submit button text back to simply 'send' and hide the 'deselect ...' link
	document.getElementById('submit_comment').value = 'SUBMIT COMMENT';
	var oDeselectForReply = document.getElementById('deselect-for-reply');
		if (oDeselectForReply) {
		oDeselectForReply.style.display = 'none';
	}

	return false;
}