/*
	Page comments
*/

/*
 * Comments for a Page (whether a basic HtmlPage or a file rendered within a page, such as in a gallery or podcast).
 * Because we may have multiple comment streams, we avoid using IDs, or at least IDs that aren't qualified by the page ID or similar.
 */

var COMMENTS_LOADING_MESSAGE = '<i class="fa fa-fw fa-spin fa-spinner" aria-hidden="true"></i> Loading comments&hellip;';

var Comments = function(pageUrl, $contentDiv, $linkEl) {
  this.url = pageUrl;
  this.$div = $contentDiv;
  this.$rootList = null;
  this.$replyTo = null;	 // populated when replying to
  this.replyToId = null; // a comment. LI element, and Comment ID.
  this.$link = $linkEl;
  this.hideDeletedComments = null;
  if (!Comments.C) {
    //misc constants
    Comments.C = {
      PERMA_REGEX: /^#c([0-9a-f]){16,32}$/,
      URL: '/sitebuilder2/api/comment'
    };
    //language constants (text and stuff)
    Comments.L = {
      LOADING: COMMENTS_LOADING_MESSAGE,
      DENIED_MESSAGE: '<span class="permission_denied">You do not have permission to view these comments.</span>',
      FAILED_MESSAGE: '<span class="error_unknown">Sorry, there was a problem loading comments.</span>',
      REPLY_HEADING: 'Reply to comment'
    };
  }
};
Comments.showPageComments = false;

jQuery(function($) {
  // Set up the comments for the whole page, if available
	var $pageCommentsDiv = $('.page-comments-container');
	if ($pageCommentsDiv.length > 0) {
		var $link = $pageCommentsDiv.find('.comments-link');

		var open = Comments.showPageComments;
		var opts = {};
		var query = Url.unparam(window.location.href);
		if (typeof query.comments != 'undefined') {
		    open = "no" != query.comments && "false" != query.comments && "off" != query.comments;
		}
		opts.waitForClick = !open;

		window.pageComments = Comments.fromLink($link, opts);
	}

	// handler for comments on lightbox elements
	var $lightboxCommentLinks = $('.lightbox-comments .comments-link');
  $lightboxCommentLinks.each(function() {
    var $el = jQuery(this);
    var id = $el.data('id');

    $el.popover({
      html: true,
      template: '<div class="popover comments-popover" role="tooltip"><a href="#" class="close">&times;<span class="sr-only"> Close comments popup</span></a><div class="arrow"></div><div id="popover_' + id + '" class="popover-content"></div></div>',
      content: COMMENTS_LOADING_MESSAGE + '<!-- placeholder content mandatory for bootstrap -->',
    });

    $el.on('shown.bs.popover', function() {
      const $this = $(this);
      $this.data('bs.popover').tip().find('.close').on('click', function() {
        $this.popover('hide');
      });
      $lightboxCommentLinks.not($(this)).popover('hide');

      var c = new Comments($el.data('url'), $('#popover_' + id), $el);
      c.keepLink = true;
      c.open();
    });

    $el.on('hide.bs.popover', function() {
      $el.data('bs.popover').inState.click = false;
    });
  });

  $('body').on('sitebuilder.comments.updated', function(e, data) {
  	var $target = $(e.target);
  	var $el = $target.is('.popover') ? $target : $target.closest('.popover');
  	var $popover = $el.data('bs.popover');
    if ($popover && $popover.tip().hasClass('in') && data && data.refreshCount) {
      var count = $popover.tip().find('.root-comments .page-comment:not([class~="deleted"]):not([class~="not-approved"])').length;
      $popover.$element.text(count + ' comment' + (count == 1 ? '' : 's'));
    }
  });
});

/*
	Convenience function for turning a link to comments into
	embedded comments. Gets the page from the page parameter in the link,
	and creates a div with an ID based on the page URL. It then returns
	the Comments object for you, initialised.

	options:

	waitForClick
		if true, comments are loaded when the link is clicked rather than
		straight away.
*/
Comments.fromLink = function($link, options) {
	options = options || {};
	var page = Url.unparam($link.attr('href')).page;
	if (page) {
		var id = options.divId || ('commentsFor' + page.replace(/[^a-z0-9]+/g,'_'));
		var $div = jQuery('<div/>').attr('id', id);
		// the Comments object is being created before the div
		// is actually in the DOM, so watch out for that. Basically
		// don't do too much in the constructor; do DOM stuff in init().
		// that is what it is for.
		var c = new Comments(page, $div, $link);
		var hasPermalink = !!((window.location.hash||'').match(Comments.C.PERMA_REGEX));
		// Add init as a click event if requested, BUT if there is a comment
		// hash in the browser then the user probably wants to go straight to it,
		// so we should load the comments now
		if (options.waitForClick && !hasPermalink && $link.length > 0) {
			$link.click(jQuery.proxy(function(ev){
				ev.preventDefault();
				ev.stopPropagation();

				this.open();
				return false;
			}, c));
		} else {
			c.open();
		}
		return c;
	}
};


/*
  An even more convenient function. Looks for all links
  with the "comments-link" class and decorates them into
  comment feeds. Probably not that useful in practice since you
  don't want to be generating more than one or two comments
  feeds in a page; it'll work but it'll be a bit of a
  squeeze :)
*/
Comments.decorateLinks = function() {
    jQuery('.comments-link').each(function(i, link) {
    	Comments.fromLink(jQuery(link));
    });
}

Comments.prototype = {
	init: function() {
		this._loadingMessage();
		jQuery.ajax({
			url: Comments.C.URL,
			data: this.params({page: this.url, random: this._rand() }),
			success: jQuery.proxy(this.initDone, this),
			error: jQuery.proxy(this.initFail, this)
		});
	},

	_rand: function(alwaysRandom){
		if (alwaysRandom) parseInt(Math.random()*999999999999999);
		var rand = (window.SitebuilderInfo||{}).lastUpdated || parseInt(Math.random()*999999999999999);
		if (new WCookie('WarwickSSO').value) {
			//make sure content is updated between signed in/out
			rand += "U";
		}
		return rand;
	},

	params : function(p) {
	   if (typeof this.hideDeletedComments == 'boolean') {
	       p.HideDeletedComments = this.hideDeletedComments;
	   }
	   return p;
	},

	setHideDeletedComments: function(hide) {
	   if (hide != this.hideDeletedComments) {
	      this.hideDeletedComments = hide;
	      this.init();
	   }
	},

	_loadingMessage: function() {
		this.$div.html(Comments.L.LOADING);
		this.$div.trigger('sitebuilder.comments.updated');
	},

	initDone: function(r) {
		this.$div.html(r);
		this.$div.trigger('sitebuilder.comments.updated', { refreshCount: true });

		//make permalinks work for lazyloaded comments
		if ((window.location.hash||'').match(Comments.C.PERMA_REGEX)) {
			window.location.hash = window.location.hash;
		}
		this.decorateEverything();

		this.initialHeading = this._getFormHolder().find('h5:first').html();
		this.initialSubmitText = this._getFormHolder().find('input[name=postSubmit]') ? this._getFormHolder().find('input[name=postSubmit]').val() : null;
	},

	initFail: function(r) {
		if (r.status == 403 || ((r.status == 301 || r.status == 302)
				&& r.getResponseHeader('Location').indexOf('websignon') > -1)) {
			this.$div.html(Comments.L.DENIED_MESSAGE);
		} else {
			this.$div.html(Comments.L.FAILED_MESSAGE);
		}

		this.$div.trigger('sitebuilder.comments.updated');
	},

	_paramsToObject: function(array) {
		var params = {};
		for (var i=0; i < array.length; i++) {
			params[array[i].name] = array[i].value;
		}
		return params;
	},

	previewClick: function(ev) {
		var button = ev.target;
		const params = this._paramsToObject(jQuery(button).closest('form').serializeArray());

		params.previewSubmit = 'Preview';
		delete params.postSubmit;

		jQuery.ajax({
			type: 'POST',
			url: Comments.C.URL,
			data: jQuery.param(params),
			success: jQuery.proxy(function(res) {
				var previewText = res;
				var box = this.$div.find('.previewContainer');
				var $well = jQuery('<div class="well"></div>');
				$well.html(previewText);
				jQuery(box).html($well).prepend('<h5>Preview</h5>');
				this.$div.trigger('sitebuilder.comments.updated');
			}, this)
		});
		ev.preventDefault();
		ev.stopPropagation();
	},

	unfollowClick: function(ev) {
		var button = ev.target;
		params = jQuery(button).closest('form').serialize();

		jQuery.ajax({
			url: Comments.C.URL,
			type:'POST',
			data: params,
			success: jQuery.proxy(function(res) {
				this.$div.html(res);
				this.$div.trigger('sitebuilder.comments.updated', { refreshCount: true });
				this.decorateEverything();
			}, this)
		});

		ev.preventDefault();
		ev.stopPropagation();
	},

	decorateForm: function() {
		if (this.$div.find('input[name=previewSubmit]').length > 0) {
			this.$div.find('input[name=previewSubmit]')
				.click(jQuery.proxy(this.previewClick, this));
		}

		var submitButton = this.$div.find('input[name=postSubmit]');
		if (submitButton.length > 0) {
			jQuery(submitButton).removeAttr('disabled');
			jQuery(submitButton).click(jQuery.proxy(this.submitClick, this));
		}

		var unfollowButton = this.$div.find('input[name=unfollowSubmit]');
		// This more often than not won't actually be here
		if (unfollowButton.length > 0) {
			unfollowButton.click(jQuery.proxy(this.unfollowClick, this));
		}

		this._reassignFormTabindex();
	},

	resetForm: function() {
		var form = this._getForm();
		if (form.length > 0) {
			var formHolder = this._getFormHolder();
			form.find('textarea').val('');
			this.$div.find('.previewContainer').html('');
			this.$div.trigger('sitebuilder.comments.updated');
			jQuery(form.find('input[name=postSubmit]')).removeAttr('disabled');

			/*
			 * SBTWO-3155:
			 * form.select() fails in IE because it tries to use form.id, which
			 * instead gets form/input[name=id]
			 */
			form.find('.user_error').remove();

			form.find('input[name=commentId]').remove();

			formHolder.find('.editReplyText').remove();

			formHolder.find('h5:first').html(this.initialHeading);
			formHolder.find('input[name=postSubmit]').val(this.initialSubmitText);
		}

		this.resetFlags();
	},

	resetFlags: function() {
		this.$editing = null;
		this.editingId = null;
		this.$replyTo = null;
		this.replyToId = null;
	},

	/**
	 * Decorate the items within the given container. The container
	 * is specified so you can either give an entire list, or just a list
	 * item.
	 */
	decorateListWithin: function($container) {
		this.$rootList = this.$div.find('ul');
		if ($container.length > 0) {
			$container.find('a.reply-comment').each(jQuery.proxy(function(i, a) {
				var $a = jQuery(a);

				var commentId = Url.unparam($a.attr('href')).replyTo;
				$a.click(jQuery.proxy(function(ev) {
					ev.preventDefault();
					ev.stopPropagation();

					if (commentId != this.replyToId) {
						this.resetForm();
						this.$replyTo = jQuery('#pageComment_'+commentId).closest('li');
						this.replyToId = commentId;
						this.$replyTo.append(this._getFormHolder());
						this._getFormHolder().find('h2').html(Comments.L.REPLY_HEADING);
						this.$div.trigger('sitebuilder.comments.updated');
					}

					return false;
				}, this));
			}, this));

			/*
				Permalinks on AJAX pages point to the noscript page, but
				when we're actually on the page looking at the AJAX comments
				we can rewire the click to just visit the hash. This way,
				right clicking to save as a bookmark will still correctly
				save the version on the noscript page.
			*/
			$container.find('a.permalink').each(jQuery.proxy(function(i, a) {
				var $a = jQuery(a);

				var hash = $a.attr('href').match(/#(.+)/)[1];
				if (hash) {
					$a.click(jQuery.proxy(function(ev) {
						ev.preventDefault();
						ev.stopPropagation();

						window.location.hash = hash;
						return false;
					}, this));
				}
			}, this));
		}

		this._replaceClick($container, 'a.delete-comment', true, function(ev, commentId) {
			var question = 'Are you sure you want to delete this comment? ';
			var replies = jQuery(ev.target).closest('li').find('li').length;
			if (replies == 1) {
				question += ' one reply will also be deleted.';
			} else if (replies > 1) {
				question += replies + ' replies will also be deleted.';
			}
			if (confirm(question)) {
				this.deleteComment(commentId);
			}
		});

		this._replaceClick($container, 'a.approve-comment', true, function(ev, commentId) {
			if (confirm('Are you sure you want to approve this comment?')) {
				this.approveComment(commentId);
			}
		});
        this._replaceClick($container, 'a.show-deleted-comments', false, function() {
            this.setHideDeletedComments(false);
        });
        this._replaceClick($container, 'a.hide-deleted-comments', false, function() {
            this.setHideDeletedComments(true);
        });

        this._replaceClick(this.$div, 'a.edit-comment', true, function(ev, commentId) {
            if (commentId != this.editingId) {
                this.resetForm();
                this.$editing = jQuery(ev.target).closest('li');
                this.editingId = commentId;
                //TODO some indication that the edit form is loading

                jQuery.ajax({
                	url: Comments.C.URL,
                    type:'GET',
                    data: jQuery.param({ commentId:commentId, random: this._rand(true), page:this._getForm() ? this._getForm().find('input[name=page]').val() : '' }),
                    success: jQuery.proxy(function(res) {
                         this._getFormHolder().html(res);
                         this._getFormHolder().find('.cancelEditingLink').remove();

                         this.decorateForm();
                         this.$editing.append(this._getFormHolder());
                         var replies = this.$editing.find('ul');
                         if (replies.length > 0) {
                             this.$editing.append(replies);
                         }

                         this.$div.trigger('sitebuilder.comments.updated');
                    }, this)
                });
            }
        });

	},

	/**
	   Finds all elements under root matching the selector, and replaces
	   the click action with the given function. If getComment is true, it
	   will find the commentId of the comment containing the link,
	   and call func(event, commentId). func will have this
	   comment object bound to "this".
	*/
	_replaceClick: function($root, selector, getComment, func) {
		if ($root.length > 0) {
		   $root.find(selector).each(jQuery.proxy(function(i, a) {
			   var $a = jQuery(a);

		       var commentId = null;
		       if (getComment) {
		           var $div = $a.closest('.page-comment');
	               commentId = this._commentIdFromDiv($div);
		       }

		       $a.click(jQuery.proxy(function(ev) {
	                ev.preventDefault();
	                ev.stopPropagation();

	                (jQuery.proxy(func, this))(ev, commentId);
	                return false;
	           }, this));
		   }, this));
		}
	},

	decorateList: function() {
		this.decorateListWithin(this.$div);
	},

	decorateEverything: function() {
		this.decorateList();
		this.decorateForm();
	},

	deleteComment: function(commentId) {
		jQuery.ajax({
			url: Comments.C.URL,
			type:'post',
			data: jQuery.param(this.params({ action:'delete', commentId:commentId, page:this._getForm().find('input[name=page]').val() })),
			success: jQuery.proxy(this.onDeleteSuccess, this),
			error: jQuery.proxy(this.onDeleteFailure, this)
		});
	},

	//when a delete completes, we repopulate everything.
	onDeleteSuccess: function(res) {
		this.$div.html(res);
		this.$div.trigger('sitebuilder.comments.updated', { refreshCount: true });

		this.decorateEverything();
	},



	onDeleteFailure: function(res) {

	},

	approveComment: function(commentId) {
		jQuery.ajax({
			url: Comments.C.URL,
			type:'post',
			data: jQuery.param({ action:'approve', commentId:commentId, page:this._getForm().find('input[name=page]').val() }),
			success: jQuery.proxy(this.onApproveSuccess, this),
			error: jQuery.proxy(this.onApproveFailure, this)
		});
	},

	//when a approve completes, we repopulate everything.
	onApproveSuccess: function(res) {
		this.$div.html(res);
		this.$div.trigger('sitebuilder.comments.updated', { refreshCount: true });

		this.decorateEverything();
	},

	onApproveFailure: function(res) {

	},

	submitClick: function(ev) {
		ev.preventDefault();
		ev.stopPropagation();

		var button = ev.target;
		jQuery(button).attr('disabled', 'disabled');

		var params = this._paramsToObject(this._getForm().serializeArray());

		params.postSubmit = 'Submit';
		delete params.previewSubmit;

		if (this.replyToId) {
			params.replyTo = this.replyToId;
		}

		jQuery.ajax({
			url: Comments.C.URL,
			type:'post',
			data: jQuery.param(params),
			success: jQuery.proxy(this.onSubmitSuccess, this),
			error: jQuery.proxy(this.onSubmitFailure, this)
		});
		return false;
	},
	onSubmitSuccess: function(res) {
		var validationError = (res.indexOf('user_error') != -1);
		if (validationError){
			this._getFormHolder().html(res);
			this.decorateForm();
		} else {
			this.$div.html(res);
			this.$div.trigger('sitebuilder.comments.updated', { refreshCount: true });

			this.decorateEverything();
			this.resetFlags();
		}
	},
	onSubmitFailure: function(res) {
		this._getFormHolder().html(res);
		this.decorateForm();
	},

	open: function() {
		if (this.$link.length > 0 && !this.keepLink) {
			this.$link.replaceWith(this.$div);
		}

		this.init();
	},

	/**
	 * Move the form back to its original position, below the comments list.
	 */
	resetFormPosition: function() {
		this._insertAfter(this._getListHolder(), this._getFormHolder());
	},

	_commentIdFromDiv: function($div) {
		return $div.attr('id').substring("pageComment_".length);
	},

	/* find the highest tabindex in the current document. */
	_greatestTabindex: function() {
		var max = 0;
		jQuery('[tabindex]').each(function(i, el) {
			max = Math.max(max, jQuery(el).attr('tabindex'));
		});

		return max;
	},

	// Set the tabindex attributes within the comment form so that they
	// don't clash with any existing tab index values, allowing tabbing
	// to work properly.
	_reassignFormTabindex: function() {
		var greatestTabindex = this._greatestTabindex();

		if (this._getForm().length > 0) {
			var tabbedElements = this._getForm().find('[tabindex]');
			tabbedElements.each(function(i, el){ jQuery(el).attr('tabindex', ++greatestTabindex) });
		}
	},

	/**
	 * Get the FORM element.
	 */
	_getForm: function() {
		return this.$div.find('form');
	},
	/**
	 * Get the div containing the FORM element, the contents of
	 * which get replaced by AJAX responses.
	 */
	_getFormHolder: function() {
		return this.$div.find('.comment_form_holder');
	},
	/**
	 * Get the div containing the UL of comments.
	 */
	_getListHolder: function() {
		return this.$div.find('.comment_list_holder');
	},
	/**
	 * Insertion.After doesn't work on DOM elements. This does!
	 * TODO probably change to Element.insert after Prototype 1.6
	 */
	_insertAfter: function(referenceElement, newElement){
		referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
	}

};

window.Comments = Comments;
