// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * This module handles the contacts area of the messaging area.
 *
 * @module     core_message/message_area_contacts
 * @package    core_message
 * @copyright  2016 Mark Nelson <markn@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/custom_interaction_events', 'core/str',
        'core_message/message_area_events'],
    function($, Ajax, Templates, Notification, CustomEvents, Str, Events) {

        /** @type {Object} The list of selectors for the message area. */
        var SELECTORS = {
            CONTACT: "[data-region='contact']",
            CONTACTICONBLOCKED: "[data-region='contact-icon-blocked']",
            CONTACTS: "[data-region='contacts'][data-region-content='contacts']",
            CONTACTSAREA: "[data-region='contacts-area']",
            CONVERSATIONS: "[data-region='contacts'][data-region-content='conversations']",
            COURSE: "[data-region='course']",
            LASTMESSAGETEXT: "[data-region='last-message-text']",
            LASTMESSAGEUSER: "[data-region='last-message-user']",
            LOADINGICON: '.loading-icon',
            MESSAGETEXT: "[data-region='message-text']",
            MESSAGINGAREA: "[data-region='messaging-area']",
            NOCONTACTS: "[data-region=no-contacts]",
            SEARCHBOX: "[data-region='search-box']",
            SEARCHRESULTSAREA: "[data-region='search-results-area']",
            SEARCHTEXTAREA: "[data-region='search-text-area']",
            SELECTEDVIEWCONVERSATION: "[data-action='view-contact-msg'].selected",
            SELECTEDVIEWPROFILE: "[data-action='view-contact-profile'].selected",
            SHOWMESSAGES: "[data-action='show-messages']",
            VIEWCONVERSATION: "[data-action='view-contact-msg']",
            VIEWPROFILE: "[data-action='view-contact-profile']"
        };

        /**
         * Contacts class.
         *
         * @param {Messagearea} messageArea The messaging area object.
         */
        function Contacts(messageArea) {
            this.messageArea = messageArea;
            this._init();
        }

        /** @type {Boolean} checks if we are currently loading conversations */
        Contacts.prototype._isLoadingConversations = false;

        /** @type {Boolean} checks if we are currently loading contacts */
        Contacts.prototype._isLoadingContacts = false;

        /** @type {int} the number of contacts displayed */
        Contacts.prototype._numContactsDisplayed = 0;

        /** @type {int} the number of contacts to retrieve */
        Contacts.prototype._numContactsToRetrieve = 20;

        /** @type {int} the number of conversations displayed */
        Contacts.prototype._numConversationsDisplayed = 0;

        /** @type {int} the number of conversations to retrieve */
        Contacts.prototype._numConversationsToRetrieve = 20;

        /** @type {int} the number of chars of the message to show */
        Contacts.prototype._messageLength = 60;

        /** @type {Messagearea} The messaging area object. */
        Contacts.prototype.messageArea = null;

        /**
         * Initialise the event listeners.
         *
         * @private
         */
        Contacts.prototype._init = function() {
            CustomEvents.define(this.messageArea.node, [
                CustomEvents.events.activate,
                CustomEvents.events.down,
                CustomEvents.events.up,
            ]);

            this.messageArea.onCustomEvent(Events.MESSAGESEARCHCANCELED, this._viewConversations.bind(this));
            this.messageArea.onCustomEvent(Events.USERSSEARCHCANCELED, this._viewContacts.bind(this));
            this.messageArea.onCustomEvent(Events.CONTACTSSELECTED, this._viewContacts.bind(this));
            this.messageArea.onCustomEvent(Events.CONVERSATIONDELETED, this._deleteConversation.bind(this));
            this.messageArea.onCustomEvent(Events.CONVERSATIONSSELECTED, this._viewConversations.bind(this));
            this.messageArea.onCustomEvent(Events.CONTACTSSELECTED, this._viewContacts.bind(this));
            this.messageArea.onCustomEvent(Events.MESSAGESDELETED, this._updateLastMessage.bind(this));
            this.messageArea.onCustomEvent(Events.MESSAGESENT, this._handleMessageSent.bind(this));
            this.messageArea.onCustomEvent(Events.CONTACTREMOVED, function(e, userid) {
                this._removeContact(SELECTORS.CONTACTS, userid);
            }.bind(this));
            this.messageArea.onCustomEvent(Events.CONTACTADDED, function(e, userid) {
                this._addContact(userid);
            }.bind(this));
            this.messageArea.onCustomEvent(Events.CONTACTBLOCKED, function(e, userid) {
                this._blockContact(userid);
            }.bind(this));
            this.messageArea.onCustomEvent(Events.CONTACTUNBLOCKED, function(e, userid) {
                this._unblockContact(userid);
            }.bind(this));
            this.messageArea.onCustomEvent(Events.CHOOSEMESSAGESTODELETE,
                this._startDeleting.bind(this));
            this.messageArea.onCustomEvent(Events.CANCELDELETEMESSAGES,
                this._stopDeleting.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.VIEWCONVERSATION,
                this._viewConversation.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.VIEWPROFILE,
                this._viewContact.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.SHOWMESSAGES,
                this._showMessagingArea.bind(this));

            this.messageArea.onDelegateEvent(CustomEvents.events.up, SELECTORS.CONTACT,
                this._selectPreviousContact.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.down, SELECTORS.CONTACT,
                this._selectNextContact.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.up, SELECTORS.VIEWCONVERSATION,
                this._selectPreviousConversation.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.down, SELECTORS.VIEWCONVERSATION,
                this._selectNextConversation.bind(this));

            this.messageArea.onDelegateEvent(CustomEvents.events.up, SELECTORS.COURSE, this._selectPreviousCourse.bind());
            this.messageArea.onDelegateEvent(CustomEvents.events.down, SELECTORS.COURSE, this._selectNextCourse.bind());

            this.messageArea.onDelegateEvent('focus', SELECTORS.SEARCHBOX, this._setSearching.bind(this));
            this.messageArea.onDelegateEvent('blur', SELECTORS.SEARCHBOX, this._clearSearching.bind(this));

            // Now enable the ability to infinitely scroll through conversations and contacts.
            CustomEvents.define(this.messageArea.find(SELECTORS.CONVERSATIONS), [
                CustomEvents.events.scrollBottom
            ]);
            CustomEvents.define(this.messageArea.find(SELECTORS.CONTACTS), [
                CustomEvents.events.scrollBottom
            ]);
            this.messageArea.onDelegateEvent(CustomEvents.events.scrollBottom, SELECTORS.CONVERSATIONS,
                this._loadConversations.bind(this));
            this.messageArea.onDelegateEvent(CustomEvents.events.scrollBottom, SELECTORS.CONTACTS,
                this._loadContacts.bind(this));

            if (!this.messageArea.showContactsFirst()) {
                // Set the initial number of conversations to retrieve. Otherwise it will display no conversations.
                this._numConversationsDisplayed = 20;
            }
        };

        /**
         * Turn on deleting.
         *
         * @private
         */
        Contacts.prototype._startDeleting = function() {
            this.messageArea.find(SELECTORS.CONTACTSAREA).addClass('editing');
        };

        /**
         * Turn off deleting.
         *
         * @private
         */
        Contacts.prototype._stopDeleting = function() {
            this.messageArea.find(SELECTORS.CONTACTSAREA).removeClass('editing');
        };

        /**
         * Handles viewing the list of conversations.
         *
         * @private
         */
        Contacts.prototype._viewConversations = function() {
            // If conversations is empty then try load some.
            if (this._numConversationsDisplayed === 0) {
                this._loadConversations();
            }

            this.messageArea.find(SELECTORS.CONTACTS).hide();
            this.messageArea.find(SELECTORS.CONVERSATIONS).show();
        };

        /**
         * Handles viewing the list of contacts.
         *
         * @private
         */
        Contacts.prototype._viewContacts = function() {
            // If contacts is empty then try load some.
            if (this._numContactsDisplayed === 0) {
                this._loadContacts();
            }

            this.messageArea.find(SELECTORS.CONVERSATIONS).hide();
            this.messageArea.find(SELECTORS.CONTACTS).show();
        };

        /**
         * Handles when a message is sent.
         *
         * @param {Event} event The message sent event
         * @param {int} userid The id of the user who the message was sent to
         * @param {String} text The message text
         * @private
         */
        Contacts.prototype._handleMessageSent = function(event, userid, text) {
            // Switch to viewing the conversations.
            this._viewConversations();
            // Get the user node.
            var user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
            // If the user has not been loaded yet, let's copy the element from contact or search panel to the conversation panel.
            if (user.length === 0) {
                // Let's clone the data on the contact page.
                var usercontact = this._getUserNode(SELECTORS.CONTACTS, userid);
                if (usercontact.length === 0) {
                    // No luck, maybe we sent the message to a user we searched for - check search page.
                    usercontact = this._getUserNode(SELECTORS.SEARCHRESULTSAREA, userid);
                }
                if (usercontact.length == 0) {
                    // Can't do much.
                    return;
                }
                user = usercontact.clone();
                // Change the data action attribute.
                user.attr('data-action', 'view-contact-msg');
                // Remove the 'no conversations' message.
                this.messageArea.find(SELECTORS.CONVERSATIONS + " " +
                    SELECTORS.NOCONTACTS).remove();
                // Increment the number of conversations displayed.
                this._numConversationsDisplayed++;
            }
            // Move the contact to the top of the list.
            user.prependTo(this.messageArea.find(SELECTORS.CONVERSATIONS));
            // Scroll to the top.
            this.messageArea.find(SELECTORS.CONVERSATIONS).scrollTop(0);
            // Get the new text to show.
            this._updateContactText(user, text, true);
            // Ensure user is selected.
            this._setSelectedUser("[data-userid='" + userid + "']");
        };

        /**
         * Handles loading conversations.
         *
         * @return {Promise|boolean} The promise resolved when the contact area has been rendered,
         * @private
         */
        Contacts.prototype._loadConversations = function() {
            if (this._isLoadingConversations) {
                return false;
            }

            // Tell the user we are loading items.
            this._isLoadingConversations = true;

            // Keep track of the number of contacts
            var numberreceived = 0;
            // Add loading icon to the end of the list.
            return Templates.render('core/loading', {}).then(function(html, js) {
                if (this._numConversationsDisplayed) {
                    Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS),
                        "<div style='text-align:center'>" + html + "</div>", js);
                } else { // No conversations, just replace contents.
                    Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS),
                        "<div style='text-align:center'>" + html + "</div>", js);
                }
                return this._getItems('core_message_data_for_messagearea_conversations',
                    this._numConversationsDisplayed, this._numConversationsToRetrieve);
            }.bind(this)).then(function(data) {
                numberreceived = data.contacts.length;
                data.isconversation = true;
                return Templates.render('core_message/message_area_contacts', data);
            }).then(function(html, js) {
                // Remove the loading icon.
                this.messageArea.find(SELECTORS.CONVERSATIONS + " " +
                    SELECTORS.LOADINGICON).remove();
                // Only append data if we got data back.
                if (numberreceived > 0) {
                    // Show the new content.
                    Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS), html, js);
                    // Increment the number of conversations displayed. We increment by the number of conversations we
                    // asked to retrieve not by the number that was actually retrieved, see MDL-55870.
                    this._numConversationsDisplayed += this._numConversationsToRetrieve;
                } else if (!this._numConversationsDisplayed) {
                    // If we didn't receive any contacts and there are currently none, then we want to show a message.
                    Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS), html, js);
                }
                // Mark that we are no longer busy loading data.
                this._isLoadingConversations = false;
            }.bind(this)).fail(Notification.exception);
        };

        /**
         * Handles loading contacts.
         *
         * @return {Promise|boolean} The promise resolved when the contact area has been rendered
         * @private
         */
        Contacts.prototype._loadContacts = function() {
            if (this._isLoadingContacts) {
                return false;
            }

            // Tell the user we are loading items.
            this._isLoadingContacts = true;

            // Keep track of the number of contacts
            var numberreceived = 0;
            // Add loading icon to the end of the list.
            return Templates.render('core/loading', {}).then(function(html, js) {
                if (this._numContactsDisplayed) {
                    Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONTACTS),
                        "<div style='text-align:center'>" + html + "</div>", js);
                } else { // No contacts, just replace contents.
                    Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONTACTS),
                        "<div style='text-align:center'>" + html + "</div>", js);
                }
                return this._getItems('core_message_data_for_messagearea_contacts',
                    this._numContactsDisplayed, this._numContactsToRetrieve);
            }.bind(this)).then(function(data) {
                numberreceived = data.contacts.length;
                data.isconversation = false;
                return Templates.render('core_message/message_area_contacts', data);
            }).then(function(html, js) {
                // Remove the loading icon.
                this.messageArea.find(SELECTORS.CONTACTS + " " +
                    SELECTORS.LOADINGICON).remove();
                // Only append data if we got data back.
                if (numberreceived > 0) {
                    // Show the new content.
                    Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONTACTS), html, js);
                    // Increment the number of contacts displayed.
                    this._numContactsDisplayed += numberreceived;
                } else if (!this._numContactsDisplayed) {
                    // If we didn't receive any contacts and there are currently none, then we want to show a message.
                    Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONTACTS), html, js);
                }
                // Mark that we are no longer busy loading data.
                this._isLoadingContacts = false;
            }.bind(this)).fail(Notification.exception);
        };

        /**
         * Handles viewing a particular conversation.
         *
         * @param {Event} event
         * @private
         */
        Contacts.prototype._viewConversation = function(event) {
            // Cancel any deletion of messages we may have.
            this.messageArea.trigger(Events.CANCELDELETEMESSAGES);

            var userid = $(event.currentTarget).data('userid');
            var messageid = $(event.currentTarget).data('messageid');
            var selector = "[data-userid='" + userid + "']";
            // If we have a specific message id then we did a search and the contact may appear in multiple
            // places - we don't want to highlight them all.
            if (messageid) {
                selector = "[data-messageid='" + messageid + "']";
            }

            this._setSelectedUser(selector);
            this.messageArea.trigger(Events.CONVERSATIONSELECTED, userid);
            // Don't highlight the contact because the message region has changed.
            this.messageArea.find(SELECTORS.SELECTEDVIEWPROFILE).removeClass('selected');
            this._showMessagingArea();
        };

        /**
         * Handles viewing a particular contact.
         *
         * @param {Event} event
         * @private
         */
        Contacts.prototype._viewContact = function(event) {
            // Cancel any deletion of messages we may have.
            this.messageArea.trigger(Events.CANCELDELETEMESSAGES);

            var userid = $(event.currentTarget).data('userid');
            this._setSelectedUser("[data-userid='" + userid + "']");
            this.messageArea.trigger(Events.CONTACTSELECTED, userid);
            // Don't highlight the conversation because the message region has changed.
            this.messageArea.find(SELECTORS.SELECTEDVIEWCONVERSATION).removeClass('selected');
            this._showMessagingArea();
        };

        /**
         * Handles returning a list of items to display.
         *
         * @param {String} webservice The web service to call
         * @param {int} limitfrom
         * @param {int} limitnum
         * @return {Promise} The promise resolved when the contact area has been rendered
         * @private
         */
        Contacts.prototype._getItems = function(webservice, limitfrom, limitnum) {
            // Call the web service to return the data we want to view.
            var promises = Ajax.call([{
                methodname: webservice,
                args: {
                    userid: this.messageArea.getCurrentUserId(),
                    limitfrom: limitfrom,
                    limitnum: limitnum
                }
            }]);

            return promises[0];
        };

        /**
         * Handles deleting a conversation.
         *
         * @param {Event} event
         * @param {int} userid The user id belonging to the messages we are deleting.
         * @private
         */
        Contacts.prototype._deleteConversation = function(event, userid) {
            // Remove the conversation.
            this._removeContact(SELECTORS.CONVERSATIONS, userid);
            this._numConversationsDisplayed--;
            this._hideMessagingArea();
            // Now we have done all the deletion we can set the flag back to false.
            this._stopDeleting();
        };

        /**
         * Handles updating the last message in the contact.
         *
         * @param {Event} event
         * @param {int} userid The user id belonging to the messages we are deleting
         * @param {jQuery|null} updatemessage The message we need to update the contact panel with
         * @private
         */
        Contacts.prototype._updateLastMessage = function(event, userid, updatemessage) {
            // Check if the last message needs updating.
            if (updatemessage) {
                var user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
                var updatemessagetext = updatemessage.find(SELECTORS.MESSAGETEXT).text().trim();
                var sentbyuser = false;
                if (updatemessage.data('useridto') == userid) {
                    // Must have been sent by the currently logged in user.
                    sentbyuser = true;
                }

                this._updateContactText(user, updatemessagetext, sentbyuser);
            }

            // Now we have done all the deletion we can set the flag back to false.
            this._stopDeleting();
        };

        /**
         * Handles adding a contact to the list.
         *
         * @private
         */
        Contacts.prototype._addContact = function() {
            this.messageArea.find(SELECTORS.CONTACTS).empty();
            this._numContactsDisplayed = 0;
            this._loadContacts();
        };

        /**
         * Handles removing a contact from the list.
         *
         * @param {String} selector
         * @param {int} userid
         * @private
         */
        Contacts.prototype._removeContact = function(selector, userid) {
            this._getUserNode(selector, userid).remove();
            this._numContactsDisplayed--;
        };

        /**
         * Handles marking a contact as blocked on the list.
         *
         * @param {int} userid
         * @private
         */
        Contacts.prototype._blockContact = function(userid) {
            var user = this._getUserNode(SELECTORS.CONTACTS, userid);
            user.find(SELECTORS.CONTACTICONBLOCKED).removeClass('hidden');

            user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
            user.find(SELECTORS.CONTACTICONBLOCKED).removeClass('hidden');

            user = this._getUserNode(SELECTORS.SEARCHRESULTSAREA, userid);
            user.find(SELECTORS.CONTACTICONBLOCKED).removeClass('hidden');
        };

        /**
         * Handles marking a contact as unblocked on the list.
         *
         * @param {int} userid
         * @private
         */
        Contacts.prototype._unblockContact = function(userid) {
            var user = this._getUserNode(SELECTORS.CONTACTS, userid);
            user.find(SELECTORS.CONTACTICONBLOCKED).addClass('hidden');

            user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
            user.find(SELECTORS.CONTACTICONBLOCKED).addClass('hidden');

            user = this._getUserNode(SELECTORS.SEARCHRESULTSAREA, userid);
            user.find(SELECTORS.CONTACTICONBLOCKED).addClass('hidden');
        };

        /**
         * Handles retrieving a user node from a list.
         *
         * @param {String} selector
         * @param {int} userid
         * @return {jQuery} The user node
         * @private
         */
        Contacts.prototype._getUserNode = function(selector, userid) {
            return this.messageArea.find(selector + " " + SELECTORS.CONTACT +
                "[data-userid='" + userid + "']");
        };

        /**
         * Handles selecting a contact in the list.
         *
         * @param {String} selector
         * @private
         */
        Contacts.prototype._setSelectedUser = function(selector) {
            // Remove the 'selected' class from any other contact.
            this.messageArea.find(SELECTORS.CONTACT).removeClass('selected');
            this.messageArea.find(SELECTORS.CONTACT).attr('aria-pressed', false);
            // Set the tab for the user to selected.
            this.messageArea.find(SELECTORS.CONTACT + selector).addClass('selected');
            this.messageArea.find(SELECTORS.CONTACT + selector).attr('aria-pressed', true);
        };

        /**
         * Converts a text message into the text that should be stored in the contact list
         *
         * @param {String} text
         * @return {String} The altered text
         */
        Contacts.prototype._getContactText = function(text) {
            if (text.length > this._messageLength) {
                text = text.substr(0, this._messageLength - 3);
                text += '...';
            }

            // Text node prevents script injection through HTML entities.
            return document.createTextNode(text);
        };

        /**
         * Handles updating the contact text.
         *
         * @param {jQuery} user The user to update
         * @param {String} text The text to update the contact with
         * @param {Boolean} sentbyuser Was it sent by the currently logged in user?
         * @private
         */
        Contacts.prototype._updateContactText = function(user, text, sentbyuser) {
            // Get the text we will display on the contact panel.
            text = this._getContactText(text);
            if (sentbyuser) {
                Str.get_string('you', 'message').done(function(string) {
                    // Ensure we display that the message is from this user.
                    user.find(SELECTORS.LASTMESSAGEUSER).empty().append(string);
                }).always(function() {
                    user.find(SELECTORS.LASTMESSAGETEXT).empty().append(text);
                });
            } else {
                user.find(SELECTORS.LASTMESSAGEUSER).empty();
                user.find(SELECTORS.LASTMESSAGETEXT).empty().append(text);
            }
        };

        /**
         * Shifts focus to the next contact in the list.
         *
         * @param {event} e The jquery event
         * @param {object} data Additional event data
         */
        Contacts.prototype._selectNextContact = function(e, data) {
            var contact = $(e.target).closest(SELECTORS.CONTACT);
            var next = contact.next();
            next.focus();

            data.originalEvent.preventDefault();
            data.originalEvent.stopPropagation();
        };

        /**
         * Shifts focus to the previous contact in the list.
         *
         * @param {event} e The jquery event
         * @param {object} data Additional event data
         */
        Contacts.prototype._selectPreviousContact = function(e, data) {
            var contact = $(e.target).closest(SELECTORS.CONTACT);
            var previous = contact.prev();
            previous.focus();

            data.originalEvent.preventDefault();
            data.originalEvent.stopPropagation();
        };

        /**
         * Shifts focus to the next course in the list.
         *
         * @param {event} e The jquery event
         * @param {object} data Additional event data
         */
        Contacts.prototype._selectNextCourse = function(e, data) {
            var course = $(e.target).closest(SELECTORS.COURSE);
            course.next().focus();

            data.originalEvent.preventDefault();
            data.originalEvent.stopPropagation();
        };

        /**
         * Shifts focus to the previous course in the list.
         *
         * @param {event} e The jquery event
         * @param {object} data Additional event data
         */
        Contacts.prototype._selectPreviousCourse = function(e, data) {
            var course = $(e.target).closest(SELECTORS.COURSE);
            course.prev().focus();

            data.originalEvent.preventDefault();
            data.originalEvent.stopPropagation();
        };

        /**
         * Shifts focus to the next conversation in the list.
         *
         * @param {event} e The jquery event
         * @param {object} data Additional event data
         */
        Contacts.prototype._selectNextConversation = function(e, data) {
            var conversation = $(e.target).closest(SELECTORS.VIEWCONVERSATION);
            var next = conversation.next();
            next.focus();

            data.originalEvent.preventDefault();
            data.originalEvent.stopPropagation();
        };

        /**
         * Shifts focus to the previous conversation in the list.
         *
         * @param {event} e The jquery event
         * @param {object} data Additional event data
         */
        Contacts.prototype._selectPreviousConversation = function(e, data) {
            var conversation = $(e.target).closest(SELECTORS.VIEWCONVERSATION);
            var previous = conversation.prev();
            previous.focus();

            data.originalEvent.preventDefault();
            data.originalEvent.stopPropagation();
        };

        /**
         * Flags the search area as seaching.
         */
        Contacts.prototype._setSearching = function() {
            $(SELECTORS.SEARCHTEXTAREA).addClass('searching');
        };

        /**
         * Flags the search area as seaching.
         */
        Contacts.prototype._clearSearching = function() {
            $(SELECTORS.SEARCHTEXTAREA).removeClass('searching');
        };

        /**
         * Make the messaging area visible.
         */
        Contacts.prototype._showMessagingArea = function() {
            this.messageArea.find(SELECTORS.MESSAGINGAREA)
                .removeClass('hide-messages')
                .addClass('show-messages');
        };

        /**
         * Hide the messaging area.
         */
        Contacts.prototype._hideMessagingArea = function() {
            this.messageArea.find(SELECTORS.MESSAGINGAREA)
                .removeClass('show-messages')
                .addClass('hide-messages');
        };

        return Contacts;
    }
);
