/* Hash of normal, mergeable chat messages */
var mergeable  = { chat: 1, file: 1, html: 1 };
/* Hash of renderable (visible) messages */
var renderable = { chat: 1, file: 1, join: 1, part: 1, topic: 1, me: 1, html: 1 }; // Removed timeout for now
/* Hash of actionable (special) messages */
var actionable = { join: 1, part: 1, topic: 1, timeout: 1 };

/* Send a new post */
function sendNewPost() {
    var message_body = $F('messageBox');
    if(!user_info && !$F('username')) {
        displayBadMessage('Please enter a username (if posting as guest) or log in!');
        return true;
    }
    else if(message_body.length > post_max_size) {
        displayBadMessage('Maximum message length is 4KB (4096 bytes), please shorten your message and try again.');
        return true;
    }
    else if(!message_body || message_body.match(/^\s*$/)) {
        displayBadMessage("I'm sorry, your message is empty! Please type something: some words, perhaps.");
        return true;
    }
    else {
        clearBadMessage();
        makeRequest('makePost', {
            guest_name: $F('username'),
            message:    message_body
        }, receiveNewPostConfirmation);
        $('messageBox').value = '';
        $('messageBox').focus();
        
        // Reset heartbeat so we get the new message fast
        if(poll_running) {
            poll_heartbeat_reset = true;
        }
        else {
            poll_heartbeat_multi = HB_BASE_POLL;
        }
    }
}

/* HANDLE INCOMING POSTS */

/* Checks post array and renders any new posts in the appropriate places. */
/* suppress_actions -> don't execute topic change, join, part etc. events */
function addPosts(posts, suppress_actions, suppress_post_limit) {
    var post;
    var last = current_posts.keys().sort().last(); // last block starter
    $H(posts).keys().sortBy(function(s){ 
        // Force IDs to integers (otherwise sort order is 1, 10, 11.... 2, 3)
        return parseInt(s);
    }).each(function(post_id) {
        if(!current_posts_inc_merged[post_id]) {
            post = posts[post_id];     
            
            poll_last_id = Math.max(poll_last_id, post_id);
            var last_post_id = getLastPostByAuthor(post.username, post.guest_name); // TODO: only do if needed??
            if(last_post_id && post.username && post.message_type == 'chat' && post.message.match(/^\*.+?->.*$/)) {
                performStarReplacement(last_post_id, post.message); // direct replacment of one phrase with another
            }
            else if(last_post_id && post.username && post.message_type == 'chat' && post.message.match(/^\*\S+[^*]$/)) {
                performStarCorrection(last_post_id, post.message); // automatic correction of one word
            }
            else {
                if(!suppress_actions && actionable[post.message_type]) {
                    actionPost(post);
                }
                if(renderable[post.message_type]) {
                    // Returns true if the post started a new 'block'
                    if(renderPost(post)) {
                        current_posts[post_id] = 1;
                        last = post_id;
                    }
                    current_posts_inc_merged[post_id] = post;
                    
                    if(last) {
                        if(!merge_link[last]) merge_link[last] = new Array();
                        merge_link[last].push(post_id); // link merged post for later deletion
                    }
                    new_message_count++;
                }
            }
        }
    });
    userListRender();  // Reflect new changes to user list
    if(!suppress_post_limit) enforcePostHistory();
}

/* High-performance routine designed for rendering large arrays of posts. Designed to be able to do a single DOM update instead of 2-3 per post. */
function addHistoryPosts(posts, order, keywords) {
    if(!order) order = 'id';
    var order_func = function(s) { return parseInt(s); } // id sort
    if(order == 'relevance')
        order_func = function(s) { return -1 * posts[s].score; };
    else if(order == 'date_asc')
        order_func = function(s) { return posts[s].post_stamp; };
    else if(order == 'date_desc')
        order_func = function(s) { return -1 * posts[s].post_stamp; };
    
    var last_post;
    var post_cache = {
        google: $H({}),
        youtube: $H({}),
        inner: $H({}),
        html: ''
    };
    
    $H(posts).keys().sortBy(order_func).each(function(post_id) {
        post = posts[post_id];
        
        // Render the post to our html cache
        if(renderable[post.message_type]) {
            renderPost(post, post_cache);
        }
    });
    
    // Splice in inner post clumps
    post_cache.html = post_cache.html.replace(/(<div class="comment" id="innerpost)(\d+)"><\/div>/g,
        function(all, head, id) {
            return head + id + '">' + post_cache.inner[id] + '</div>';
        }
    );
    // Update DOM in one go
    $('content').innerHTML = post_cache.html;
    // Now make the relevant youtube queries
    $A(post_cache.youtube.keys()).each(requestYoutubeInfo);
    $A(post_cache.google.keys()).each(requestGoogleVideoInfo);
}

/* Low-level function to render a single post. Does not update ID counters or remove posts exceeding the history limit */
/* Returns true if a new post was created; false if it was merged in */
/* If post_cache is provided, renderPost will write to the cache rather than updating the DOM */
function renderPost(post, post_cache) {
    var html = '';
    var post_content = '';
    var new_post = 1;
    
    if(!renderable[post.message_type]) return;
    
    // Work out if we are merging this post or not (same author, mergeable post, happened < 5 minutes ago)
    if(pm_last_post && postsHaveSameAuthor(pm_last_post, post)
        && mergeable[post.message_type] && mergeable[pm_last_post.message_type]
        && Math.abs(post.post_stamp - pm_last_post.post_stamp) < 300) {
        new_post = 0;
    }
    
    if(new_post) {
        pm_last_post = post;
        
        // Construct a div frame around the post
        html += '<div class="post p_' + post.message_type + '" id="post' + post.message_id + '"';
        if(post.username && mergeable[post.message_type]) {
            html += ' style="border-color: ' + getBorderColour(post.username) + '"';
        } 
        html += '>';
        if(mergeable[post.message_type]) {
            html += renderPostAuthor(post.username, post.guest_name, post.message_id, post.rank);
        }
        html += '<div class="comment" id="innerpost' + post.message_id + '">';
    }
    else {
        // Prepend a horizontal divider
        var border_color = post.username ? getBorderColour(post.username) : '#aaa';
        post_content += '<hr class="postdivider" style="border-top: 1px solid ' + border_color + ';" />';
    }
    
    post_content += '<div id="innerpostcontent' + post.message_id + '">';
    post_content += eval('renderPost_' + post.message_type + '(post)') + '</div>';
    
    if(new_post) {
        // Close comment div, insert stamp, close entire post div
        html += '</div>';
        // Stamp is on the same line as message if it's non-mergeable (join, part, topic etc.)
        if(mergeable[post.message_type]) {
            html += '<div class="stamp">' + formatMessageStamp(post.post_stamp) + '</div>';
        }
        else {
            html += '<span class="rightStamp">' + formatMessageStamp(post.post_stamp) + '</span>';
        }
        html += '</div>';
        if(post_cache) post_cache.html += html;
        else {
            $('content').innerHTML += html;
            if($('author' + post.message_id)) gradientText.set($('author' + post.message_id));
        }
    }

    // Add new post into message div
    if(post_cache) {
        if(new_post) post_cache.inner[pm_last_post.message_id] = post_content;
        else post_cache.inner[pm_last_post.message_id] += post_content;
    } 
    else $('innerpost' + pm_last_post.message_id).innerHTML += post_content;
    
    // Fill in video information blocks, if present
    var result = post_content.match(/youtube_info_([A-Za-z0-9\-_]+)/);
    if(result) {
        if(post_cache) post_cache.youtube[result[1]] = 1;
        else requestYoutubeInfo(result[1]);
    }
    result = post_content.match(/googlevideo_info_(\-?[0-9]+)/);
    if(result) {
        if(post_cache) post_cache.google[result[1]] = 1;
        else requestGoogleVideoInfo(result[1]);
    }
    
    return new_post;
}

function actionPost(post) {
    if(actionable[post.message_type]) {
        eval('actionPost_' + post.message_type + '(post)');
    }
}

/* MESSAGE RENDERERS */

/* Renders a standard chat post body */
function renderPost_chat(post) {
    return formatMessageBody(post.message);
}

/* Renders a join message */
function renderPost_join(post) {
    return '<b>' + renderNonPostAuthor(post.username) + "</b> has entered the room.";
}

/* Renders a part message */
function renderPost_part(post) {
    var message = "has left the room" + (post.message ? ' <i>(' + post.message.escapeHTML() + ')</i>' : '.');
    return '<b>' + renderNonPostAuthor(post.username) + '</b> ' + message;
}

/* Renders a part message */
function renderPost_timeout(post) {
    return '<b>' + renderNonPostAuthor(post.username) + "</b> has left the room (timed out)";
}

/* Renders a topic change message */
function renderPost_topic(post) {
    return '<b>' + renderNonPostAuthor(post.username) + "</b> has changed the topic to: <span style='font-style: italic; color: #940'>" + formatTopic(post.message) + "</span>";
}

/* Renders a /me action message */
function renderPost_me(post) {
    var html;
    if(post.guest_name) {
        html = '<span class="nonPostGuest">' + post.guest_name + ' ' + formatTopic(post.message) + '</span>';
    }
    else {
        html = '<span style="color: ' + getNameColour(post.username) + '"><span class="meAuthor">' + post.username.escapeHTML() + '</span> ' + formatTopic(post.message) + '</span>';
    }
    return html;
}

/* Renders a raw HTML message */
function renderPost_html(post) {
    return post.message; // pretty simple this one
}

/* Renders a file upload message (body contains JSON struct of file info */
function renderPost_file(post) {
    return formatFileUpload(post.message.evalJSON());
}

/* Renders an error message */
function renderPost_error(post) {
    return post.message.escapeHTML();
}

/* MESSAGE ACTIONERS */

/* Adds the new user to the user list */
function actionPost_join(post) {
    userListAdd(post.username);
}

/* Removes the parting user from the user list */
function actionPost_part(post) {
    userListRemove(post.username);
}

/* Removes the user from the user list */
function actionPost_timeout(post) {
    userListRemove(post.username);
}

/* Changes the room topic */
function actionPost_topic(post) {
    updateTopic(post.message);
}

/* Renders the author part of most posts */
function renderPostAuthor(un, gn, id, rank) {
    var html = '';
    if(gn) {
        html += '<span class="guest">' + gn.escapeHTML() + '</span>';
    }
    else {
        html += '<span id="author' + id + '" style="color: ' + getNameColour(un);
        //if(rank) html += '; padding-left: 43px';
        html += '" class="author ' + getGradientClassName(un) + '">';
        if(0 && rank) {
            html += '<img src="rank_icons/' + rank.badge + '.gif" alt="' + rank.name.escapeHTML() + '"';
            html += ' title="' + rank.name.escapeHTML() + '" width="32" height="32" style="position: absolute; left: 4px; top: -5px;" /> ';
        }
        html += un.escapeHTML() + '</span>';
    }
    return html;
}

/* Renders the gradient author name (outside of posts) */
function renderNonPostAuthor(un) {
    if(!un) un = '[null]';
    // For now, just print the name - over coloured backgrounds, lighter names will be too hard to read
    //return '<span class="nonPostAuthor ' + getGradientClassName(un) + '">' + un.escapeHTML() + '</span>';
    return un.escapeHTML();
}

/* Constructs and adds a system error post */
function addSystemErrorPost(msg) {
    // Under the current post handling system, we track posts by message_id, so
    // display this message in the left sidebar instead.
    displayBadMessage(msg);
}

/* Trim away excess posts */
function enforcePostHistory() {
    var to_remove = current_posts.keys().sort();
    while(to_remove.length > MAX_HISTORY) {
        var top_item = to_remove.first();
        if(!top_item) return; // for safety
        delete current_posts[top_item];
        // Also delete children
        if(merge_link[top_item]) {
            merge_link[top_item].each(function(id) {
                delete current_posts_inc_merged[id];
            });
            delete merge_link[top_item];
        }
        $('content').removeChild($('post' + top_item));
        to_remove.shift(); // so we can move onto the next one
    }
    scrollIfNeeded();
    updateTitle();
}

/* Returns true if two post objects share authors */
function postsHaveSameAuthor(p1, p2) {
    return (p1.username == p2.username && p1.guest_name == p2.guest_name);
}

/* Returns the id of the last post a given author produced (of the 'chat' type if none is specified) */
function getLastPostByAuthor(username, guest_name, type) {
    if(!type) type = 'chat';
    // Inspect the last ten posts for a possible correction target (this is a bit of a mouthful)
    return current_posts_inc_merged.keys().sort().reverse().slice(0, 10).findAll(
        function(id) { return (
                current_posts_inc_merged[id].username     == username
            && !current_posts_inc_merged[id].guest_name
            &&  current_posts_inc_merged[id].message_type == type
            && !current_posts_inc_merged[id].message.match(/^\*$/)
        ); }
    ).first();
}

/* Resets the Slipzone, removing all posts and hiding the user list */
function wipeAllPosts() {
    clearInterval(poll_func_ptr);   // Stop post polling
    init_tries = 0;
    poll_last_check = 0;
    poll_last_activity_ping = 0;
    poll_last_id = 0;
    poll_running = false;
    poll_heartbeat_multi = HB_BASE_POLL;
    poll_heartbeat_max = HB_NO_NEW_MAX;
    poll_heartbeat_reset = false;
    poll_error = 0;
    current_posts = $H({});
    current_posts_inc_merged = $H({});
    merge_link = {};
    user_list = $A([]);
    pm_last_post = 0;
    new_message_count = 0;
    $('pnlUserList').hide();
    $('uploadForm').hide();
    $('topicFrame').hide();
    $('searchHeaderFrame').hide();
    $('historyHeaderFrame').hide();    
    $('postFrame').hide();
    $('searchProgress').hide();
    $('pnlBackToChat').hide();
    $('helpBox').hide();
    $('content').innerHTML = '';
}

/* Clear history/search results and re-initialise live chat */
function restoreLiveChat() {
    wipeAllPosts();
    getInitialPosts();
}