define('bitbucket/internal/feature/comments/likes/controller', [
    'aui',
    'jquery',
    'lib/jsuri',
    'lodash',
    'bitbucket/util/events',
    'bitbucket/util/navbuilder',
    'bitbucket/util/server',
    'bitbucket/util/state'
],
function(
    AJS,
    $,
    Uri,
    _,
    events,
    navbuilder,
    server,
    pageState
) {

    'use strict';

    /**
     * Initialize a comment likes object.
     *
     * If there is a comment likes button nearby it will bind itself to the button.
     * If there is no button or info to display it will remove the placeholder element from the DOM and do nothing.
     *
     * @param {Object} options
     * @param {Comment} options.comment
     * @constructor
     */
    function CommentLikes(options) {
        _.bindAll(this);

        this.options = options;
        this.$buttonEl = null;
        this.$infoPanel = null;
        this.likes = options.likes;
        this.dialog = null;

        this.$infoPanel = options.$infoPlaceholder.parent();
        this.$infoPanel.on('click', 'button', this.onInfoClicked);
        this.updateLikeInfo();

        if (this.likes.canLike) {
            // get the <li> that is next to us
            this.$buttonEl = this.$infoPanel.parentsUntil(".content").find(".comment-likes-button");
            this.$buttonEl.on('click', this.onLikeClick);
            // handling when liking the comment via a deep link
            this.likeIfDeepLinked();
        } else if (this.likes.likerCount === 0) {
            // this panel will never change and is blank so we remove it so there isn't
            // an extra separator in the actions list
            this.$infoPanel.remove();
        }
    }

    /**
     * Updates the text on the like button
     */
    CommentLikes.prototype.updateLikeButton = function () {
        var text = this.likes.userHasLiked ?
            AJS.I18n.getText('bitbucket.like.comment.unlike') :
            AJS.I18n.getText('bitbucket.like.comment.like');
        this.$buttonEl.text(text);
    };

    /**
     * Updates the info panel based on the current state
     *
     * Note: this wont go to the server to request an update of the state
     */
    CommentLikes.prototype.updateLikeInfo = function () {
        if (this.likes.likerCount === 0) {
            this.$infoPanel.addClass("hidden");
        } else {
            this.$infoPanel.html(bitbucket.internal.plugin.comment.likes.likeInfo(this.likes));
            this.$infoPanel.removeClass("hidden");
            this.dialog = null;
        }
    };

    /**
     * Gets the URL that should be used to like/unlike this comment
     *
     * @returns {String} the URL
     */
    CommentLikes.prototype.getLikesURL = function () {
        var PR = pageState.getPullRequest();
        var commit = pageState.getCommit();
        var base = navbuilder.rest("comment-likes");

        if (PR) {
            base = base.currentPullRequest();
        } else if (commit) {
            base = base.project(pageState.getProject().key).repo(pageState.getRepository().slug).commit(commit.id);
        }
        return base.addPathComponents("comments", this.options.commentId, "likes").build();
    };

    /**
     * Displays the info dialog
     *
     * @param {Event} e
     */
    CommentLikes.prototype.onInfoClicked = function (e) {
        e.preventDefault();
        var self = this;
        if (!this.dialog) {
            // lazy init the dialog the first time the like button is clicked
            this.dialog = AJS.InlineDialog(e.target, "likes-info-panel",
                function (content, trigger, showPopup) {
                    content.html(bitbucket.internal.plugin.comment.likes.dialog(self.likes));
                    showPopup();
                }, {
                    closeOnTriggerClick: true,
                    initCallback: dialogInitCallback,
                    hideCallback: dialogHideCallback
                });
            // immediately show the dialog
            this.dialog.show();
        }
    };

    /**
     * Sets the comment to be liked if focused via a deep link.
     */
    CommentLikes.prototype.likeIfDeepLinked = function() {
        var uri = new Uri(window.location);
        if (uri.getQueryParamValue('action') === 'like' &&
            parseInt(uri.getQueryParamValue("commentId"), 10) === this.options.commentId &&
            !this.likes.userHasLiked) {
                this.setLike(true);
                this._triggerEvent('actions.like');
        }
    };

    /**
     * @this {InlineDialog}
     */
    function mouseUpHandler(e) {
        if (this.popup && !this.popup.has(e.target).length) {
            this.hide();
        }
    }

    var boundMouseHandler;

    /**
     * Set up a mouseup handler to hide the likes dialog in case a preventDefaulted link/button is pressed.
     *
     * @this {InlineDialog}
     */
    function dialogInitCallback() {
        boundMouseHandler = mouseUpHandler.bind(this);
        $(document).on('mouseup', boundMouseHandler);
    }

    /**
     * Ensure the handler we
     * @this {InlineDialog}
     */
    function dialogHideCallback() {
        $(document).off('mouseup', boundMouseHandler);
        boundMouseHandler = null;
    }

    /**
     * Will Like/Unlike the comment depending on the current state
     *
     * @param {Event} e
     */
    CommentLikes.prototype.onLikeClick = function (e) {
        e.preventDefault();
        this.setLike(!this.likes.userHasLiked);
    };

    /**
     * Set the like status for a comment and sync to the server.
     * @param {boolean} isLiking - are we liking?
     */
    CommentLikes.prototype.setLike = function(isLiking) {
        this._setInteralLikeStatus(isLiking);
        this._syncLike(isLiking);
    };

    /**
     * Set the like status for a comment.
     * @param {boolean} isLiking - are we liking?
     * @private
     */
    CommentLikes.prototype._setInteralLikeStatus = function(isLiking) {
        this.likes.userHasLiked = isLiking;
        this.likes.likerCount = isLiking ? this.likes.likerCount + 1 : this.likes.likerCount - 1;
        this.updateLikeButton();
        this.updateLikeInfo();
    };

    var statusCodeHelper = {
        '404': function(xhr, testStatus, errorThrown, data){
            var error = data && data.errors && data.errors.length && data.errors[0];
            var message = error && error.message;
            return {
                title: AJS.I18n.getText('bitbucket.web.pullrequest.comment.notfound.title'),
                message: message,
                shouldReload: true,
                fallbackUrl: undefined
            };
        }
    };

    /**
     * Syncs the like status to the server.
     * @param {boolean} isLiking - are we liking?
     * @private
     */
    CommentLikes.prototype._syncLike = function(isLiking) {
        server.rest({
            type: isLiking ? 'POST' : 'DELETE',
            url: this.getLikesURL(),
            statusCode: statusCodeHelper
        })
            .done(this._triggerEvent.bind(this, isLiking ? 'like' : 'unlike'))
            .fail(this._setInteralLikeStatus.bind(this, !isLiking));
    };

    /**
     * Trigger a public event in the comment likes namespace.
     *
     * @param {string} name - The name of the event to trigger
     * @private
     */
    CommentLikes.prototype._triggerEvent = function(name) {
        events.trigger("bitbucket.internal.feature.comments.likes." + name, null, _.extend({
            commentId: this.options.commentId
        }, this.likes));
    };

    return CommentLikes;
});
