// ==UserScript==
// @name                Forum wrangler
// @version             1.2
// @date                2008-11-22
// @author              Ian Malpass ( ian AT etsyhacks DOT com )
// @namespace           etsy.com
// @description         Keeps track of your forum usage and adds features to forum pages
// @include             http://www.etsy.com/*
// ==/UserScript==

// capture logout events
decorateLogout();

// find out which user I am
var username = whoami();

// configure field prefixes - use usernames to separate data for multiple users using the same browser
var statePrefix = username + '_state_';
var posPrefix = username + '_pos_';
var lastReadPagePrefix = username + '_lastReadPage_';
var lastReadOnPrefix = username + '_lastReadOn_';
var postCountPrefix = username + '_postCount_';
var forumPrefix = 'forum_';

// configure the colours we'll use for highlighting the threads
var colours = {
    posted: '#fbeaea',
    postedNew: '#ffd6d6',
    read: '#f2fbe4',
    readNew: '#ddecc4',
    started: '#ffa3a3'
};    

// useful pattern we'll use a lot
var extractThread = /thread_id=(\d+)/;

if ( username != null && username != "" && document.location.href.indexOf( 'forums' > -1 ) ) {
    // if we're in the forums, and we know who we are, do the real forum wrangling
    // if we're not in the forums, we're just capturing logouts

    if ( document.location.href.match( 'forums_main.php' ) ) {
        decorateForumList()
    } else if ( document.location.href.match( 'forums_board.php' ) ) {
        decorateForumList()
    } else if ( document.location.href.match( 'forums_search.php' ) ) {
        decorateForumList()
    } else if ( document.location.href.match( 'forums_thread.php' ) ) {
        checkPosted(); // have I just posted?
        addFormHandler(); // make sure we capture posts
        captureForumPosition(); // and track where we are
    } else if ( document.location.href.match( 'forums_user_threads.php' ) ) {
        // don't use coloured highlights - all these are posted-in
        decorateForumList( { noPostedHighlight: true } );
    }
}

// we set a flag when we post - we need to check to see if the flag is set 
// and if the post was successful.
function checkPosted () {
    var justPosted = GM_getValue( 'justPosted' );
    if ( justPosted == null || justPosted == "" ) return; // no post
    GM_setValue( 'justPosted', "" );
    var redText = getElementsByClassName( 'red_text' ); // seek errors
    var postedOK = true;
    for ( var r = 0; r < redText.length; r++ ) {
        if ( redText[ r ].firstChild && redText[ r ].firstChild.data && redText[ r ].firstChild.data.indexOf( 'Your post contained no text' ) > -1 ) {
            // we found the error text - post failed
            postedOK = false;
        }
    }
    if ( postedOK ) {
        // post succeeded
        var bits = document.location.search.substring( 1 ).split( '&' );
        // find out where we are so we can flag the thread and move to the last page we were on
        var data = {};
        for ( var b = 0; b < bits.length; b++ ) {
            var vals = bits[ b ].split( '=' );
            data[ vals[ 0 ] ] = vals[ 1 ];
        }
        // set status
        GM_setValue( statePrefix + data.thread_id, 'posted' );
        var page = GM_getValue( lastReadPagePrefix + data.thread_id );
        if ( page != null && Number( page ) > 1 ) {
            // redirect to the last page we were on
            document.location.href = document.location.href + '&page=' + page;
        }
    }
}

// set a flag when we post so checkPosted() knows to do its work
function addFormHandler () {
    if ( document.forms[ 1 ] ) {
        document.forms[ 1 ].addEventListener( "submit", function () { GM_setValue( 'justPosted', "true" ) }, true );
    }
}

// where am I?
function captureForumPosition () {
    var bits = document.location.search.substring( 1 ).split( '&' );
    // parse the query string to find the thread ID and page
    var data = {};
    for ( var b = 0; b < bits.length; b++ ) {
        var vals = bits[ b ].split( '=' );
        data[ vals[ 0 ] ] = vals[ 1 ];
    }
    if ( data.thread_id == null ) return;
    data.page = ( data.page == null ) ? 1 : Number( data.page );

    // configure field names for storing data
    var posField = posPrefix + data.thread_id;
    var lastReadPageField = lastReadPagePrefix + data.thread_id;
    var stateField = statePrefix + data.thread_id;
    var lastReadOnField = lastReadOnPrefix + data.thread_id;
    var postCountField = postCountPrefix + data.thread_id;

    var lastPage = GM_getValue( posField );
    lastPage = ( lastPage == null ) ? 0 : Number( lastPage );
    GM_setValue( lastReadPageField, data.page ); // where was I last
    if ( lastPage == null || lastPage < data.page ) {
        GM_setValue( posField , data.page ); // what's the latest page in the the thread that I've read?
    }
    var state = GM_getValue( stateField );
    if ( state == null ) {
        GM_setValue( stateField, 'read' ); // First time reading this thread
    }
    var now = new Date;
    GM_setValue( lastReadOnField, String( now.valueOf() ) );

    // find a store which forum this thread is in - for future use
    var getForumId = /forums_board.php\?forum_id=(\d+)/;
    var links = document.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href ) {
            var match = getForumId.exec( link.href );
            if ( match ) {
                GM_setValue( forumPrefix + data.thread_id, match[ 1 ] );
            } else if ( link.href.indexOf( 'forums_thread.php' ) > -1 ) {
                // capture how many posts are in the thread, so we know if there are new posts later
                var postCount = String( link.parentNode.parentNode.cells[ 0 ].innerHTML.match( /\d+/ ) );
                break;
            }
        }
    }
    GM_setValue( postCountField, postCount );
}

// this does all the highlighting and adding-of-links
function decorateForumList( args ) {
    if ( args == null ) args = {};
    var links = document.getElementsByTagName( 'a' );
    // loop through all the links, looking for thread links
    for ( var l = 0; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href && link.href.indexOf( 'forums_thread.php' ) > -1 ) {
            var match = extractThread.exec( link.href );
            var thread_id = match[ 1 ];

            // find out what we know about this thread
            var page  = GM_getValue( posPrefix + thread_id );
            var state = GM_getValue( statePrefix + thread_id );

            // get the parent row node
            var rowNode = link.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
            var count = Number( rowNode.cells[ 2 ].innerHTML ); // how many posts?
            var lastCount = GM_getValue( postCountPrefix + thread_id );
            if ( lastCount != null && lastCount != count ) {
                // new posts - make the row bold and get the right state colour
                state += 'New';
                rowNode.style.fontWeight = 'bold';
            }
            if ( state != null && colours[ state ] != null && ! args.noPostedHighlight ) {
                // we have a state, and we want coloured highlights - set the background colour
                rowNode.style.backgroundColor = colours[ state ];
            }
            // calculate the last page
            var lastPage = Math.floor( ( count - 1 ) / 10 ) + 1;

            // build the "jump to" links
            var div = document.createElement( 'div' );
            var threadLinks = [];
            if ( page != null ) {
                // we've read this thread - link to the last read page
                threadLinks.push( '<a href="forums_thread.php?thread_id=' + thread_id + '&page=' + page + '">last read</a>' );
            }
            // link to the last page
            threadLinks.push( '<a href="forums_thread.php?thread_id=' + thread_id + '&page=' + lastPage + '">last page</a>' );
            // create the links
            div.innerHTML = 'Jump to: ' + threadLinks.join( ' &bull; ' );
            // format the div
            div.className = 'dark_grey_text';
            div.style.width = '100%';
            div.style.borderTop = '1px solid rgb(218, 219, 214)';
            div.style.marginTop = '2px';
            div.style.paddingTop = '2px';
            div.style.fontSize = '10px';
            // add it to the row
            rowNode.cells[ 3 ].appendChild( div );
            // move on
            l += threadLinks.length + 2;
        }
    }
}

// find out who I'm logged in as
function whoami ( username ) {
    var currentSessid;
    var cookies = document.cookie.split( '; ' );

    // track session ID, to cover logging out by closing the browser
    for ( var c = 0; c < cookies.length; c++ ) {
        var bits = cookies[ c ].split( '=' );
        if ( bits[ 0 ] == "PHPSESSID" ) {
            currentSessid = bits[ 1 ];
            break;
        }
    }

    // I've been given a username - set it and be done
    if ( username != null ) {
        GM_setValue( 'username', username );
        GM_setValue( 'sessid', currentSessid );
        return;
    }

    var storedSessid = GM_getValue( 'sessid' );
    var username = GM_getValue( 'username' );
    if ( storedSessid == currentSessid && username!= "" && username != null ) {
        // we have a username for the current session - return it
        return username;
    }
    // find the user name by parsing links
    var links = document.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href ) {
            if ( link.href.indexOf( 'login.php' ) > -1 ) {
                // not logged in - no username
                return null;
            }
            if ( link.href.indexOf( 'your_etsy.php' ) > -1  ){
                // username is next to the Your Etsy link
                var cell = link.parentNode;
                var text = cell.parentNode.cells[ cell.cellIndex - 1 ].innerHTML;
                text = text.substring( 0, text.indexOf( ':' ) );
                // set the values and return
                GM_setValue( 'username', text );
                GM_setValue( 'sessid', currentSessid );
                return text;
            }
         }
     }
}

// add a listener to clear the stored username when you log out
function decorateLogout () {
    var links = document.getElementsByTagName( 'a' );
    for ( var l = 0; l < links.length; l++ ) {
        var link = links[ l ];
        if ( link.href ) {
            if ( link.href.indexOf( 'login.php' ) > -1 ) {
                // not logged in - bail out
                return;
            }
            if ( link.href.indexOf( 'logout.php' ) > -1 ) {
                // add listener to clear stored username
                link.addEventListener( "click", function () { whoami( "" ) }, true );
            }
        }
    }
}    

// utility function to replicate getElementsByClassName() on older Firefoxes
function getElementsByClassName ( class, node ) {
    if ( node == null ) node = document;
    if ( node.getElementsByClassName ) {
        return node.getElementsByClassName( class );
    } else {
        var classElements = new Array();
        var els = node.getElementsByTagName( '*' );
        var elsLen = els.length;
        var pattern = new RegExp("(^|\\s)"+class+"(\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
            if ( pattern.test(els[i].className) ) {
                classElements[j] = els[i];
                j++;
            }
        }
        return classElements;
    }
}


