<commit_before>﻿/// <reference path="Scripts/jquery-1.7.js" />
/// <reference path="Scripts/jQuery.tmpl.js" />
/// <reference path="Scripts/jquery.cookie.js" />
/// <reference path="Chat.toast.js" />
/// <reference path="Scripts/livestamp.min.js" />
/// <reference path="Scripts/moment.min.js" />

/*jshint bitwise:false */
(function ($, window, document, chat, utility, emoji, linkify) {
    "use strict";

    var $chatArea = null,
        $tabs = null,
        $submitButton = null,
        $newMessage = null,
        $roomActions = null,
        $toast = null,
        $disconnectDialog = null,
        $downloadIcon = null,
        $downloadDialog = null,
        $downloadDialogButton = null,
        $downloadRange = null,
        $logout = null,
        $help = null,
        $ui = null,
        $sound = null,
        templates = null,
        focus = true,
        readOnly = false,
        Keys = { Up: 38, Down: 40, Esc: 27, Enter: 13, Slash: 47, Space: 32, Tab: 9, Question: 191 },
        scrollTopThreshold = 75,
        toast = window.chat.toast,
        preferences = null,
        $login = null,
        lastCycledMessage = null,
        $helpPopup = null,
        $helpBody = null,
        helpHeight = 0,
        $shortCutHelp = null,
        $globalCmdHelp = null,
        $roomCmdHelp = null,
        $userCmdHelp = null,
        $updatePopup = null,
        $window = $(window),
        $document = $(document),
        $lobbyRoomFilterForm = null,
        lobbyLoaded = false,
        $roomFilterInput = null,
        $closedRoomFilter = null,
        updateTimeout = 15000,
        $richness = null,
        lastPrivate = null,
        roomCache = {},
        $reloadMessageNotification = null,
        popoverTimer = null,
        $connectionStatus = null,
        connectionState = -1,
        $connectionStateChangedPopover = null,
        connectionStateIcon = null,
        $connectionInfoPopover = null,
        $connectionInfoContent = null,
        $fileUploadButton = null,
        $hiddenFile = null,
        $uploadForm = null,
        $clipboardUpload = null,
        $clipboardUploadPreview = null,
        $clipboardUploadButton = null,
        $fileRoom = null,
        $fileConnectionId = null,
        connectionInfoStatus = null,
        connectionInfoTransport = null,
        $topicBar = null,
        $loadingHistoryIndicator = null,
        trimRoomHistoryFrequency = 1000 * 60 * 2, // 2 minutes in ms
        $loadMoreRooms = null,
        sortedRoomList = null,
        maxRoomsToLoad = 100,
        lastLoadedRoomIndex = 0,
        $lobbyPrivateRooms = null,
        $lobbyOtherRooms = null,
        $roomLoadingIndicator = null,
        roomLoadingDelay = 250,
        roomLoadingTimeout = null,
        Room = chat.Room,
        $unreadNotificationCount = null;

    function getRoomNameFromHash(hash) {
        if (hash.length && hash[0] === '/') {
            hash = hash.substr(1);
        }

        var parts = hash.split('/');
        if (parts[0] === 'rooms') {
            return parts[1];
        }

        return null;
    }

    function getRoomId(roomName) {
        return window.escape(roomName.toString().toLowerCase()).replace(/[^A-Za-z0-9]/g, '_');
    }

    function getUserClassName(userName) {
        return '[data-name="' + userName + '"]';
    }

    function getRoomPreferenceKey(roomName) {
        return '_room_' + roomName;
    }

    function setRoomLoading(isLoading, roomName) {
        if (isLoading) {
            var room = getRoomElements(roomName);
            if (!room.isInitialized()) {
                roomLoadingTimeout = window.setTimeout(function () {
                    $roomLoadingIndicator.find('i').addClass('icon-spin');
                    $roomLoadingIndicator.show();
                }, roomLoadingDelay);
            }
        } else {
            if (roomLoadingTimeout) {
                clearTimeout(roomLoadingDelay);
            }
            $roomLoadingIndicator.hide();
            $roomLoadingIndicator.find('i').removeClass('icon-spin');
        }
    }

    function populateLobbyRoomList(item, template, listToPopulate) {
        $.tmpl(template, item).appendTo(listToPopulate);
    }

    function sortRoomList(listToSort) {
        var sortedList = listToSort.sort(function (a, b) {
            if (a.Closed && !b.Closed) {
                return 1;
            } else if (b.Closed && !a.Closed) {
                return -1;
            }

            if (a.Count > b.Count) {
                return -1;
            } else if (b.Count > a.Count) {
                return 1;
            }

            return a.Name.toString().toUpperCase().localeCompare(b.Name.toString().toUpperCase());
        });
        return sortedList;
    }

    function getRoomElements(roomName) {
        var roomId = getRoomId(roomName);
        var room = new Room($('#tabs-' + roomId),
                        $('#userlist-' + roomId),
                        $('#userlist-' + roomId + '-owners'),
                        $('#userlist-' + roomId + '-active'),
                        $('#messages-' + roomId),
                        $('#roomTopic-' + roomId));
        return room;
    }

    function getCurrentRoomElements() {
        var $tab = $tabs.find('li.current');
        var room;
        if ($tab.data('name') === 'Lobby') {
            room = new Room($tab,
                $('#userlist-lobby'),
                $('#userlist-lobby-owners'),
                $('#userlist-lobby-active'),
                $('.messages.current'),
                $('.roomTopic.current'));
        } else {
            room = new Room($tab,
                $('.users.current'),
                $('.userlist.current .owners'),
                $('.userlist.current .active'),
                $('.messages.current'),
                $('.roomTopic.current'));
        }
        return room;
    }

    function getAllRoomElements() {
        var rooms = [];
        $("ul#tabs > li.room").each(function () {
            rooms[rooms.length] = getRoomElements($(this).data("name"));
        });
        return rooms;
    }

    function getLobby() {
        return getRoomElements('Lobby');
    }

    function getRoomsNames() {
        var lobby = getLobby();

        return lobby.users.find('li')
                     .map(function () {
                         var room = $(this).data('name');
                         roomCache[room.toString().toUpperCase()] = true;
                         return room + ' ';
                     });
    }

    function updateLobbyRoomCount(room, count) {
        var lobby = getLobby(),
            $targetList = room.Private === true ? lobby.owners : lobby.users,
            $room = $targetList.find('[data-room="' + room.Name + '"]'),
            $count = $room.find('.count'),
            roomName = room.Name.toString().toUpperCase();

        $room.css('background-color', '#f5f5f5');
        if (count === 0) {
            $count.text('Unoccupied');
        } else if (count === 1) {
            $count.text('1 occupant');
        } else {
            $count.text(count + ' occupants');
        }

        if (room.Private === true) {
            $room.addClass('locked');
        } else {
            $room.removeClass('locked');
        }

        if (room.Closed === true) {
            $room.addClass('closed');
        } else {
            $room.removeClass('closed');
        }

        var nextListElement = getNextRoomListElement($targetList, roomName, count, room.Closed);

        $room.data('count', count);
        if (nextListElement !== null) {
            $room.insertBefore(nextListElement);
        } else {
            $room.appendTo($targetList);
        }

        // Do a little animation
        $room.animate({ backgroundColor: '#ffffff' }, 800);
    }

    function addRoomToLobby(roomViewModel) {
        var lobby = getLobby(),
            $room = templates.lobbyroom.tmpl(roomViewModel),
            roomName = roomViewModel.Name.toString().toUpperCase(),
            count = roomViewModel.Count,
            closed = roomViewModel.Closed,
            $targetList = roomViewModel.Private ? lobby.owners : lobby.users;

        var nextListElement = getNextRoomListElement($targetList, roomName, count, closed);

        if (nextListElement !== null) {
            $room.insertBefore(nextListElement);
        } else {
            $room.appendTo($targetList);
        }

        filterIndividualRoom($room);
    }
    
    function getNextRoomListElement($targetList, roomName, count, closed) {
        var nextListElement = null;

        // move the item to before the next element
        $targetList.find('li').each(function () {
            var $this = $(this),
                liRoomCount = $this.data('count'),
                liRoomClosed = $this.hasClass('closed'),
                name = $this.data('name'),
                nameComparison;

            if (name === undefined) {
                return true;
            }

            nameComparison = name.toString().toUpperCase().localeCompare(roomName);

            // skip this element
            if (nameComparison === 0) {
                return true;
            }

            // skip closed rooms which always go after unclosed ones
            if (!liRoomClosed && closed) {
                return true;
            }

            // skip where we have more occupants
            if (liRoomCount > count) {
                return true;
            }

            // skip where we have the same number of occupants but the room is alphabetically earlier
            if (liRoomCount === count && nameComparison < 0) {
                return true;
            }

            nextListElement = $this;
            return false;
        });

        return nextListElement;
    }
    
    function filterIndividualRoom($room) {
        var filter = $roomFilterInput.val().toUpperCase(),
            showClosedRooms = $closedRoomFilter.is(':checked');
        
        if ($room.data('room').toString().toUpperCase().score(filter) > 0.0 && (showClosedRooms || !$room.is('.closed'))) {
            $room.show();
        } else {
            $room.hide();
        }
    }

    function addRoom(roomViewModel) {
        // Do nothing if the room exists
        var roomName = roomViewModel.Name,
            room = getRoomElements(roomViewModel.Name),
            roomId = null,
            viewModel = null,
            $messages = null,
            $roomTopic = null,
            scrollHandler = null,
            userContainer = null;

        if (room.exists()) {
            return false;
        }

        roomId = getRoomId(roomName);

        // Add the tab
        viewModel = {
            id: roomId,
            name: roomName,
            closed: roomViewModel.Closed
        };

        if (!roomCache[roomName.toString().toUpperCase()]) {
            addRoomToLobby(roomViewModel);
        }

        roomCache[roomName.toString().toUpperCase()] = true;

        templates.tab.tmpl(viewModel).data('name', roomName).appendTo($tabs);

        $messages = $('<ul/>').attr('id', 'messages-' + roomId)
                              .addClass('messages')
                              .appendTo($chatArea)
                              .hide();

        $roomTopic = $('<div/>').attr('id', 'roomTopic-' + roomId)
                              .addClass('roomTopic')
                              .appendTo($topicBar)
                              .hide();

        userContainer = $('<div/>').attr('id', 'userlist-' + roomId)
            .addClass('users')
            .appendTo($chatArea).hide();
        templates.userlist.tmpl({ listname: '- Room Owners', id: 'userlist-' + roomId + '-owners' })
            .addClass('owners')
            .appendTo(userContainer);
        templates.userlist.tmpl({ listname: '- Users', id: 'userlist-' + roomId + '-active' })
            .appendTo(userContainer);
        userContainer.find('h3').click(function () {
            if ($.trim($(this).text())[0] === '-') {
                $(this).text($(this).text().replace('-', '+'));
            } else {
                $(this).text($(this).text().replace('+', '-'));
            }
            $(this).next().toggle(0);
            return false;
        });
        
        $tabs.find('li')
            .not('.lobby')
            .sortElements(function (a, b) {
                return $(a).data('name').toString().toUpperCase() > $(b).data('name').toString().toUpperCase() ? 1 : -1;
            });

        scrollHandler = function (ev) {
            var messageId = null;

            // Do nothing if there's nothing else
            if ($(this).data('full') === true) {
                return;
            }

            // If you're we're near the top, raise the event, but if the scroll
            // bar is small enough that we're at the bottom edge, ignore it.
            // We have to use the ui version because the room object above is
            // not fully initialized, so there are no messages.
            if ($(this).scrollTop() <= scrollTopThreshold && !ui.isNearTheEnd(roomId)) {
                var $child = $messages.children('.message:first');
                if ($child.length > 0) {
                    messageId = $child.attr('id')
                                      .substr(2); // Remove the "m-"
                    $ui.trigger(ui.events.scrollRoomTop, [{ name: roomName, messageId: messageId }]);
                }
            }
        };

        // Hookup the scroll handler since event delegation doesn't work with scroll events
        $messages.bind('scroll', scrollHandler);

        // Store the scroll handler so we can remove it later
        $messages.data('scrollHandler', scrollHandler);

        setAccessKeys();

        lobbyLoaded = false;
        return true;
    }

    function removeRoom(roomName) {
        var room = getRoomElements(roomName),
            scrollHandler = null;

        if (room.exists()) {
            // Remove the scroll handler from this room
            scrollHandler = room.messages.data('scrollHandler');
            room.messages.unbind('scrollHandler', scrollHandler);

            room.tab.remove();
            room.messages.remove();
            room.users.remove();
            room.roomTopic.remove();
            setAccessKeys();
        }
    }

    function setAccessKeys() {
        $.each($tabs.find('li.room'), function (index, item) {
            $(item).children('button').attr('accesskey', getRoomAccessKey(index));
        });
    }

    function getRoomAccessKey(index) {
        if (index < 10) {
            return index + 1;
        }
        return 0;
    }

    function navigateToRoom(roomName) {
        var hash = (document.location.hash || '#').substr(1),
            hashRoomName = getRoomNameFromHash(hash);

        if (hashRoomName && hashRoomName === roomName) {
            ui.setActiveRoomCore(roomName);
        }
        else {
            document.location.hash = '#/rooms/' + roomName;
        }
    }

    function processMessage(message, roomName) {
        var isFromCollapibleContentProvider = isFromCollapsibleContentProvider(message.message),
            collapseContent = shouldCollapseContent(message.message, roomName);

        message.when = message.date.formatTime(true);
        message.fulldate = message.date.toLocaleString();

        if (collapseContent) {
            message.message = collapseRichContent(message.message);
        }
    }

    function isFromCollapsibleContentProvider(content) {
        return content.indexOf('class="collapsible_box') > -1; // leaving off trailing " purposefully
    }

    function shouldCollapseContent(content, roomName) {
        var collapsible = isFromCollapsibleContentProvider(content),
            collapseForRoom = roomName ? getRoomPreference(roomName, 'blockRichness') : getActiveRoomPreference('blockRichness');

        return collapsible && collapseForRoom;
    }

    function collapseRichContent(content) {
        content = content.replace(/class="collapsible_box/g, 'style="display: none;" class="collapsible_box');
        return content.replace(/class="collapsible_title"/g, 'class="collapsible_title" title="Content collapsed because you have Rich-Content disabled"');
    }

    function triggerFocus() {
        if (!utility.isMobile && !readOnly) {
            $newMessage.focus();
        }

        if (focus === false) {
            focus = true;
            $ui.trigger(ui.events.focusit);
        }
    }

    function loadPreferences() {
        // Restore the global preferences
    }

    function toggleRichness($element, roomName) {
        var blockRichness = roomName ? getRoomPreference(roomName, 'blockRichness') : preferences.blockRichness;

        if (blockRichness === true) {
            $element.addClass('off');
        }
        else {
            $element.removeClass('off');
        }
    }

    function toggleElement($element, preferenceName, roomName) {
        var value = roomName ? getRoomPreference(roomName, preferenceName) : preferences[preferenceName];

        if (value === true) {
            $element.removeClass('off');
        }
        else {
            $element.addClass('off');
        }
    }

    function loadRoomPreferences(roomName) {
        var roomPreferences = getRoomPreference(roomName);

        // Placeholder for room level preferences
        toggleElement($sound, 'hasSound', roomName);
        toggleElement($toast, 'canToast', roomName);
        toggleRichness($richness, roomName);
    }

    function setPreference(name, value) {
        preferences[name] = value;

        $(ui).trigger(ui.events.preferencesChanged);
    }

    function setRoomPreference(roomName, name, value) {
        var roomPreferences = preferences[getRoomPreferenceKey(roomName)];

        if (!roomPreferences) {
            roomPreferences = {};
            preferences[getRoomPreferenceKey(roomName)] = roomPreferences;
        }

        roomPreferences[name] = value;

        $ui.trigger(ui.events.preferencesChanged);
    }

    function getRoomPreference(roomName, name) {
        return (preferences[getRoomPreferenceKey(roomName)] || {})[name];
    }

    function getActiveRoomPreference(name) {
        var room = getCurrentRoomElements();
        return getRoomPreference(room.getName(), name);
    }

    function anyRoomPreference(name, value) {
        for (var key in preferences) {
            if (preferences[key][name] === value) {
                return true;
            }
        }
        return false;
    }

    function triggerSend() {
        if (readOnly) {
            return;
        }

        var msg = $.trim($newMessage.val());

        focus = true;

        if (msg) {
            if (msg.toUpperCase() === '/LOGIN') {
                ui.showLogin();
            }
            else {
                $ui.trigger(ui.events.sendMessage, [msg]);
            }
        }

        $newMessage.val('');
        $newMessage.focus();

        // always scroll to bottom after new message sent
        var room = getCurrentRoomElements();
        room.scrollToBottom();
        room.removeSeparator();
    }

    function updateNote(userViewModel, $user) {
        var $title = $user.find('.name'),
            noteText = userViewModel.note,
            noteTextEncoded = null,
            requireRoomUpdate = false;

        if (userViewModel.noteClass === 'afk') {
            noteText = userViewModel.note + ' (' + userViewModel.timeAgo + ')';
            requireRoomUpdate = ui.setUserInActive($user);
        }
        else if (userViewModel.active) {
            requireRoomUpdate = ui.setUserActive($user);
        }
        else {
            requireRoomUpdate = ui.setUserInActive($user);
        }

        noteTextEncoded = $('<div/>').html(noteText).text();

        // Remove all classes and the text
        $title.removeAttr('title');

        if (userViewModel.note) {
            $title.attr('title', noteTextEncoded);
        }

        if (requireRoomUpdate) {
            $user.each(function () {
                var room = getRoomElements($(this).data('inroom'));
                room.updateUserStatus($(this));
                room.sortLists($(this));
            });
        }
    }

    function updateFlag(userViewModel, $user) {
        var $flag = $user.find('.flag');

        $flag.removeAttr('class');
        $flag.addClass('flag');
        $flag.removeAttr('title');

        if (userViewModel.flagClass) {
            $flag.addClass(userViewModel.flagClass);
            $flag.show();
        } else {
            $flag.hide();
        }

        if (userViewModel.country) {
            $flag.attr('title', userViewModel.country);
        }
    }

    function updateRoomTopic(roomViewModel) {
        var room = getRoomElements(roomViewModel.Name);
        var topic = roomViewModel.Topic;
        var topicHtml = topic === '' ? 'You\'re chatting in ' + roomViewModel.Name : ui.processContent(topic);
        var roomTopic = room.roomTopic;
        var isVisibleRoom = getCurrentRoomElements().getName() === roomViewModel.Name;

        if (isVisibleRoom) {
            roomTopic.hide();
        }

        roomTopic.html(topicHtml);

        if (isVisibleRoom) {
            roomTopic.fadeIn(2000);
        }
    }

    function getConnectionStateChangedPopoverOptions(statusText) {
        var options = {
            html: true,
            trigger: 'hover',
            template: $connectionStateChangedPopover,
            content: function () {
                return statusText;
            }
        };
        return options;
    }

    function getConnectionInfoPopoverOptions(transport) {
        var options = {
            html: true,
            trigger: 'hover',
            delay: {
                show: 0,
                hide: 500
            },
            template: $connectionInfoPopover,
            content: function () {
                var connectionInfo = $connectionInfoContent;
                connectionInfo.find(connectionInfoStatus).text('Status: Connected');
                connectionInfo.find(connectionInfoTransport).text('Transport: ' + transport);
                return connectionInfo.html();
            }
        };
        return options;
    }

    function loadMoreLobbyRooms() {
        var lobby = getLobby(),
            moreRooms = sortedRoomList.slice(lastLoadedRoomIndex, lastLoadedRoomIndex + maxRoomsToLoad);

        populateLobbyRoomList(moreRooms, templates.lobbyroom, lobby.users);
        lastLoadedRoomIndex = lastLoadedRoomIndex + maxRoomsToLoad;
        
        // re-filter lists
        $lobbyRoomFilterForm.submit();
    }

    var ui = {

        //lets store any events to be triggered as constants here to aid intellisense and avoid
        //string duplication everywhere
        events: {
            closeRoom: 'jabbr.ui.closeRoom',
            prevMessage: 'jabbr.ui.prevMessage',
            openRoom: 'jabbr.ui.openRoom',
            nextMessage: 'jabbr.ui.nextMessage',
            activeRoomChanged: 'jabbr.ui.activeRoomChanged',
            scrollRoomTop: 'jabbr.ui.scrollRoomTop',
            typing: 'jabbr.ui.typing',
            sendMessage: 'jabbr.ui.sendMessage',
            focusit: 'jabbr.ui.focusit',
            blurit: 'jabbr.ui.blurit',
            preferencesChanged: 'jabbr.ui.preferencesChanged',
            loggedOut: 'jabbr.ui.loggedOut',
            reloadMessages: 'jabbr.ui.reloadMessages',
            fileUploaded: 'jabbr.ui.fileUploaded'
        },

        help: {
            shortcut: 'shortcut',
            global: 'global',
            room: 'room',
            user: 'user'
        },

        initialize: function (state) {
            $ui = $(this);
            preferences = state || {};
            $chatArea = $('#chat-area');
            $tabs = $('#tabs');
            $submitButton = $('#send');
            $newMessage = $('#new-message');
            $roomActions = $('#room-actions');
            $toast = $('#room-preferences .toast');
            $sound = $('#room-preferences .sound');
            $richness = $('#room-preferences .richness');
            $downloadIcon = $('#room-preferences .download');
            $downloadDialog = $('#download-dialog');
            $downloadDialogButton = $('#download-dialog-button');
            $downloadRange = $('#download-range');
            $logout = $('#preferences .logout');
            $help = $('#preferences .help');
            $disconnectDialog = $('#disconnect-dialog');
            $login = $('#jabbr-login');
            $helpPopup = $('#jabbr-help');
            $clipboardUpload = $('#jabbr-clipboard-upload');
            $clipboardUploadPreview = $('#jabbr-clipboard-upload #clipboard-upload-preview');
            $clipboardUploadButton = $('#jabbr-clipboard-upload #clipboard-upload');
            $helpBody = $('#jabbr-help .help-body');
            $shortCutHelp = $('#jabbr-help #shortcut');
            $globalCmdHelp = $('#jabbr-help #global');
            $roomCmdHelp = $('#jabbr-help #room');
            $userCmdHelp = $('#jabbr-help #user');
            $updatePopup = $('#jabbr-update');
            focus = true;
            $lobbyRoomFilterForm = $('#room-filter-form');
            $roomFilterInput = $('#room-filter');
            $closedRoomFilter = $('#room-filter-closed');
            templates = {
                userlist: $('#new-userlist-template'),
                user: $('#new-user-template'),
                message: $('#new-message-template'),
                notification: $('#new-notification-template'),
                separator: $('#message-separator-template'),
                tab: $('#new-tab-template'),
                gravatarprofile: $('#gravatar-profile-template'),
                commandhelp: $('#command-help-template'),
                multiline: $('#multiline-content-template'),
                lobbyroom: $('#new-lobby-room-template'),
                otherlobbyroom: $('#new-other-lobby-room-template')
            };
            $reloadMessageNotification = $('#reloadMessageNotification');
            $fileUploadButton = $('.upload-button');
            $hiddenFile = $('#hidden-file');
            $uploadForm = $('#upload');
            $fileRoom = $('#file-room');
            $fileConnectionId = $('#file-connection-id');
            $connectionStatus = $('#connectionStatus');

            $connectionStateChangedPopover = $('#connection-state-changed-popover');
            connectionStateIcon = '#popover-content-icon';
            $connectionInfoPopover = $('#connection-info-popover');
            $connectionInfoContent = $('#connection-info-content');
            connectionInfoStatus = '#connection-status';
            connectionInfoTransport = '#connection-transport';
            $topicBar = $('#topic-bar');
            $loadingHistoryIndicator = $('#loadingRoomHistory');

            $loadMoreRooms = $('#load-more-rooms-item');
            $lobbyPrivateRooms = $('#lobby-private');
            $lobbyOtherRooms = $('#lobby-other');
            $roomLoadingIndicator = $('#room-loading');

            $unreadNotificationCount = $('#notification-unread-count');
            
            if (toast.canToast()) {
                $toast.show();
            }
            else {
                $richness.css({ left: '55px' });
                $downloadIcon.css({ left: '90px' });
                // We need to set the toast setting to false
                preferences.canToast = false;
                $toast.hide();
            }

            // DOM events
            $document.on('click', 'h3.collapsible_title', function () {
                var nearEnd = ui.isNearTheEnd();

                $(this).next().toggle(0, function () {
                    if (nearEnd) {
                        ui.scrollToBottom();
                    }
                });
            });

            $document.on('click', '#tabs li', function () {
                ui.setActiveRoom($(this).data('name'));
            });

            $document.on('mousedown', '#tabs li.room', function (ev) {
                // if middle mouse
                if (ev.which === 2) {
                    $ui.trigger(ui.events.closeRoom, [$(this).data('name')]);
                }
            });

            $document.on('click', 'li.room .room-row', function () {
                var roomName = $(this).parent().data('name'),
                    room = getRoomElements(roomName);

                if (room.exists()) {
                    ui.setActiveRoom(roomName);
                }
                else {
                    $ui.trigger(ui.events.openRoom, [roomName]);
                }
            });

            $document.on('click', '#load-more-rooms-item', function () {
                var spinner = $loadMoreRooms.find('i'),
                    lobby = getLobby();
                spinner.addClass('icon-spin');
                spinner.show();
                var loader = $loadMoreRooms.find('.load-more-rooms a');
                loader.html(' Loading more rooms...');
                loadMoreLobbyRooms();
                spinner.hide();
                spinner.removeClass('icon-spin');
                loader.html('Load More...');
                if (lastLoadedRoomIndex < sortedRoomList.length) {
                    $loadMoreRooms.show();
                } else {
                    $loadMoreRooms.hide();
                }
            });

            $document.on('click', '#tabs li .close', function (ev) {
                var roomName = $(this).closest('li').data('name');

                $ui.trigger(ui.events.closeRoom, [roomName]);

                ev.preventDefault();
                return false;
            });

            // handle click on notifications
            $document.on('click', '.notification a.info', function (ev) {
                var $notification = $(this).closest('.notification');

                if ($(this).hasClass('collapse')) {
                    ui.collapseNotifications($notification);
                }
                else {
                    ui.expandNotifications($notification);
                }
            });

            $document.on('click', '#reloadMessageNotification a', function () {
                $ui.trigger(ui.events.reloadMessages);
            });

            // handle tab cycling - we skip the lobby when cycling
            // handle shift+/ - display help command
            $document.on('keydown', function (ev) {
                // ctrl + tab event is sent to the page in firefox when the user probably means to change browser tabs
                if (ev.keyCode === Keys.Tab && !ev.ctrlKey && $newMessage.val() === "") {
                    var current = getCurrentRoomElements(),
                        index = current.tab.index(),
                        tabCount = $tabs.children().length - 1;

                    if (!ev.shiftKey) {
                        // Next tab
                        index = index % tabCount + 1;
                    } else {
                        // Prev tab
                        index = (index - 1) || tabCount;
                    }

                    ui.setActiveRoom($tabs.children().eq(index).data('name'));
                    if (!readOnly) {
                        $newMessage.focus();
                    }
                }

                if (!$newMessage.is(':focus') && ev.shiftKey && ev.keyCode === Keys.Question) {
                    ui.showHelp();
                    // Prevent the ? be recorded in the message box
                    ev.preventDefault();
                }
            });

            // hack to get Chrome to scroll back to top of help body
            // when redisplaying it after scrolling down and closing it
            $helpPopup.on('hide', function () {
                $helpBody.scrollTop(0);
            });

            // set the height of the help body when displaying the help dialog
            // so that the scroll bar does not block the rounded corners
            $helpPopup.on('show', function () {
                if (helpHeight === 0) {
                    helpHeight = $helpPopup.height() - $helpBody.position().top - 10;
                }
                $helpBody.css('height', helpHeight);
            });

            // handle click on names in chat / room list
            var prepareMessage = function (ev) {
                if (readOnly) {
                    return false;
                }

                var message = $newMessage.val().trim();

                // If it was a message to another person, replace that
                if (message.indexOf('/msg') === 0) {
                    message = message.replace(/^\/msg \S+/, '');
                }

                // Re-focus because we lost it on the click
                $newMessage.focus();

                // Do not convert this to a message if it is a command
                if (message[0] === '/') {
                    return false;
                }

                // Prepend our target username
                message = '@' + $(this).text().trim() + ' ' + message;
                ui.setMessage(message);
                return false;
            };
            $document.on('click', '.users li.user .name', prepareMessage);
            $document.on('click', '.message .left .name', prepareMessage);

            $submitButton.click(function (ev) {
                triggerSend();

                ev.preventDefault();
                return false;
            });

            $sound.click(function () {
                var room = getCurrentRoomElements();

                if (room.isLobby()) {
                    return;
                }

                $(this).toggleClass('off');

                var enabled = !$(this).hasClass('off');

                // Store the preference
                setRoomPreference(room.getName(), 'hasSound', enabled);
            });

            $richness.click(function () {
                var room = getCurrentRoomElements(),
                    $richContentMessages = room.messages.find('h3.collapsible_title');

                if (room.isLobby()) {
                    return;
                }

                $(this).toggleClass('off');

                var enabled = !$(this).hasClass('off');

                // Store the preference
                setRoomPreference(room.getName(), 'blockRichness', !enabled);

                // toggle all rich-content for current room
                $richContentMessages.each(function (index) {
                    var $this = $(this),
                        isCurrentlyVisible = $this.next().is(":visible");

                    if (enabled) {
                        $this.attr('title', 'Content collapsed because you have Rich-Content disabled');
                    } else {
                        $this.removeAttr('title');
                    }

                    if (isCurrentlyVisible ^ enabled) {
                        $this.trigger('click');
                    }
                });
            });

            $toast.click(function () {
                var $this = $(this),
                    enabled = !$this.hasClass('off'),
                    room = getCurrentRoomElements();

                if (room.isLobby()) {
                    return;
                }

                if (enabled) {
                    // If it's enabled toggle the preference
                    setRoomPreference(room.getName(), 'canToast', !enabled);
                    $this.toggleClass('off');
                }
                else {
                    toast.enableToast()
                    .done(function () {
                        setRoomPreference(room.getName(), 'canToast', true);
                        $this.removeClass('off');
                    })
                    .fail(function () {
                        setRoomPreference(room.getName(), 'canToast', false);
                        $this.addClass('off');
                    });
                }
            });

            $(toast).bind('toast.focus', function (ev, room) {
                window.focus();

                // focus on the room
                $ui.trigger(ui.events.openRoom, [room]);
            });

            $downloadIcon.click(function () {
                var room = getCurrentRoomElements();

                if (room.isLobby()) {
                    return; //Show a message?<commit_msg>open room on exact match + enter on lobby filter textbox.  Slightly refactor ui actions that result in rooms being opened or focussed.<commit_after>﻿/// <reference path="Scripts/jquery-1.7.js" />
/// <reference path="Scripts/jQuery.tmpl.js" />
/// <reference path="Scripts/jquery.cookie.js" />
/// <reference path="Chat.toast.js" />
/// <reference path="Scripts/livestamp.min.js" />
/// <reference path="Scripts/moment.min.js" />

/*jshint bitwise:false */
(function ($, window, document, chat, utility, emoji, linkify) {
    "use strict";

    var $chatArea = null,
        $tabs = null,
        $submitButton = null,
        $newMessage = null,
        $roomActions = null,
        $toast = null,
        $disconnectDialog = null,
        $downloadIcon = null,
        $downloadDialog = null,
        $downloadDialogButton = null,
        $downloadRange = null,
        $logout = null,
        $help = null,
        $ui = null,
        $sound = null,
        templates = null,
        focus = true,
        readOnly = false,
        Keys = { Up: 38, Down: 40, Esc: 27, Enter: 13, Slash: 47, Space: 32, Tab: 9, Question: 191 },
        scrollTopThreshold = 75,
        toast = window.chat.toast,
        preferences = null,
        $login = null,
        lastCycledMessage = null,
        $helpPopup = null,
        $helpBody = null,
        helpHeight = 0,
        $shortCutHelp = null,
        $globalCmdHelp = null,
        $roomCmdHelp = null,
        $userCmdHelp = null,
        $updatePopup = null,
        $window = $(window),
        $document = $(document),
        $lobbyRoomFilterForm = null,
        lobbyLoaded = false,
        $roomFilterInput = null,
        $closedRoomFilter = null,
        updateTimeout = 15000,
        $richness = null,
        lastPrivate = null,
        roomCache = {},
        $reloadMessageNotification = null,
        popoverTimer = null,
        $connectionStatus = null,
        connectionState = -1,
        $connectionStateChangedPopover = null,
        connectionStateIcon = null,
        $connectionInfoPopover = null,
        $connectionInfoContent = null,
        $fileUploadButton = null,
        $hiddenFile = null,
        $uploadForm = null,
        $clipboardUpload = null,
        $clipboardUploadPreview = null,
        $clipboardUploadButton = null,
        $fileRoom = null,
        $fileConnectionId = null,
        connectionInfoStatus = null,
        connectionInfoTransport = null,
        $topicBar = null,
        $loadingHistoryIndicator = null,
        trimRoomHistoryFrequency = 1000 * 60 * 2, // 2 minutes in ms
        $loadMoreRooms = null,
        sortedRoomList = null,
        maxRoomsToLoad = 100,
        lastLoadedRoomIndex = 0,
        $lobbyPrivateRooms = null,
        $lobbyOtherRooms = null,
        $roomLoadingIndicator = null,
        roomLoadingDelay = 250,
        roomLoadingTimeout = null,
        Room = chat.Room,
        $unreadNotificationCount = null;

    function getRoomNameFromHash(hash) {
        if (hash.length && hash[0] === '/') {
            hash = hash.substr(1);
        }

        var parts = hash.split('/');
        if (parts[0] === 'rooms') {
            return parts[1];
        }

        return null;
    }

    function getRoomId(roomName) {
        return window.escape(roomName.toString().toLowerCase()).replace(/[^A-Za-z0-9]/g, '_');
    }

    function getUserClassName(userName) {
        return '[data-name="' + userName + '"]';
    }

    function getRoomPreferenceKey(roomName) {
        return '_room_' + roomName;
    }

    function setRoomLoading(isLoading, roomName) {
        if (isLoading) {
            var room = getRoomElements(roomName);
            if (!room.isInitialized()) {
                roomLoadingTimeout = window.setTimeout(function () {
                    $roomLoadingIndicator.find('i').addClass('icon-spin');
                    $roomLoadingIndicator.show();
                }, roomLoadingDelay);
            }
        } else {
            if (roomLoadingTimeout) {
                clearTimeout(roomLoadingDelay);
            }
            $roomLoadingIndicator.hide();
            $roomLoadingIndicator.find('i').removeClass('icon-spin');
        }
    }

    function populateLobbyRoomList(item, template, listToPopulate) {
        $.tmpl(template, item).appendTo(listToPopulate);
    }

    function sortRoomList(listToSort) {
        var sortedList = listToSort.sort(function (a, b) {
            if (a.Closed && !b.Closed) {
                return 1;
            } else if (b.Closed && !a.Closed) {
                return -1;
            }

            if (a.Count > b.Count) {
                return -1;
            } else if (b.Count > a.Count) {
                return 1;
            }

            return a.Name.toString().toUpperCase().localeCompare(b.Name.toString().toUpperCase());
        });
        return sortedList;
    }

    function getRoomElements(roomName) {
        var roomId = getRoomId(roomName);
        var room = new Room($('#tabs-' + roomId),
                        $('#userlist-' + roomId),
                        $('#userlist-' + roomId + '-owners'),
                        $('#userlist-' + roomId + '-active'),
                        $('#messages-' + roomId),
                        $('#roomTopic-' + roomId));
        return room;
    }

    function getCurrentRoomElements() {
        var $tab = $tabs.find('li.current');
        var room;
        if ($tab.data('name') === 'Lobby') {
            room = new Room($tab,
                $('#userlist-lobby'),
                $('#userlist-lobby-owners'),
                $('#userlist-lobby-active'),
                $('.messages.current'),
                $('.roomTopic.current'));
        } else {
            room = new Room($tab,
                $('.users.current'),
                $('.userlist.current .owners'),
                $('.userlist.current .active'),
                $('.messages.current'),
                $('.roomTopic.current'));
        }
        return room;
    }

    function getAllRoomElements() {
        var rooms = [];
        $("ul#tabs > li.room").each(function () {
            rooms[rooms.length] = getRoomElements($(this).data("name"));
        });
        return rooms;
    }

    function getLobby() {
        return getRoomElements('Lobby');
    }

    function getRoomsNames() {
        var lobby = getLobby();

        return lobby.users.find('li')
                     .map(function () {
                         var room = $(this).data('name');
                         roomCache[room.toString().toUpperCase()] = true;
                         return room + ' ';
                     });
    }

    function updateLobbyRoomCount(room, count) {
        var lobby = getLobby(),
            $targetList = room.Private === true ? lobby.owners : lobby.users,
            $room = $targetList.find('[data-room="' + room.Name + '"]'),
            $count = $room.find('.count'),
            roomName = room.Name.toString().toUpperCase();

        $room.css('background-color', '#f5f5f5');
        if (count === 0) {
            $count.text('Unoccupied');
        } else if (count === 1) {
            $count.text('1 occupant');
        } else {
            $count.text(count + ' occupants');
        }

        if (room.Private === true) {
            $room.addClass('locked');
        } else {
            $room.removeClass('locked');
        }

        if (room.Closed === true) {
            $room.addClass('closed');
        } else {
            $room.removeClass('closed');
        }

        var nextListElement = getNextRoomListElement($targetList, roomName, count, room.Closed);

        $room.data('count', count);
        if (nextListElement !== null) {
            $room.insertBefore(nextListElement);
        } else {
            $room.appendTo($targetList);
        }

        // Do a little animation
        $room.animate({ backgroundColor: '#ffffff' }, 800);
    }

    function addRoomToLobby(roomViewModel) {
        var lobby = getLobby(),
            $room = templates.lobbyroom.tmpl(roomViewModel),
            roomName = roomViewModel.Name.toString().toUpperCase(),
            count = roomViewModel.Count,
            closed = roomViewModel.Closed,
            $targetList = roomViewModel.Private ? lobby.owners : lobby.users;

        var nextListElement = getNextRoomListElement($targetList, roomName, count, closed);

        if (nextListElement !== null) {
            $room.insertBefore(nextListElement);
        } else {
            $room.appendTo($targetList);
        }

        filterIndividualRoom($room);
    }
    
    function getNextRoomListElement($targetList, roomName, count, closed) {
        var nextListElement = null;

        // move the item to before the next element
        $targetList.find('li').each(function () {
            var $this = $(this),
                liRoomCount = $this.data('count'),
                liRoomClosed = $this.hasClass('closed'),
                name = $this.data('name'),
                nameComparison;

            if (name === undefined) {
                return true;
            }

            nameComparison = name.toString().toUpperCase().localeCompare(roomName);

            // skip this element
            if (nameComparison === 0) {
                return true;
            }

            // skip closed rooms which always go after unclosed ones
            if (!liRoomClosed && closed) {
                return true;
            }

            // skip where we have more occupants
            if (liRoomCount > count) {
                return true;
            }

            // skip where we have the same number of occupants but the room is alphabetically earlier
            if (liRoomCount === count && nameComparison < 0) {
                return true;
            }

            nextListElement = $this;
            return false;
        });

        return nextListElement;
    }
    
    function filterIndividualRoom($room) {
        var filter = $roomFilterInput.val().toUpperCase(),
            showClosedRooms = $closedRoomFilter.is(':checked');
        
        if ($room.data('room').toString().toUpperCase().score(filter) > 0.0 && (showClosedRooms || !$room.is('.closed'))) {
            $room.show();
        } else {
            $room.hide();
        }
    }

    function addRoom(roomViewModel) {
        // Do nothing if the room exists
        var roomName = roomViewModel.Name,
            room = getRoomElements(roomViewModel.Name),
            roomId = null,
            viewModel = null,
            $messages = null,
            $roomTopic = null,
            scrollHandler = null,
            userContainer = null;

        if (room.exists()) {
            return false;
        }

        roomId = getRoomId(roomName);

        // Add the tab
        viewModel = {
            id: roomId,
            name: roomName,
            closed: roomViewModel.Closed
        };

        if (!roomCache[roomName.toString().toUpperCase()]) {
            addRoomToLobby(roomViewModel);
        }

        roomCache[roomName.toString().toUpperCase()] = true;

        templates.tab.tmpl(viewModel).data('name', roomName).appendTo($tabs);

        $messages = $('<ul/>').attr('id', 'messages-' + roomId)
                              .addClass('messages')
                              .appendTo($chatArea)
                              .hide();

        $roomTopic = $('<div/>').attr('id', 'roomTopic-' + roomId)
                              .addClass('roomTopic')
                              .appendTo($topicBar)
                              .hide();

        userContainer = $('<div/>').attr('id', 'userlist-' + roomId)
            .addClass('users')
            .appendTo($chatArea).hide();
        templates.userlist.tmpl({ listname: '- Room Owners', id: 'userlist-' + roomId + '-owners' })
            .addClass('owners')
            .appendTo(userContainer);
        templates.userlist.tmpl({ listname: '- Users', id: 'userlist-' + roomId + '-active' })
            .appendTo(userContainer);
        userContainer.find('h3').click(function () {
            if ($.trim($(this).text())[0] === '-') {
                $(this).text($(this).text().replace('-', '+'));
            } else {
                $(this).text($(this).text().replace('+', '-'));
            }
            $(this).next().toggle(0);
            return false;
        });
        
        $tabs.find('li')
            .not('.lobby')
            .sortElements(function (a, b) {
                return $(a).data('name').toString().toUpperCase() > $(b).data('name').toString().toUpperCase() ? 1 : -1;
            });

        scrollHandler = function (ev) {
            var messageId = null;

            // Do nothing if there's nothing else
            if ($(this).data('full') === true) {
                return;
            }

            // If you're we're near the top, raise the event, but if the scroll
            // bar is small enough that we're at the bottom edge, ignore it.
            // We have to use the ui version because the room object above is
            // not fully initialized, so there are no messages.
            if ($(this).scrollTop() <= scrollTopThreshold && !ui.isNearTheEnd(roomId)) {
                var $child = $messages.children('.message:first');
                if ($child.length > 0) {
                    messageId = $child.attr('id')
                                      .substr(2); // Remove the "m-"
                    $ui.trigger(ui.events.scrollRoomTop, [{ name: roomName, messageId: messageId }]);
                }
            }
        };

        // Hookup the scroll handler since event delegation doesn't work with scroll events
        $messages.bind('scroll', scrollHandler);

        // Store the scroll handler so we can remove it later
        $messages.data('scrollHandler', scrollHandler);

        setAccessKeys();

        lobbyLoaded = false;
        return true;
    }

    function removeRoom(roomName) {
        var room = getRoomElements(roomName),
            scrollHandler = null;

        if (room.exists()) {
            // Remove the scroll handler from this room
            scrollHandler = room.messages.data('scrollHandler');
            room.messages.unbind('scrollHandler', scrollHandler);

            room.tab.remove();
            room.messages.remove();
            room.users.remove();
            room.roomTopic.remove();
            setAccessKeys();
        }
    }

    function setAccessKeys() {
        $.each($tabs.find('li.room'), function (index, item) {
            $(item).children('button').attr('accesskey', getRoomAccessKey(index));
        });
    }

    function getRoomAccessKey(index) {
        if (index < 10) {
            return index + 1;
        }
        return 0;
    }

    function navigateToRoom(roomName) {
        var hash = (document.location.hash || '#').substr(1),
            hashRoomName = getRoomNameFromHash(hash);

        if (hashRoomName && hashRoomName === roomName) {
            ui.setActiveRoomCore(roomName);
        }
        else {
            document.location.hash = '#/rooms/' + roomName;
        }
    }

    function processMessage(message, roomName) {
        var isFromCollapibleContentProvider = isFromCollapsibleContentProvider(message.message),
            collapseContent = shouldCollapseContent(message.message, roomName);

        message.when = message.date.formatTime(true);
        message.fulldate = message.date.toLocaleString();

        if (collapseContent) {
            message.message = collapseRichContent(message.message);
        }
    }

    function isFromCollapsibleContentProvider(content) {
        return content.indexOf('class="collapsible_box') > -1; // leaving off trailing " purposefully
    }

    function shouldCollapseContent(content, roomName) {
        var collapsible = isFromCollapsibleContentProvider(content),
            collapseForRoom = roomName ? getRoomPreference(roomName, 'blockRichness') : getActiveRoomPreference('blockRichness');

        return collapsible && collapseForRoom;
    }

    function collapseRichContent(content) {
        content = content.replace(/class="collapsible_box/g, 'style="display: none;" class="collapsible_box');
        return content.replace(/class="collapsible_title"/g, 'class="collapsible_title" title="Content collapsed because you have Rich-Content disabled"');
    }

    function triggerFocus() {
        if (!utility.isMobile && !readOnly) {
            $newMessage.focus();
        }

        if (focus === false) {
            focus = true;
            $ui.trigger(ui.events.focusit);
        }
    }

    function loadPreferences() {
        // Restore the global preferences
    }

    function toggleRichness($element, roomName) {
        var blockRichness = roomName ? getRoomPreference(roomName, 'blockRichness') : preferences.blockRichness;

        if (blockRichness === true) {
            $element.addClass('off');
        }
        else {
            $element.removeClass('off');
        }
    }

    function toggleElement($element, preferenceName, roomName) {
        var value = roomName ? getRoomPreference(roomName, preferenceName) : preferences[preferenceName];

        if (value === true) {
            $element.removeClass('off');
        }
        else {
            $element.addClass('off');
        }
    }

    function loadRoomPreferences(roomName) {
        var roomPreferences = getRoomPreference(roomName);

        // Placeholder for room level preferences
        toggleElement($sound, 'hasSound', roomName);
        toggleElement($toast, 'canToast', roomName);
        toggleRichness($richness, roomName);
    }

    function setPreference(name, value) {
        preferences[name] = value;

        $(ui).trigger(ui.events.preferencesChanged);
    }

    function setRoomPreference(roomName, name, value) {
        var roomPreferences = preferences[getRoomPreferenceKey(roomName)];

        if (!roomPreferences) {
            roomPreferences = {};
            preferences[getRoomPreferenceKey(roomName)] = roomPreferences;
        }

        roomPreferences[name] = value;

        $ui.trigger(ui.events.preferencesChanged);
    }

    function getRoomPreference(roomName, name) {
        return (preferences[getRoomPreferenceKey(roomName)] || {})[name];
    }

    function getActiveRoomPreference(name) {
        var room = getCurrentRoomElements();
        return getRoomPreference(room.getName(), name);
    }

    function anyRoomPreference(name, value) {
        for (var key in preferences) {
            if (preferences[key][name] === value) {
                return true;
            }
        }
        return false;
    }

    function triggerSend() {
        if (readOnly) {
            return;
        }

        var msg = $.trim($newMessage.val());

        focus = true;

        if (msg) {
            if (msg.toUpperCase() === '/LOGIN') {
                ui.showLogin();
            }
            else {
                $ui.trigger(ui.events.sendMessage, [msg]);
            }
        }

        $newMessage.val('');
        $newMessage.focus();

        // always scroll to bottom after new message sent
        var room = getCurrentRoomElements();
        room.scrollToBottom();
        room.removeSeparator();
    }

    function updateNote(userViewModel, $user) {
        var $title = $user.find('.name'),
            noteText = userViewModel.note,
            noteTextEncoded = null,
            requireRoomUpdate = false;

        if (userViewModel.noteClass === 'afk') {
            noteText = userViewModel.note + ' (' + userViewModel.timeAgo + ')';
            requireRoomUpdate = ui.setUserInActive($user);
        }
        else if (userViewModel.active) {
            requireRoomUpdate = ui.setUserActive($user);
        }
        else {
            requireRoomUpdate = ui.setUserInActive($user);
        }

        noteTextEncoded = $('<div/>').html(noteText).text();

        // Remove all classes and the text
        $title.removeAttr('title');

        if (userViewModel.note) {
            $title.attr('title', noteTextEncoded);
        }

        if (requireRoomUpdate) {
            $user.each(function () {
                var room = getRoomElements($(this).data('inroom'));
                room.updateUserStatus($(this));
                room.sortLists($(this));
            });
        }
    }

    function updateFlag(userViewModel, $user) {
        var $flag = $user.find('.flag');

        $flag.removeAttr('class');
        $flag.addClass('flag');
        $flag.removeAttr('title');

        if (userViewModel.flagClass) {
            $flag.addClass(userViewModel.flagClass);
            $flag.show();
        } else {
            $flag.hide();
        }

        if (userViewModel.country) {
            $flag.attr('title', userViewModel.country);
        }
    }

    function updateRoomTopic(roomViewModel) {
        var room = getRoomElements(roomViewModel.Name);
        var topic = roomViewModel.Topic;
        var topicHtml = topic === '' ? 'You\'re chatting in ' + roomViewModel.Name : ui.processContent(topic);
        var roomTopic = room.roomTopic;
        var isVisibleRoom = getCurrentRoomElements().getName() === roomViewModel.Name;

        if (isVisibleRoom) {
            roomTopic.hide();
        }

        roomTopic.html(topicHtml);

        if (isVisibleRoom) {
            roomTopic.fadeIn(2000);
        }
    }

    function getConnectionStateChangedPopoverOptions(statusText) {
        var options = {
            html: true,
            trigger: 'hover',
            template: $connectionStateChangedPopover,
            content: function () {
                return statusText;
            }
        };
        return options;
    }

    function getConnectionInfoPopoverOptions(transport) {
        var options = {
            html: true,
            trigger: 'hover',
            delay: {
                show: 0,
                hide: 500
            },
            template: $connectionInfoPopover,
            content: function () {
                var connectionInfo = $connectionInfoContent;
                connectionInfo.find(connectionInfoStatus).text('Status: Connected');
                connectionInfo.find(connectionInfoTransport).text('Transport: ' + transport);
                return connectionInfo.html();
            }
        };
        return options;
    }

    function loadMoreLobbyRooms() {
        var lobby = getLobby(),
            moreRooms = sortedRoomList.slice(lastLoadedRoomIndex, lastLoadedRoomIndex + maxRoomsToLoad);

        populateLobbyRoomList(moreRooms, templates.lobbyroom, lobby.users);
        lastLoadedRoomIndex = lastLoadedRoomIndex + maxRoomsToLoad;
        
        // re-filter lists
        $lobbyRoomFilterForm.submit();
    }

    var ui = {

        //lets store any events to be triggered as constants here to aid intellisense and avoid
        //string duplication everywhere
        events: {
            closeRoom: 'jabbr.ui.closeRoom',
            prevMessage: 'jabbr.ui.prevMessage',
            openRoom: 'jabbr.ui.openRoom',
            nextMessage: 'jabbr.ui.nextMessage',
            activeRoomChanged: 'jabbr.ui.activeRoomChanged',
            scrollRoomTop: 'jabbr.ui.scrollRoomTop',
            typing: 'jabbr.ui.typing',
            sendMessage: 'jabbr.ui.sendMessage',
            focusit: 'jabbr.ui.focusit',
            blurit: 'jabbr.ui.blurit',
            preferencesChanged: 'jabbr.ui.preferencesChanged',
            loggedOut: 'jabbr.ui.loggedOut',
            reloadMessages: 'jabbr.ui.reloadMessages',
            fileUploaded: 'jabbr.ui.fileUploaded'
        },

        help: {
            shortcut: 'shortcut',
            global: 'global',
            room: 'room',
            user: 'user'
        },

        initialize: function (state) {
            $ui = $(this);
            preferences = state || {};
            $chatArea = $('#chat-area');
            $tabs = $('#tabs');
            $submitButton = $('#send');
            $newMessage = $('#new-message');
            $roomActions = $('#room-actions');
            $toast = $('#room-preferences .toast');
            $sound = $('#room-preferences .sound');
            $richness = $('#room-preferences .richness');
            $downloadIcon = $('#room-preferences .download');
            $downloadDialog = $('#download-dialog');
            $downloadDialogButton = $('#download-dialog-button');
            $downloadRange = $('#download-range');
            $logout = $('#preferences .logout');
            $help = $('#preferences .help');
            $disconnectDialog = $('#disconnect-dialog');
            $login = $('#jabbr-login');
            $helpPopup = $('#jabbr-help');
            $clipboardUpload = $('#jabbr-clipboard-upload');
            $clipboardUploadPreview = $('#jabbr-clipboard-upload #clipboard-upload-preview');
            $clipboardUploadButton = $('#jabbr-clipboard-upload #clipboard-upload');
            $helpBody = $('#jabbr-help .help-body');
            $shortCutHelp = $('#jabbr-help #shortcut');
            $globalCmdHelp = $('#jabbr-help #global');
            $roomCmdHelp = $('#jabbr-help #room');
            $userCmdHelp = $('#jabbr-help #user');
            $updatePopup = $('#jabbr-update');
            focus = true;
            $lobbyRoomFilterForm = $('#room-filter-form');
            $roomFilterInput = $('#room-filter');
            $closedRoomFilter = $('#room-filter-closed');
            templates = {
                userlist: $('#new-userlist-template'),
                user: $('#new-user-template'),
                message: $('#new-message-template'),
                notification: $('#new-notification-template'),
                separator: $('#message-separator-template'),
                tab: $('#new-tab-template'),
                gravatarprofile: $('#gravatar-profile-template'),
                commandhelp: $('#command-help-template'),
                multiline: $('#multiline-content-template'),
                lobbyroom: $('#new-lobby-room-template'),
                otherlobbyroom: $('#new-other-lobby-room-template')
            };
            $reloadMessageNotification = $('#reloadMessageNotification');
            $fileUploadButton = $('.upload-button');
            $hiddenFile = $('#hidden-file');
            $uploadForm = $('#upload');
            $fileRoom = $('#file-room');
            $fileConnectionId = $('#file-connection-id');
            $connectionStatus = $('#connectionStatus');

            $connectionStateChangedPopover = $('#connection-state-changed-popover');
            connectionStateIcon = '#popover-content-icon';
            $connectionInfoPopover = $('#connection-info-popover');
            $connectionInfoContent = $('#connection-info-content');
            connectionInfoStatus = '#connection-status';
            connectionInfoTransport = '#connection-transport';
            $topicBar = $('#topic-bar');
            $loadingHistoryIndicator = $('#loadingRoomHistory');

            $loadMoreRooms = $('#load-more-rooms-item');
            $lobbyPrivateRooms = $('#lobby-private');
            $lobbyOtherRooms = $('#lobby-other');
            $roomLoadingIndicator = $('#room-loading');

            $unreadNotificationCount = $('#notification-unread-count');
            
            if (toast.canToast()) {
                $toast.show();
            }
            else {
                $richness.css({ left: '55px' });
                $downloadIcon.css({ left: '90px' });
                // We need to set the toast setting to false
                preferences.canToast = false;
                $toast.hide();
            }

            // DOM events
            $document.on('click', 'h3.collapsible_title', function () {
                var nearEnd = ui.isNearTheEnd();

                $(this).next().toggle(0, function () {
                    if (nearEnd) {
                        ui.scrollToBottom();
                    }
                });
            });

            var activateOrOpenRoom = function(roomName) {
                var room = getRoomElements(roomName);

                if (room.exists()) {
                    ui.setActiveRoom(roomName);
                }
                else {
                    $ui.trigger(ui.events.openRoom, [roomName]);
                }
            };
            
            $document.on('click', 'li.room .room-row', function () {
                var roomName = $(this).parent().data('name');
                activateOrOpenRoom(roomName);
            });
            
            $roomFilterInput.keypress(function (ev) {
                var key = ev.keyCode || ev.which,
                    roomName = $(this).val();
                
                switch (key) {
                    case Keys.Enter:
                        // only if it's an exact match
                        if (roomCache[roomName.toUpperCase()]) {
                            activateOrOpenRoom(roomName);
                            return;
                        }
                }
            });
            
            $document.on('click', '#tabs li', function () {
                var roomName = $(this).data('name');
                activateOrOpenRoom(roomName);
            });

            $document.on('mousedown', '#tabs li.room', function (ev) {
                // if middle mouse
                if (ev.which === 2) {
                    $ui.trigger(ui.events.closeRoom, [$(this).data('name')]);
                }
            });

            $document.on('click', '#load-more-rooms-item', function () {
                var spinner = $loadMoreRooms.find('i'),
                    lobby = getLobby();
                spinner.addClass('icon-spin');
                spinner.show();
                var loader = $loadMoreRooms.find('.load-more-rooms a');
                loader.html(' Loading more rooms...');
                loadMoreLobbyRooms();
                spinner.hide();
                spinner.removeClass('icon-spin');
                loader.html('Load More...');
                if (lastLoadedRoomIndex < sortedRoomList.length) {
                    $loadMoreRooms.show();
                } else {
                    $loadMoreRooms.hide();
                }
            });

            $document.on('click', '#tabs li .close', function (ev) {
                var roomName = $(this).closest('li').data('name');

                $ui.trigger(ui.events.closeRoom, [roomName]);

                ev.preventDefault();
                return false;
            });

            // handle click on notifications
            $document.on('click', '.notification a.info', function (ev) {
                var $notification = $(this).closest('.notification');

                if ($(this).hasClass('collapse')) {
                    ui.collapseNotifications($notification);
                }
                else {
                    ui.expandNotifications($notification);
                }
            });

            $document.on('click', '#reloadMessageNotification a', function () {
                $ui.trigger(ui.events.reloadMessages);
            });

            // handle tab cycling - we skip the lobby when cycling
            // handle shift+/ - display help command
            $document.on('keydown', function (ev) {
                // ctrl + tab event is sent to the page in firefox when the user probably means to change browser tabs
                if (ev.keyCode === Keys.Tab && !ev.ctrlKey && $newMessage.val() === "") {
                    var current = getCurrentRoomElements(),
                        index = current.tab.index(),
                        tabCount = $tabs.children().length - 1;

                    if (!ev.shiftKey) {
                        // Next tab
                        index = index % tabCount + 1;
                    } else {
                        // Prev tab
                        index = (index - 1) || tabCount;
                    }

                    ui.setActiveRoom($tabs.children().eq(index).data('name'));
                    if (!readOnly) {
                        $newMessage.focus();
                    }
                }

                if (!$newMessage.is(':focus') && ev.shiftKey && ev.keyCode === Keys.Question) {
                    ui.showHelp();
                    // Prevent the ? be recorded in the message box
                    ev.preventDefault();
                }
            });

            // hack to get Chrome to scroll back to top of help body
            // when redisplaying it after scrolling down and closing it
            $helpPopup.on('hide', function () {
                $helpBody.scrollTop(0);
            });

            // set the height of the help body when displaying the help dialog
            // so that the scroll bar does not block the rounded corners
            $helpPopup.on('show', function () {
                if (helpHeight === 0) {
                    helpHeight = $helpPopup.height() - $helpBody.position().top - 10;
                }
                $helpBody.css('height', helpHeight);
            });

            // handle click on names in chat / room list
            var prepareMessage = function (ev) {
                if (readOnly) {
                    return false;
                }

                var message = $newMessage.val().trim();

                // If it was a message to another person, replace that
                if (message.indexOf('/msg') === 0) {
                    message = message.replace(/^\/msg \S+/, '');
                }

                // Re-focus because we lost it on the click
                $newMessage.focus();

                // Do not convert this to a message if it is a command
                if (message[0] === '/') {
                    return false;
                }

                // Prepend our target username
                message = '@' + $(this).text().trim() + ' ' + message;
                ui.setMessage(message);
                return false;
            };
            $document.on('click', '.users li.user .name', prepareMessage);
            $document.on('click', '.message .left .name', prepareMessage);

            $submitButton.click(function (ev) {
                triggerSend();

                ev.preventDefault();
                return false;
            });

            $sound.click(function () {
                var room = getCurrentRoomElements();

                if (room.isLobby()) {
                    return;
                }

                $(this).toggleClass('off');

                var enabled = !$(this).hasClass('off');

                // Store the preference
                setRoomPreference(room.getName(), 'hasSound', enabled);
            });

            $richness.click(function () {
                var room = getCurrentRoomElements(),
                    $richContentMessages = room.messages.find('h3.collapsible_title');

                if (room.isLobby()) {
                    return;
                }

                $(this).toggleClass('off');

                var enabled = !$(this).hasClass('off');

                // Store the preference
                setRoomPreference(room.getName(), 'blockRichness', !enabled);

                // toggle all rich-content for current room
                $richContentMessages.each(function (index) {
                    var $this = $(this),
                        isCurrentlyVisible = $this.next().is(":visible");

                    if (enabled) {
                        $this.attr('title', 'Content collapsed because you have Rich-Content disabled');
                    } else {
                        $this.removeAttr('title');
                    }

                    if (isCurrentlyVisible ^ enabled) {
                        $this.trigger('click');
                    }
                });
            });

            $toast.click(function () {
                var $this = $(this),
                    enabled = !$this.hasClass('off'),
                    room = getCurrentRoomElements();

                if (room.isLobby()) {
                    return;
                }

                if (enabled) {
                    // If it's enabled toggle the preference
                    setRoomPreference(room.getName(), 'canToast', !enabled);
                    $this.toggleClass('off');
                }
                else {
                    toast.enableToast()
                    .done(function () {
                        setRoomPreference(room.getName(), 'canToast', true);
                        $this.removeClass('off');
                    })
                    .fail(function () {
                        setRoomPreference(room.getName(), 'canToast', false);
                        $this.addClass('off');
                    });
                }
            });

            $(toast).bind('toast.focus', function (ev, room) {
                window.focus();

                // focus on the room
                activateOrOpenRoom(room);
            });

            $downloadIcon.click(function () {
                var room = getCurrentRoomElements();

                if (room.isLobby()) {
                    return; //Show a message?
{'fraction_non_alphanumeric': 0.13098349334452736, 'fraction_numerical': 0.002789475822286016, 'mean_word_length': 1.5856032490721939, 'pattern_counts': {'":': 2, '<': 31, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2682544', 'n_tokens_mistral': 19549, 'n_tokens_neox': 18440, 'n_words': 3857}