// ==UserScript==
// @name Shack - Comment Filter
// @namespace http://www.lmnopc.com/greasemonkey/shackCommentsFilter/
// @description Filter out Shacknews comments you're not interested in.
// @include http://*.shacknews.com/chatty*
// @include http://shacknews.com/chatty*
// ==/UserScript==
/*

	Shacknews Comment Filter
	Author: ThomW - http://www.lmnopc.com/
	(c)2005-2011 Thom Wetzel

	If you like this script: http://amazon.com/gp/registry/1YRBQ22VGN9PR

	---------------------------------------------------------

	Version info:
	---------------------------------------------------------
   2011-07-19
      * Updated to work with nuShack	

   2008-08-17
      + Fixed that weird bug that caused all the posts to collapse when 
         no words were being filtered.   

	2008-08-15
	  + I pulled my head out of my ass and I'm using a regular expression
	     in place of .indexOf.  The advantage to this method is that the script
	     doesn't slow to a crawl when there are a bunch of words in the filter
	     list.  Before, the script would loop through each thread on the page,
	     and cycle through all the words in the filter and do an indexOf to 
	     look for them.  This is much slower than using a regular expression
	     that contains the multiple search terms.  I've always been kind of 
	     apprehensive toward regex but I realized how simple and perfect an 
	     application this is for them.  
	
	2007-10-16
		+ Modified stripPunctuation to convert \n into ' '
	
	2007-09-18
		+ Added missing trim() in addWord()

	2007-09-14
		+ Shack2007 Compatible

	2005.05.10:
		+ Initial release
*/

(function() {

	// grab start time of filtering
	var scriptStartTime = getTime();

	// utility functions

	function getTime()
	{
		benchmarkTimer = new Date();
		return benchmarkTimer.getTime();
	}

	// from http://www.somacon.com/p355.php
	var regEx_trim = RegExp(/^\s+|\s+$/g);	String.prototype.trim = function() { return this.replace(regEx_trim,""); }
	var regEx_ltrim = RegExp(/^\s+/);	String.prototype.ltrim = function() { return this.replace(regEx_ltrim,""); }
	var regEx_rtrim = RegExp(/\s+$/);	String.prototype.rtrim = function() { return this.replace(regEx_rtrim,""); }

	// I use TW_log in place of GM_log where I want to be able to turn it on/off
	TW_log = function(str) { return; return GM_log(str); }

	// from http://diveintogreasemonkey.org/
	function addGlobalStyle(css)
	{
		if(/microsoft/i.test(navigator.appName) && !/opera/i.test(navigator.userAgent))
		{
			document.createStyleSheet().cssText=css;
		}
		else
		{
			var ele=document.createElement('link');
			ele.rel='stylesheet';
			ele.type='text/css';
			ele.href='data:text/css;charset=utf-8,'+escape(css);
			document.getElementsByTagName('head')[0].appendChild(ele);
		}
	}

	// from http://www.crockford.com/javascript/remedial.html
	function isFunction(a) { return typeof a == 'function'; }
	function isObject(a) { return (a && typeof a == 'object') || isFunction(a); }
	function isArray(a) { return isObject(a) && a.constructor == Array; }

	// based on code from http://www.quirksmode.org/js/selected.html
	function getSel()
	{
		var txt = '';
		if (window.getSelection)
		{
			txt = window.getSelection();
		}
		else if (document.getSelection)
		{
			txt = document.getSelection();
		}
		else if (document.selection)
		{
			txt = document.selection.createRange().text;
		}
		else
		{
			return;
		}
		return String(txt).trim();
	}

	//
	function getElementByClassName(oElm, strTagName, strClassName)
	{
		var arrElements = oElm.getElementsByTagName(strTagName);
		for(var i=0; i < arrElements.length; i++)
		{
			if (arrElements[i].className.indexOf(strClassName) == 0)
			{
				return arrElements[i];
			}
		}
	}
	
   function getCookie(name, defaultValue) {
   	var dc = document.cookie;
   	var prefix = name + "=";
   	var begin = dc.indexOf("; " + prefix);
   	if (begin == -1) {
   		begin = dc.indexOf(prefix);
   		if (begin != 0) return defaultValue;
   	} else
   			begin += 2;
   	var end = document.cookie.indexOf(";", begin);
   	if (end == -1)
   		end = dc.length;
   	return unescape(dc.substring(begin + prefix.length, end));
   }
   function setCookie(name, value, future) {
   	if (future == null) future = 5;
   	var tempdate = new Date();
   	tempdate = tempdate.getTime() + ((future - 1 + future) * (7 * 24 * 60 * 60 * 1000));
   	var cookieDate = new Date(tempdate);
   	var curCookie = name + "=" + escape(value) + "; expires=" + cookieDate.toGMTString();
   	document.cookie = curCookie;
   }


	// var regEx_StripHtml = RegExp(/<\S[^>]*>/g);
	var regEx_StripHtml = RegExp(/<[^<|>]+?>/gi);
	stripHtml = function(str) { return String(str).replace(regEx_StripHtml, ' '); };

	var regEx_StripPunctuation = RegExp(/(!|\(|\)|\\|:|\;|&quot;|"|'|,|\.|\/|\?|<|&lt;|>|&gt;|\n)/g);
	stripPunctuation = function(str) { return String(str).replace(regEx_StripPunctuation, " "); };

// ----------------------------------------------------------------------------
//	Set up Greasemonkey menu items and callbacks
// ----------------------------------------------------------------------------

	// callback function for my Greasemonkey menu command
	function cb_addWord()
	{
		// find text that's highlighted
		var word = getSel();
		if (!word)
		{
			alert("No words were highlighted!");
			return;
		}

		// convert to lower case
		word = word.toLowerCase();

		// search array to make sure word isn't already in the list
		if (arrWordFilter_EZ.indexOf(word) != -1)
		{
			return;
		}

		// add the new word to our filter list
		arrWordFilter_EZ.push(word);

		// save the new filter list
		setCookie(sFilteredWordList_EZ, String(arrWordFilter_EZ));

		// alert user
		alert("'" + word + "' added to the comments filter");

		// update the filtered word list
		displayFilteredWordList();

		// update the page with the new filter
		doFilter();
      
		// publish updated word list
		publishWordList();
	}

	// Add word management tools to Greasemonkey menu
	GM_registerMenuCommand('Filter Highlighted Word(s)', cb_addWord)


	// Nuke all filters script
	function cb_NukeAllFilters()
	{
		// confirm nuke
		var res = confirm("Delete all of the words and users from your Shack Comments Filter?");
		if (!res)
			return;

		// remove all words
		arrWordFilter_EZ = new Array();
		arrUserFilter_EZ = new Array();

		// save the new filter list
		setCookie(sFilteredWordList_EZ, String(arrWordFilter_EZ));
		setCookie(sFilteredUserList_EZ, String(arrUserFilter_EZ));

		// alert user
		alert("Your Shack Comments Filter has been cleared.  You need to refresh the page for the change to take effect.");

		// update the filtered lists
		displayFilteredWordList();
		displayFilteredUserList();
	}

	// add nuke all filtered words option to the menu
	GM_registerMenuCommand('Clear Shack Comments Filter word list', cb_NukeAllFilters)


	function cb_setWoWFilter()
	{
		resetWoWFilter();
	}
	GM_registerMenuCommand('WoW Filter', cb_setWoWFilter)

	// Create menu option to allow users to set their publish filtered words list preference
	function cb_setPublishFilteredWords()
	{
		resetPublishFilteredWords();
	}
	GM_registerMenuCommand('Publish Filtered Words List?', cb_setPublishFilteredWords)



// ----------------------------------------------------------------------------
// filtering functions
// ----------------------------------------------------------------------------

function unFilter(event)
{
	var elem = event.target;
	if (!elem)
		return;

	// hide the filteron span
	elem.style.display = "none";

	// get threadid
	var id = elem.getAttribute('id');
	if (!id.length)
		return;
	id = id.substr(8);

	// find filteron's next sibling
	elem = elem.nextSibling;
	if (!elem)
		return;

	// display filteroff
	elem.style.display = '';

	// show preview block
	getElementByClassName(document.getElementById('item_' + id), 'div', 'capcontainer').style.display = '';
}

function collapsePost(postbody, threadid, reason)
{
	// collapses a filtered post

	//modify the post to include a filtered message and an ID so we can display it later
	postbody.innerHTML = '<span id="filteron' + threadid + '" class="pseudolink">' + reason + '</span><span style="display:none">' + postbody.innerHTML + '</span>'

	// programmatically add filteron's unfilter function
	var filteron = document.getElementById('filteron' + threadid);
	filteron.addEventListener('click', unFilter, true);

	//hide preview block
	try
	{
		var item = document.getElementById('item_' + threadid);
		var cc = getElementByClassName(item, 'div', 'capcontainer');
		if (cc == null)
		{
			GM_log('There is some malformed HTML in the postbody -- the collapse script cannot collapse item_' + threadid);
			return;
		}
		cc.style.display = 'none';
	}
	catch(err)
	{
		GM_log(err.message + ' (threadid = ' + threadid + ')');
	}
}

function generateRegExp()
{
   // create a regular expression that's used on all the posts 
   var rexp = ''; 
   var filterListLength = arrWordFilter_EZ.length; 
   for (i = 0; i < filterListLength; i++)
   {
      if (rexp.length)
         rexp += '|';
      
      word = arrWordFilter_EZ[i];

      // Deal with leading and trailing wildcards         
      word = (word[0] == '*') ? word.substr(1) : '\\b' + word; 
      word = (word[word.length - 1] == '*') ? word.substr(0, word.length - 1) : word + '\\b';
      
      rexp += word;
   }
   
   if (rexp.length)
   {
      rexp = '(' + rexp + ')';
      return new RegExp(rexp, 'i');
   }

   return null; 
}


function doFilter(tgtObject, pattern)
{
	// If the script is installed twice, this will prevent it from
	// running twice (at least in the instance I saw here on my PC
	if ((!arrUserFilter_EZ) || (!arrWordFilter_EZ))
	{
		return;
	}
   
	if (!tgtObject)
	{
      tgtObject = getFullPosts();
	}
	
	if (pattern == null)
	{
      pattern = generateRegExp();	
	}
	
	// handle arrays by filtering each element of the array then exit
	if (isArray(tgtObject))
	{
		for (var i = 0; i < tgtObject.length; i++)
		{
			if (boolReverseFilter)
			{
				doReverseFilter(tgtObject[i]);
			}
			else
			{
				var initTime = getTime();

				doFilter(tgtObject[i], pattern);

				TW_log('doFilter(' + tgtObject[i].id + '): ' + (getTime() - initTime));
			}
		}
		return;
	}

	var item, i, threadid;
	var postbody, postText;
	var postAuthor;

	// get thread id
	threadid = tgtObject.getAttribute("id").substring(5);

	// prevent double-filtering from happening
	if (document.getElementById('filteron' + threadid))
		return;

	// get all the divs under each post
	var oThreadDivs = tgtObject.getElementsByTagName('div');

	// walk through divs and look for the div containing 'postbody'
	var threadsLength = oThreadDivs.length; 
	for (var idxDiv = 0; idxDiv < threadsLength; idxDiv++)
	{
		// Grab user data for the current thread
		if (oThreadDivs[idxDiv].className.indexOf('postmeta') >= 0)
		{
			// init postAuthor for this go-around
			postAuthor = '';

			// search spans inside div#postmeta for author
			var oMetaSpans = oThreadDivs[idxDiv].getElementsByTagName('span');
			for (var idxSpan = 0; idxSpan < oMetaSpans.length; idxSpan++)
			{
				if (oMetaSpans[idxSpan].className.indexOf('author') >= 0)
				{
					postAuthor = oMetaSpans[idxSpan].getElementsByTagName('a')[0].innerHTML;
					postAuthor = postAuthor.trim();
					postAuthor = postAuthor.toLowerCase();
				}
			}
		}
		else if (oThreadDivs[idxDiv].className.indexOf('postbody') >= 0)
		{
			//
			postbody = oThreadDivs[idxDiv];

			// Now that we have postbody, check user filter
			if (arrUserFilter_EZ.indexOf(postAuthor) != -1)
			{
					collapsePost(postbody, threadid, "Filtered Author: " + postAuthor);
					break;
			}

			// prevent double-filtering from happening
			if (document.getElementById('filteron' + threadid))
				break;

			// get the post text, and surround it with spaces
			postText = " " + postbody.innerHTML + " ";

			// strip the HTML out of the post
			postText = stripHtml(postText);

			// WoW filter!
			if (boolFilterWoW)
			{
				if (postText.indexOf(" WoW ") >= 0)
				{
					collapsePost(postbody, threadid, "Filtered Word: WoW");
					break;
				}
			}
			
			// Make sure we have a valid pattern
			if (pattern != null)
			{
   			var filtered = postText.match(pattern);
   			if (filtered != null)
   			{
   			   collapsePost(postbody, threadid, 'Filtered Word: ' + filtered[0]);
   			}
   		}
		}
	}
}


function doReverseFilter()
{
	alert('ReverseFilter is still broken');
	return;

	// get collection of threads
	var items = document.evaluate("//div[@class='thread']/..", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	var item, i, threadid;
	var postbody, postText;
	var postAuthor;

	// loop through root posts
	for (item = null, i = 0; item = items.snapshotItem(i); i++)
	{
		var bFilter = true;

		// get thread id
		threadid = item.getAttribute("id").substring(11);

		// prevent double-filtering from happening
		if (document.getElementById('filteron' + threadid))
			continue;

		// get all the divs under each post
		var oThreadDivs = item.getElementsByTagName('div');

		// walk through divs and look for the div containing 'postbody'
		for (var idxDiv = 0; idxDiv < oThreadDivs.length; idxDiv++)
		{
			// Grab user data for the current thread
			if (oThreadDivs[idxDiv].className.indexOf('postmeta') >= 0) {

				// init postAuthor for this go-around
				postAuthor = '';

				// search spans inside div#postmeta for author
				var oMetaSpans = oThreadDivs[idxDiv].getElementsByTagName('span');
				for (var idxSpan = 0; idxSpan < oMetaSpans.length; idxSpan++) {
					if (oMetaSpans[idxSpan].className.indexOf('author') >= 0) {
						postAuthor = oMetaSpans[idxSpan].innerHTML.toLowerCase();
						postAuthor = stripHtml(postAuthor);
						postAuthor = trim(postAuthor.substring(4));
					}
				}
			}
			else if (oThreadDivs[idxDiv].className.indexOf('postbody') >= 0) {
				//
				postbody = oThreadDivs[idxDiv];

				// Now that we have postbody, check user filter
				for (var idx = 0; idx < arrUserFilter_EZ.length; idx++) {
					if (postAuthor == arrUserFilter_EZ[idx]) {
						bFilter = false;
						break;
					}
				}

				// jump out of the loop if the post author is filtered
				if (!bFilter)
					break;

				// get the post text, and surround it with spaces
				postText = " " + postbody.innerHTML + " ";

				// strip the HTML out of the post
				postText = stripHtml(postText);

				// convert punctuation in the post to spaces
				postText = stripPunctuation(postText);

				// WoW filter!
				if (boolFilterWoW) 
            {
					if (postText.indexOf(" WoW ") >= 0) 
               {
						bFilter = false;
						break;
					}
				}

				// convert postText to lower case
				postText = postText.toLowerCase();

				//spin through the list of filtered words
				for (iWordFilterIndex_EZ = 0; iWordFilterIndex_EZ < arrWordFilter_EZ.length; iWordFilterIndex_EZ ++ ) {
					if (postText.indexOf(" " + arrWordFilter_EZ[iWordFilterIndex_EZ] + " ") >= 0) {
						bFilter = false;
						break;
					}
				}
			}
		}

		// filter the post if bFilter is still true
		if (bFilter) 
      {
			collapsePost(postbody, threadid, "Reverse Filter");
		}
	}
}


// ----------------------------------------------------------------------------
// list management functions
// ----------------------------------------------------------------------------

function loadFilterList(filterListName)
{
	// reads in a specified list of names from the cookie

	var arr = new Array();

	arr = getCookie(filterListName);
	if (arr)
		arr = arr.split(",");

	if (!isArray(arr))
		arr = new Array();

	return arr;
}

function addWord(word)
{
	// This function is used when manually adding words to the list

	if (!word)
		return false;

	// trim whitespace
	word = word.trim();

	// convert to lower case
	word = word.toLowerCase();

	// search array to make sure word isn't already in the list
	if (arrWordFilter_EZ.indexOf(word) != -1)
	{
		return false;
	}

	// add the new word to our filter list
	arrWordFilter_EZ.push(word);

	// save the new filter list
	setCookie(sFilteredWordList_EZ, String(arrWordFilter_EZ));

	// update the filtered word list
	displayFilteredWordList();

	// re-filter
	doFilter();

	// publish updated word list
	publishWordList();

	//
	return true;
}


function addUser(userName)
{
	// function used to manually add users to the list

	if (!userName)
		return false;

	// convert to lower case
	userName = userName.toLowerCase();
	
	// search array to make sure word isn't already in the list
	if (arrUserFilter_EZ.indexOf(userName) != -1)
	{
		return false;
	}

	switch (userName)
	{
		case 'thomw': alert("HAHAHAHA I DON'T THINK SO..."); return;
	}

	// add the new word to our filter list
	arrUserFilter_EZ.push(userName);

	// save the new filter list
	setCookie(sFilteredUserList_EZ, String(arrUserFilter_EZ));

	// update the filtered word list
	displayFilteredUserList();

	// re-filter
	doFilter();

	//
	return true;
}

function displayFilteredWordList()
{
	displayFilterList("Word", arrWordFilter_EZ);
}

function displayFilteredUserList()
{
	displayFilterList("User", arrUserFilter_EZ);
}

function doAddButton(event)
{
	var button = event.target;
	if (!button)
	{
		alert('error finding button');
		return;
	}

	// determine which list we're editing
	var listName = button.getAttribute('id');
	if (!listName)
	{
		alert("button id is blank");
		return;
	}
	listName = listName.substr(3,4);

	// set a reference to the input containing the new word/user being added
	var textBox = document.getElementById('add' + listName + 'Box');
	if (!textBox)
	{
		alert('error finding textBox');
		return;
	}
	
	// call the correct function to add the word/user to the list
	if (listName == 'Word')
	{
		addWord(textBox.value);
	}
	else if (listName == 'User')
	{
		addUser(textBox.value);
	}
	else
	{
		alert('Unknown listName: ' + listName);
		return;
	}

	// clear out textBox for the next word/user
	textBox.value = '';

	// re-add the event handler
	document.getElementById('add' + listName + 'Button').addEventListener('click', doAddButton, false);	
}

function displayFilterList(listName, arr)
{
	var list = '';
	for (var i = 0; i < arr.length; i++)
	{
		list += '<span class="pseudolink">' + arr[i] + '</span> / ';
	}

	// Look for an instance of the filter list in the document
	var filterDiv = document.getElementById("filtered" + listName + "s");

	// If filterDiv hasn't already been created, create it now.
	if (filterDiv == null)
	{
		var filterDiv = document.createElement("div");
		filterDiv.setAttribute("id", "filtered" + listName + "s");

		// add filterDiv to filterBar
		filterBar.appendChild(filterDiv);
	}

	// write filterDiv's innerHTML
	filterDiv.innerHTML = '<span class="listlabel">Filtered ' + listName + 's:</span>' + list  + '\
			<input type="text" \
				name="add' + listName + 'Box" \
				id="add' + listName + 'Box" \
				class="text" /> \
			<input type="button" \
				value="Add" \
				id="add' + listName + 'Button" \
				class="button" />';

	// Loop through the anchors in filterDiv and add javascript that
	// runs on the 'onclick' event.
	var spans = filterDiv.getElementsByTagName("span");
	for (var i = 0; i < spans.length; i++)
	{
		// don't add handling to non-pseudolinks
		if (spans[i].className.indexOf("pseudolink") == -1)
		{
			continue;
		}

		if (listName == 'Word')
		{
			spans[i].addEventListener('click', removeWord, true);
		}
		else if (listName == 'User')
		{
			spans[i].addEventListener('click', removeUser, true);
		}
		else
		{
			alert('Unknown listName: ' + listName);
			return;
		}
	}
}

function removeWord(event)
{
	var word = event.target.innerHTML;

	// get the array index of the selected word
	var idx = arrWordFilter_EZ.indexOf(word);

	// word isn't in the array
	if (idx == -1)
	{
		alert("ERROR! Couldn't find '" + word + "' in the filtered word list");
		return;
	}

	// confirm word removal
	var res = confirm("Remove '" + word + "' from your Shack Comments Filter?");
	if (!res)
		return;

	// remove the word from the array
	arrWordFilter_EZ.splice(idx, 1);

	// save the new filter list
	setCookie(sFilteredWordList_EZ, String(arrWordFilter_EZ));

	// alert user
	alert("'" + word + "' removed from the comments filter list.  You must refresh the page to see the results.");

	// update the filtered word list
	displayFilteredWordList();

	// publish updated word list
	publishWordList();
}

function removeUser(event)
{
	var userName = event.target.innerHTML;

	// get the array index of the selected word
	var idx = arrUserFilter_EZ.indexOf(userName);

	// word isn't in the array
	if (idx == -1) {
		alert("ERROR! Couldn't find '" + userName + "' in the filtered user list");
		return;
	}

	// confirm word removal
	var res = confirm("Remove '" + userName + "' from your Shack Comments Filter?");
	if (!res)
		return;

	// remove the word from the array
	arrUserFilter_EZ.splice(idx, 1);

	// save the new filter list
	setCookie(sFilteredUserList_EZ, String(arrUserFilter_EZ));

	// alert user
	alert("'" + userName + "' removed from the comments filter list.  You must refresh the page to see the results.");

	// update the filtered word list
	displayFilteredUserList();
}

// ----------------------------------------------------------------------------
// WoW filter
// ----------------------------------------------------------------------------
function resetWoWFilter()
{
	if (confirm("Click OK to activate the WoW filter."))
	{
		boolFilterWoW = 1;
	}
	else
	{
		boolFilterWoW = 0;
	}
	setCookie(sFilterWoW_EZ, boolFilterWoW);
}

// ----------------------------------------------------------------------------
// list upload functions for transmitting filter lists to my site
// ----------------------------------------------------------------------------

function resetPublishFilteredWords()
{
	boolPublishFilteredWords = null;
	setPublishFilteredWords();
}

function setPublishFilteredWords()
{
	if (confirm("Welcome to the Shack Comments Filter Greasemonkey Script!\n\nThis script has a feature that allows you to share your filtered word list with the internet at large.  This feature is optional.  Your username and filtered word list will be transmitted to ThomW's site.  Your username will not be shown, and the data collected will be available in the form of a 'most filtered words' style list.\n\nAlso note, your filtered Author list is private and won't be transmitted by this script.  I don't think anyone wants to be known as the most-filtered Shacker.\n\nIf you're okay with sharing your list, click the OK button.  Click Cancel to disable this feature."))
		boolPublishFilteredWords = 1;
	else
		boolPublishFilteredWords = 0;
	GM_setValue(sPublishWordList_EZ, boolPublishFilteredWords);
}

function findUsername()
{
	return stripHtml(getElementByClassName(document.getElementById('user'), 'li', 'user').firstChild.data);
}

function publishWordList()
{
	// Uploads user's lists to my site so that I can create a
	// database of the top blocked words/phrases.  I'm not
	// collecting filtered users, because I dont' want to cause
	// anyone mental anguish.  ;)


	// exit if the user doesn't want to share their word list
	if (!boolPublishFilteredWords)
		return;

	// find the user
	var userName = findUsername();
	if (!userName)
	{
		alert('You have to be logged in to Shacknews to upload your filter list');
		return;
	}

	var wordList = loadFilterList('shackCommentsFilterList');  

	var length = wordList.length;
	if (boolFilterWoW) length++;

	//
	var addr = 'http://www.lmnopc.com/greasemonkey/shackCommentsFilter/stats_collect.php?update=' + length;
	for (var i = 0; i < wordList.length; i++)
		addr += '&' + i + '=' + escape(wordList[i]);

	if (boolFilterWoW) addr += '&' + i++ + '=' + escape('WoW');

	addr += '&userName=' + userName;

	addr += '&reverseFilter=' + boolReverseFilter;

	// use xmlhttpRequest to post the data
  GM_xmlhttpRequest({ method:"GET",
  			url: addr,
		    onload:function(result) {
		      try {
		      } catch (e) { alert('Connection failed'); }
		    }
  });
}

// this function returns an array of full posts on the current page for filtering
function getFullPosts()
{
	initStart = getTime();

	var fullPosts = new Array();
	var xPathItems = document.evaluate("//div[contains(@class, 'fullpost')]/..", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (item = null, i = 0; item = xPathItems.snapshotItem(i); i++)
	{
		fullPosts.push(item);
	}

	TW_log('getFullPosts: ' + (getTime() - initStart));

	return fullPosts;
}

// checkbox functions
function reverseFilterHandler()
{
	var endis = boolReverseFilter ? 'disable' : 'enable';
	if (!confirm('Are you sure you want to ' + endis + ' Reverse Filtering?'))
		return false;
	boolReverseFilter = !boolReverseFilter;
	setCookie(sReverseFilter, boolReverseFilter);
}
function filterWoWHandler()
{
	boolFilterWoW = !boolFilterWoW;
	setCookie(sFilterWoW_EZ, boolFilterWoW);
}
function publishWordsHandler()
{
	boolPublishFilteredWords = !boolPublishFilteredWords;
	GM_setValue(sPublishWordList_EZ, boolPublishFilteredWords);
}

// ----------------------------------------------------------------------------
// main !
// ----------------------------------------------------------------------------

	var initStart = getTime();

	// read variables
	var sPublishWordList_EZ = 'publishFilteredWords';
	var boolPublishFilteredWords = GM_getValue(sPublishWordList_EZ, null);
	if (boolPublishFilteredWords == null) {
		setPublishFilteredWords();
	}

	var sFilterWoW_EZ = 'filterWoW';
	var boolFilterWoW = getCookie(sFilterWoW_EZ, false);

	var sReverseFilter = 'reverseFilter';
	var boolReverseFilter = getCookie(sReverseFilter, false);

	// declare and initialize filtered word list
	var arrWordFilter_EZ = new Array();
	var sFilteredWordList_EZ = 'shackCommentsFilterList';
	arrWordFilter_EZ = loadFilterList(sFilteredWordList_EZ);

	// declare and initialize filtered user list
	var arrUserFilter_EZ = new Array();
	var sFilteredUserList_EZ = 'shackUsersFilterList';
	arrUserFilter_EZ = loadFilterList(sFilteredUserList_EZ);

	TW_log('Variables loaded: ' + (getTime() - initStart));
	initStart = getTime();

	// style the filterBar
	addGlobalStyle('span.pseudolink { color: #FFBB22; text-decoration: underline; cursor: pointer; } \
	div#filterBar input.text {font-family: verdana; font-size: 12px; background-color: #333333; border: 1px solid #778888; color: #eeeeee; width: 7em; } \
	div#filterBar input.button {font-family: verdana; font-size: 12px; } \
	div#filterBar span.listlabel { width: 8.6em; display: block; float: left; margin-right: 0.76em; } \
	div#filterOptions input { margin-left: 10px; } \
	div#filterOptions a { text-decoration: none; } \
	div#filterOptions a:hover { text-decoration: underline; } \
	div.commentsbar { margin-bottom: 10px; }');

	TW_log('addGlobalStyle: ' + (getTime() - initStart));
	initStart = getTime();

	// create the filterBar
	var filterBar = document.createElement("div");
	filterBar.setAttribute("id", "filterBar");
	filterBar.style.borderTop = '1px solid rgb(174, 174, 155)';
	filterBar.style.backgroundColor = '#222';
	filterBar.style.lineHeight = '2.5em';
	filterBar.style.padding = '0 10px';
	filterBar.style.margin = '60px 0 0 0';
	filterBar.style.color = '#fff';

	// display the filtered words on the page
	displayFilteredWordList();
	displayFilteredUserList();

	// Create filterOptions and add them to the filterBar
	var divFilterOptions  = document.createElement("div");
	divFilterOptions.setAttribute("id", "filterOptions");
	divFilterOptions.style.textAlign = 'right';
	divFilterOptions.style.lineHeight = '1.5em';

	// WoW checkbox
	var chkFilterWoW = document.createElement("input");
	chkFilterWoW.setAttribute("type", "checkbox");
	chkFilterWoW.setAttribute("name", "filterWoW");
	chkFilterWoW.setAttribute("id", "filterWoW");
	chkFilterWoW.checked = boolFilterWoW ? true : false;
	chkFilterWoW.addEventListener('click', filterWoWHandler, true);
	divFilterOptions.appendChild(chkFilterWoW);

	// WoW label
	var lblFilterWoW = document.createElement("label");
	lblFilterWoW.innerHTML = "WoW Filter";
	lblFilterWoW.setAttribute("for", "filterWoW");
	divFilterOptions.appendChild(lblFilterWoW);

	/*
	// Reverse filter checkbox
	var chkReverseFilter = document.createElement("input");
	chkReverseFilter.setAttribute("type", "checkbox");
	chkReverseFilter.setAttribute("name", "reverseFilter");
	chkReverseFilter.setAttribute("id", "reverseFilter");
	chkReverseFilter.checked = boolReverseFilter ? true : false;
	chkReverseFilter.addEventListener('click', reverseFilterHandler, true);
	divFilterOptions.appendChild(chkReverseFilter);

	// Reverse Filter label
	var lblReverseFilter = document.createElement("label");
	lblReverseFilter.innerHTML = "Reverse Filter";
	lblReverseFilter.setAttribute("for", "reverseFilter");
	divFilterOptions.appendChild(lblReverseFilter);
	*/

	// publishing checkbox
	var chkPublishWords = document.createElement("input");
	chkPublishWords.setAttribute("type", "checkbox");
	chkPublishWords.setAttribute("name", "publishWords");
	chkPublishWords.setAttribute("id", "publishWords");
	chkPublishWords.checked = boolPublishFilteredWords ? true : false;
	chkPublishWords.addEventListener('click', publishWordsHandler, true);
	divFilterOptions.appendChild(chkPublishWords);

	// publishing label
	var lblPublishWords = document.createElement("label");
	lblPublishWords.innerHTML = "Publish Word List";
	lblPublishWords.setAttribute("for", "publishWords");
	divFilterOptions.appendChild(lblPublishWords);

	// stats link
	var viewStats = document.createElement("span");
	viewStats.innerHTML = ' [<a href="http://lmnopc.com/greasemonkey/shackCommentsFilter/stats.php" target="_blank" />view</a>] ';
	divFilterOptions.appendChild(viewStats);

	// shackmessage ThomW link
	var smThom = document.createElement("span");
	smThom.innerHTML = '| <a href="http://www.shacknews.com/msgcenter/popup.x?person=ThomW" onclick="popup( \'ThomW\' ); return false;" title="ShackMessage Thom"><img src="data:image/gif;base64,R0lGODlhDQAJAIABAP%2F%2F%2FwAAACH5BAEAAAEALAAAAAANAAkAAAIXhI8Zy3wBmoMRymrmqrQ9x0mgeCWmUQAAOw%3D%3D" alt="ShackMessage Thom" border="0" /> Thom</a>';
	divFilterOptions.appendChild(smThom);

	// timer display
	var spTimer = document.createElement('span');
	spTimer.setAttribute('id', 'filterBenchmark');
	spTimer.innerHTML = ' |';
	divFilterOptions.appendChild(spTimer);

	// add filterOptions to the filterBar
	filterBar.appendChild(divFilterOptions);

	// add filterBar to the page
	var commentstools = getElementByClassName(document, 'div', 'commentstools');
	if (commentstools)
	{
		commentstools.style.height = 'auto';
		commentstools.appendChild(filterBar);
	}

	// Add the javascript that runs in the event of an 'onclick' event.
	document.getElementById('addUserButton').addEventListener('click', doAddButton, false);
	document.getElementById('addWordButton').addEventListener('click', doAddButton, false);

	TW_log('filterBar: ' + (getTime() - initStart));
	initStart = getTime();

	//SHUT UP DAGGAH!

	// filter all the fullPosts
	doFilter();

	// benchmark filter update
	spTimer.innerHTML = ' | ' + (getTime() - scriptStartTime) + 'ms';

	// log execution time
	GM_log((getTime() - scriptStartTime) + 'ms');

})();

