/*!
 * Dynamic comments
 * Copyright (c) 2010 Spotify
 */
/* Maintained by: Andreas Blixt <blixt@spotify.com>
 * Depends on: jQuery 1.3.2
 */
var SpotifyComments = (function ($) {
    var cls = function (api, postId, container, user) {
        var

        // The box containing paging and comments.
        comments = $(container),
        commentsList,

        // A list of time units for showing strings like "3 hours ago".
        units = [
            { single: 'decade',
              plural: 'decades',
              millisecs: 315705600000 },

            { single: 'year',
              plural: 'years',
              millisecs: 31570560000 },

            { single: 'month',
              plural: 'months',
              millisecs: 2630880000 },

            { single: 'week',
              plural: 'weeks',
              millisecs: 604800000 },

            { single: 'day',
              plural: 'days',
              millisecs: 86400000 },

            { single: 'hour',
              plural: 'hours',
              millisecs: 3600000 },

            { single: 'minute',
              plural: 'minutes',
              millisecs: 60000 },

            { single: 'second',
              plural: 'seconds',
              millisecs: 1000 },

            { single: 'millisecond',
              plural: 'milliseconds',
              millisecs: 1 }
        ],

        // Converts a range of milliseconds to a string such as "1 day, 6 hours".
        // maxPieces is optional. Specifying a value of 1 would only ever show one unit,
        // so the above would instead be "1 day".
        rangeToString = function (range, maxPieces) {
            var result = '', x = 0, i = 0;

            for (; i < units.length; i++) {
                var m = units[i].millisecs;
                if (range >= m) {
                    if (maxPieces && x++ >= maxPieces) break;
                    var a = Math.floor(range / m);
                    result += (result ? ', ' : '') + a + ' ' + (a == 1 ? units[i].single : units[i].plural);
                    range %= m;
                } else if (x > 0) {
                    x++;
                }
            }

            return result || 'a moment';
        }, 

        // Builds the DOM structure for the comments, including paging.
        buildComments = function (data) {
            var list = data.comments, paging, comment, i;

            // Empty the entire comments list.
            comments.empty();

            // Build a paging element that has a link for each comment page.
            // If there is no pages, or only one page, don't show paging.
            if (data.pages > 1) {
                paging = $('<ul/>').addClass('paging').append($('<li/>').append($('<span/>').text('Page: ')));
                for (i = 1; i <= data.pages; i++) {
                    var page = $('<li/>').appendTo(paging);
                    if (i == data.page) page = $('<strong/>').appendTo(page);
                    page.append($('<a/>')
                        .attr('href', '#')
                        .text(i));
                }
                comments.append(paging);
            } else {
                paging = null;
            }

            // Add a new element that will hold the comments.
            commentsList = $('<div/>').appendTo(comments);

            // Add all the comments.
            for (i = 0; i < list.length; i++) {
                comment = list[i];
                
                var title, type;
                if (comment.level == 1) {
                    title = 'Spotify Premium user';
                    type = 'premium';
                } else if (comment.level == 2) {
                    title = 'Staff';
                    type = 'employee';
                } else {
                    title = 'Spotify user';
                    type = 'free';
                }

                var middle, id = 'comment-' + comment.id;
                commentsList.append($('<div/>')
                    .addClass('comment')
                    .toggleClass('even', i % 2 == 0)
                    .append($('<div/>')
                        .addClass('meta')
                        .append($('<a/>')
                            .attr({href: '#' + id, id: id, name: id})
                            .append($('<span/>')
                                .addClass('timestamp')
                                // Make the tooltip of the "time ago" text to show the formatted date/time.
                                .attr('title', new Date(comment.timestamp * 1000))
                                // Show the "time ago" text.
                                .text(rangeToString((data.now - comment.timestamp) * 1000, 1) + ' ago'))
                            .append('&nbsp;')
                            .append($('<span/>')
                                .addClass('author')
                                // Show the name of the author.
                                .text(comment.author))
                            .append('&nbsp;said:')))
                    .append($('<div/>')
                        .addClass('comment-bubble-top comment-bubble-top_' + type)
                        .attr('title', title)
                        .html('&nbsp;'))
                    .append(middle = $('<div/>')
                        .addClass('comment-bubble-middle comment-bubble-middle_' + type)
                        .attr('title', title)
                        // Add the comment, linkifying URIs and converting linebreaks to </p><p> and <br/> where appropriate.
                        .append('<p>' + comment.content
                            .replace(/ ((?:www\.)?[\w-]+(?:\.[a-z]{2,6}){1,2})\b/g, ' <LINK=$1>')
                            .replace(/(?:ftp|http|https|spotify):[/:;?&+\w.=#@%-]+/g, '<a href="$&">$&</a>')
                            .replace(/<LINK=([^>]+)>/g, '<a href="http://$1/">$1</a>')
                            .replace(/(?:(\r\n|\r|\n)\1)+/g, '</p><p>')
                            .replace(/\r\n|\r|\n/g, '<br/>') + '</p>'))
                    .append($('<div/>')
                        .addClass('comment-bubble-bottom comment-bubble-bottom_' + type)
                        .attr('title', title)
                        .html('&nbsp;')));

                // Show approval messages.
                if (comment.approved == '0') {
                    middle.append($('<p/>').addClass('moderation').text('Your comment is awaiting moderation.'));
                } else if (comment.approved == 'spam') {
                    middle.append($('<p/>').addClass('moderation').text('Your comment was marked as spam and will not be published.'));
                }
            }

            comments.removeClass('loading');

            if (paging) {
                // Add a copy of the paging below the comments.
                comments.append(paging.clone().addClass('paging-foot'))
                // Add "paging-head" class to the original (top) paging.
                paging.addClass('paging-head');
            }
         },

        // Gets a list of comments, then sends the result to the buildComments function.
        getList = function (page) {
            comments
                .addClass('loading')
                .children('ul.paging')
                    .children('li')
                    .remove()
                    .end()
                .append('<li class="loading"><span>Loading...</span></li>');

            if (commentsList) commentsList.css('opacity', 0.5);

            var args = {'~': 'getList', postId: postId};
            if (user) args.user = '"' + user.replace(/"/g, '\\"') + '"';

            page = parseInt(page, 10);
            if (page) {
                args.page = page;
            } else {
                // Attempt to get the comment id from the URL. The comment id is used to determine which page to show.
                if (match = location.href.match(/#comment-(\d+)$/)) {
                    args.commentId = match[1];
                }
            }

            // Perform an XMLHttpRequest to the server.
            $.getJSON(api, args, function (data) {
                if (data.status != 'success') return;
                buildComments(data.response);
            });

            // If the list has been loaded, presumably for the first time, scroll to the referenced comment, if any.
            if (!page && args.commentId) {
                setTimeout(function () {
                    $('html, body').animate({scrollTop: $('#comment-' + args.commentId).offset().top - 5}, 600);
                }, 200);
            }
        };

        // Enable clicks for all paging elements on the page.
        $('ul.paging').live('click', function (e) {
            var target = $(e.target);
            // Only handle clicks if an <a> element was clicked.
            if (target.is('a')) {
                // Request the page for the clicked link, unless it's the current page.
                if (!target.parent().is('strong')) {
                    getList(parseInt(target.text(), 10));
                    // Scroll to the top of the comments list.
                    $('html, body').animate({scrollTop: $('#comments').offset().top - 10}, 400);
                }
                // Prevent the browser from going to whatever the <a> element links to.
                e.preventDefault();
            }
        });

        getList();
    };

    return cls;
})(jQuery);
