Jump to content

User:X!/iglooMain.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//<nowiki>
/* ======================================================== *\
** 			igloo frontend manager - main
\* ======================================================== */
/*
fixes:
- fixed a bug that prevented igloo from intercepting and blocking some key presses in Google Chome.
- fixed a bug that caused text size issues in the vector skin - igloo will now more aggressively defend its CSS.
- fixed a bug where igloo would occasionally wait much longer than was necessary after loading data from iglooNet before proceeding.
- fixed a bug where page titles containing more than one apostrophe could not be clicked on in the recent changes feed.
- fixed an initial filter error where only one condition would be checked.
- fixed a bug where igloo did not display total sessions correctly.

changes:
- implemented the filter system.
- added settings for controlling filters.
- created the default filters for general use.
- make several improvements to the profanity highlighter.

behind the scenes:
- altered iglooNet to be able to stream filters.
- introduced the ID system.

*/

	function iglooMain () {
		this.internalCounter 	= 0;
		this.iglooBase			= new wa_document ();
		
		this.launch = function ( details ) {
			switch ( this.internalCounter ) {
				default: case 0:
					this.startload = new Date ();
					
					this.canDebug = ( typeof console !== 'undefined' );
					wa_window.prototype = new wa_document;
					wa_element.prototype = new wa_document;
				
					// step 1 of launching: build the loading interface.
					var t = 'igloo - ' + iglooSettings.versionString;
					var t2 = '';
					for ( var i = 0; i < mw.config.get('wgUserGroups').length; i ++ ) {
						if ( mw.config.get('wgUserGroups')[i] == 'steward' ) { t2 += 'steward|'; }
						else if ( mw.config.get('wgUserGroups')[i] == 'oversight' ) { t2 += 'oversighter|'; }
						else if ( mw.config.get('wgUserGroups')[i] == 'checkuser' ) { t2 += 'checkuser|'; }
						else if ( mw.config.get('wgUserGroups')[i] == 'bureaucrat' ) { t2 += 'bureaucrat|'; }
						else if ( mw.config.get('wgUserGroups')[i] == 'sysop' ) { t2 += 'administrator|'; iglooSettings.mesysop = true; }
						else if ( mw.config.get('wgUserGroups')[i] == 'rollbacker' ) { t2 += 'rollback|'; }
					}
					if ( t2 !== '' ) t += ' [wiki:' + t2.substr ( 0, t2.length - 1 ) + ']';
					
					//t += ' [igloo:' + iglooSettings.iglooFlags + ']';
					document.title = t;
					
					iglooSettings.userGroup = t2.substr ( 0, t2.length - 1 );
					
					this.iglooBase.wk_base.innerHTML= ''; // just destroy the mediawiki page content
					this.iglooInterface 			= new wa_window ( this.iglooBase.wk_base );
					this.iglooInterface.win_bg 		= '#ededff';
					this.iglooInterface.win_maintfill	= false;
					this.iglooInterface.win_fill	= true;
					this.iglooInterface.applyAll ();
					
					// step 2 of launching: add text.
					this.iglooLoading			= new wa_window ( this.iglooBase.wk_base );
					this.iglooLoading.win_width = 210;
					this.iglooLoading.win_height= 17;
					this.iglooLoading.win_bd 	= '#bbbbff';
					this.iglooLoading.win_bd_wd = 1;
					this.iglooLoading.win_bg 	= '#fdfdff';
					this.iglooLoading.win_content = '<div style="color: #555588; font-size: 14px; font-weight: bold; text-align: center; width: 100%;">Finalising load. Please wait...</div>';
					this.iglooLoading.applyAll ();
					this.iglooLoading.center ( 'both', true, new Array ( 0, -100 ) );
					
					// step 3 of launching: verify session.
					this.iglooSession = new iglooSecurity ();
					if ( typeof this.iglooSession.shutdown != 'undefined' ) return false;
					this.iglooSession.verifySession ( 'initial' );
					
					// increment counter
					this.internalCounter ++;
					break;
					
				case 1:
					// step 4 of launching: the session is verified, tell the user.
					this.iglooLoading.win_width = 330;
					this.iglooLoading.win_content = '<div style="color: #555588; font-size: 14px; font-weight: bold; text-align: center; width: 100%;">Verified session. Requesting from iglooNet...</div>';
					this.iglooLoading.applyAll ();
					this.iglooLoading.center ( 'both', true, new Array ( 0, -100 ) );
					
					// increment counter
					this.internalCounter ++;
					
					// run
					this.iglooNet = new iglooNet ();
					this.iglooNet.retrieve ( true );
					
					break;
					
				case 2:
					// step 5 of lauching: load the user settings
					this.iglooLoading.win_width = 180;
					this.iglooLoading.win_content = '<div style="color: #555588; font-size: 14px; font-weight: bold; text-align: center; width: 100%;">Loading user settings...</div>';
					this.iglooLoading.applyAll();
					this.iglooLoading.center ( 'both', true, new Array ( 0, -100 ) );
					
					// increment counter
					this.internalCounter ++;
					
					// run
					this.iglooManageSettings = new iglooManageSettings ();
					this.iglooManageSettings.retrieve ();
					
					break;
					
				case 3:
					// adjust title now that the settings have loaded and we know the iglooNet permissions
					document.title = document.title + ' [igloo:' + iglooSettings.iglooFlags + ']';
					
					// display first run if relevant
					// increment counter
					this.internalCounter ++;
					
					if ( iglooSettings.firstRun == true ) {
						var url = iglooSettings.remoteHost + 'main.php?action=settings&do=set&session='+igloo.iglooSession.session+'&me='+encodeURIComponent(mw.config.get('wgUserName'))+'&setting=firstRun&value=false';
						iglooImport( url, true, 'iglooFirstRun' );
						
						wa ( ':api' ).get ( 'ig_firstrun', iglooSettings.localBase + 'config', 1 ).wait ( function () { 
							var firstruntext = 	wa ( ':api' ).results ['ig_firstrun']['query']['pages']['page']['revisions']['rev']['#text'], regTest = /firstrun:(.+?);;/i, o;
							firstruntext = regTest.exec ( firstruntext );
							firstruntext = firstruntext[1].replace ( '%CURRENTUSER%', mw.config.get('wgUserName') );
							
							igloo.iglooLoading.win_width = 900;	
							igloo.iglooLoading.win_height = 400;	
							igloo.iglooLoading.win_content = firstruntext;	
							igloo.iglooLoading.applyAll ();
							igloo.iglooLoading.center ( 'both', true );
						 } ).run ();
					} else { this.launch (); }
					
					break;
				
				case 4:
					// finalising launch: the session is verified, tell the user.
					this.iglooLoading.win_width = 210;
					this.iglooLoading.win_height= 17;
					this.iglooLoading.win_content = '<div style="color: #555588; font-size: 14px; font-weight: bold; text-align: center; width: 100%;">Success. Building interface...</div>';
					this.iglooLoading.applyAll ();
					this.iglooLoading.center ( 'both', true, new Array ( 0, -100 ) );
					
					// preload images
					if ( iglooSettings.preloadInterface === true ) {
						var images = new Array ( 'logo', 'back', 'back-grey', 'forward', 'forward-grey', 'hist', 'revert', 'settings', 'go' ), images2 = new Array ();
						for ( var i = 0, l = images.length; i < l; i ++ ) {
							images2 [i] 	= new Image ();
							images2 [i].src = iglooSettings.remoteHost + 'images/igloo-' + images [i] + '.png';
						}
					}
					
					// increment counter
					this.internalCounter ++;
					
					// run
					setTimeout( "igloo.launch ();", 750 );
					break;
					
				case 5:
					this.endload = new Date ();
				
					// start the required program elements. log if possible.
					this.iglooDiff = new iglooDiff ();
					this.iglooDiff.start ();
					if ( this.canDebug === true ) console.log ( 'igloo: started diff component' );
					
					this.iglooStatus = new iglooStatus ();
					this.iglooStatus.start ();
					if ( this.canDebug === true ) console.log ( 'igloo: started status component' );
					
					this.iglooControls = new iglooControls ();
					this.iglooControls.start ();
					if ( this.canDebug === true ) console.log ( 'igloo: started control component' );
					
					this.iglooChanges = new iglooChanges ();
					this.iglooChanges.start ();
					if ( this.canDebug === true ) console.log ( 'igloo: started changes component' );
					
					this.iglooPopup = new iglooPopup ();
					this.iglooPopup.start ();
					if ( this.canDebug === true ) console.log ( 'igloo: started popup component' );
					
					this.iglooActions = new iglooActions ();
					if ( this.canDebug === true ) console.log ( 'igloo: started actions component' );
					
					this.iglooManageSettings.start ();
					if ( this.canDebug === true ) console.log ( 'igloo: started settings component' );
					
					// hide the loading element
					this.iglooLoading.hide ();
					if ( this.canDebug === true ) console.log ( 'igloo: completed load!' );
					
					break;
			}
		}
		
		this.shutdown = function ( reason, retry ) {
			if (reason == null) { reason = ''; } else { reason = '<br />'+reason; }
			if (retry == 'retry') { reason += '<br /><span style="cursor: pointer;" onmouseover="this.style.color=\'#333333\';" onmouseout="this.style.color=\'#555588\';" onclick="window.location = window.location.href;">retry</span> | <span style="cursor: pointer;" onmouseover="this.style.color=\'#333333\';" onmouseout="this.style.color=\'#555588\';" onclick="window.location = \''+iglooSettings.articleBase+iglooSettings.localBase+'Help\';">help</span>'; }
			if (retry == 'tryagain') { reason += '<br /><span style="cursor: pointer;" onmouseover="this.style.color=\'#333333\';" onmouseout="this.style.color=\'#555588\';" onclick="window.location = \''+iglooSettings.articleBase+iglooSettings.localBase+'init\';">new session</span>'; }
			
			if ( typeof this.iglooChanges != 'undefined' ) 	this.iglooChanges.destroy ();
			if ( typeof this.iglooDiff != 'undefined' ) 	this.iglooDiff.destroy ();
			if ( typeof this.iglooControls != 'undefined' ) this.iglooControls.destroy ();
			if ( typeof this.iglooStatus != 'undefined' ) 	this.iglooStatus.destroy ();
			if ( typeof this.iglooManageSettings != 'undefined' ) 	this.iglooManageSettings.hidedisplay ();
			
			if ( typeof this.iglooSession != 'undefined' ) 	clearTimeout ( this.iglooSession.verificationTimer );
			if ( typeof this.iglooNet != 'undefined' ) 		clearTimeout ( this.iglooNet.retrievalTimer );
			
			this.iglooLoading.show ();
			this.iglooLoading.win_height = 0;
			this.iglooLoading.win_width = 350;
			this.iglooLoading.win_content = '<div style="color: #555588; font-size: 14px; font-weight: bold; text-align: center; width: 100%;">igloo has closed'+reason+'</div>';
			this.iglooLoading.applyAll ();
			this.iglooLoading.center ( 'both' );
			
			return true;
		}
	}
	
	
	
	
	function iglooNet () {
		// the iglooNet module retrieves wiki data from the igloo server to help fight vandalism
		this.initial = true;
		this.lastUpdate = 0;
		this.iglooScores = [];
		this.iglooCount = 0;
		this.pingFails = 0;
		this.iglooCommit = 0;
		
		this.retrieve = function ( cacheBypass ) {
			if ( typeof igloo.iglooStatus != 'undefined' ) igloo.iglooStatus.addStatus ( 'Requesting data from iglooNet...' );
			
			this.internalCounter = 0;
			if ( cacheBypass == true ) { var cache = '&cachebypass=true'; } else { var cache = ''; }
			iglooImport ( iglooSettings.remoteHost + 'main.php?action=retrieve&pingfails=' + this.pingFails + '' + cache + '&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&session=' + igloo.iglooSession.session, true, 'iglooRetrieve' );
		
			this.retrieveFailed = setTimeout ( function () { igloo.shutdown ( 'userlist retrieval failed - lost connection to iglooNet', 'retry' ); return false; }, iglooSettings.serverTimeout * 3 * 1000 );
		
			//this.retrieveMain ();
		}
		this.retrieveMain = function ( status ) {
			if ( status === 'ok' ) {
				clearTimeout ( this.retrieveFailed );
				igloo.iglooNet.mergeUpdates ( iglooNetScores );
				return true;
			} else {
				igloo.shutdown ( 'session invalid or expired', 'tryagain' );
				return false;
			}
			/*thisUpdate 		= false;
			if ( typeof iglooScoresDone != 'undefined' ) if ( iglooScoresDone != 'unknown' ) if ( iglooScoresDone == 'ok' ) {
				iglooScoresDone = 'unknown';
				this.pingFails = 0;
				igloo.iglooNet.mergeUpdates ( iglooNetScores );
				return true;
			} else {
				igloo.shutdown ( 'session invalid or expired', 'tryagain' );
				return false;
			}
			
			if ( this.internalCounter >= ( iglooSettings.serverTimeout * 3 ) ) {
				this.pingFails ++;
				if ( typeof igloo.iglooStatus !== 'undefined' ) igloo.iglooStatus.addStatus ( '<strong>Server connect failed (action: retrieve; pingfails ' + this.pingFails + ')</strong>' );
				if ( this.pingFails >= iglooSettings.permitPingfails ) {
					igloo.shutdown ( 'userlist retrieval failed - lost connection to iglooNet', 'retry' );
					return false;
				}
				if ( igloo.canDebug === true ) console.log ( 'igloo: pingfail on retrieve (number '+this.pingFails+')' );
				this.internalCounter = 0;
				this.retrieveMain ();
				
				return false;
			} else {
				this.internalCounter ++;
				thisUpdate 		= this;
				setTimeout ( "if ( thisUpdate ) { thisUpdate.retrieveMain (); }", 500 );
			}*/
		}
		
		this.mergeUpdates = function ( newUpdates ) {
			// the merge updates function takes the data that the iglooNet server has just sent and merges it into the central iglooScores array that is used
			// to list the diffs to the user.
			
			var j = 0;
			for ( var i in newUpdates ) {
				// newUpdates[i][0] = u [or] p
				// newUpdates[i][1] = score [or] flag
				// i 				= username [or] pagename
				
				if ( ! this.iglooScores[i] ) {
					// if we don't already have data on this item
					this.iglooScores[i] = [];
					this.iglooScores[i] = newUpdates[i];
				} else {
					// if we DO already have data on this item, update it
					this.iglooScores[i].length = 0;
					this.iglooScores[i] = [];
					this.iglooScores[i] = newUpdates[i];
				}
				
				j ++;
				this.iglooCount ++;
			}
			
			if ( this.initial == true ) {
				igloo.launch ();
				this.initial = false;
			}
			
			this.retrievalTimer = setTimeout ("igloo.iglooNet.retrieve();", 30 * 1000);
		}
		
		this.scoreObject = function ( object ) {
			// generally, administrators/crats will be sent as a risk of 0 and rollbackers will be given 0.2.
			// trusted users, who have made over 250 edits will be assigned a score of 0.4 - mysteriously meaning
			// vandalism is likely to appear first, but there will always be an 'untrustworthy' edit to check.
			// by default, other users and IPs will have 0.5 - the server assigns higher scores based on user
			// actions
			var score 	= [];
			score.length= 0;
			score[0] 	= false;
			score[1]	= '';
			
			if ( (igloo.iglooNet.iglooScores[object]) && (igloo.iglooNet.iglooScores[object][0] == 'u') ) { 
				score[0] = igloo.iglooNet.iglooScores[object][1];
			}
			
			// if we have special data on this page, such as a whitelist of priority warning, append it here.
				// pages that are whitelisted will be sent with a priority of 0, regardless of the user score
				// pages that are flagged will gain a score of 0.7
				// pages that are blacklisted will gain a score of 0.9
			if ( ( igloo.iglooNet.iglooScores[object] ) && ( igloo.iglooNet.iglooScores[object][0] == 'p' ) ) { 
				switch ( igloo.iglooNet.iglooScores[object][1] ) {
					case 'w':
						score[0] = 0;
						score[1] = 'w';
						break;
						
					case 'f':
						score[0] = 0.6;
						score[1] = 'f';
						break;
						
					case 'b':
						score[0] = 0.9;
						score[1] = 'b';
						break;
				}
			}
			
			return score;
		}
		
		this.colourScore = function ( score, defaultCol ) {
			defaultCol = typeof ( defaultCol ) != 'undefined' ? defaultCol : '#ffffff';
			if ( score === false ) return defaultCol;
			
			if (iglooSettings.enableFeedColour == false) {
				var col = defaultCol;
			} else if ( score >= 0.8 ) {
				var col = iglooSettings.flagColours[0];
			} else if ( score >= 0.6 ) {
				var col = iglooSettings.flagColours[1];
			} else if ( score >= 0.4 ) {
				var col = iglooSettings.flagColours[2];
			} else if ( score >= 0.2 ) {
				var col = iglooSettings.flagColours[3];
			} else if ( score >= 0 ) {
				var col = iglooSettings.flagColours[4];
			} else {
				var col = defaultCol;
			}
			
			return col;
		}
		
		this.genCode = function ( len ) {
			var a = new Array ( '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ), s = '', x = a.length;
			for ( var i = 0; i < len; i ++ ) {
				var r = Math.floor ( Math.random () * x );
				s += a[r];
			}
			return s;
		}
		
		this.investigate = function ( user ) {
			if ( ( typeof user !== 'string' ) || ( iglooSettings.iglooFlags.indexOf ( 't' ) === -1 ) || ( iglooSettings.commitToIgNet === false ) ) return false;
			
			var score = this.scoreObject ( user );
			if ( ( user.match ( /^[0-9]+\.[0-9]+\.[0-9]+\.?[0-9]*$/i ) === null ) && ( this.iglooCommit >= iglooSettings.commitWhen ) && ( ( score[0] === false ) || ( iglooSettings.recheck === true ) ) ) {
				this.iglooCommit = 0; // reset counter
				
				var url = iglooSettings.remoteHost + 'main.php?action=investigate&session=' + igloo.iglooSession.session + '&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) +'&user=' + user + '';
				iglooImport ( url, true, 'iglooInvestigate' );
			} else { this.iglooCommit ++; }
		}
	}
	
	function iglooFilter ( filter ) {
		
		// generate filter variables
		this.conditions = [];
		this.events = [];
		this.activeChange = false;
		
		this.blockFilters = false; // whether to block further filter execution after this one
		
		
		this.parseFilter = function ( code ) {
			var t = t2 = t3 = t4 = [];
			
			// split into statements
			t = code.split ( "\n" );
			if ( t.length === 0 ) return false; // if no statements
			
			// for each statement, extract the commands
			for ( var i = 0, l = t.length; i < l; i ++ ) {
				t2 [i] = [];
				t2 [i] = t [i].split ( ' ' );
				
				// switch the type of statement (i.e. the first command)
				switch ( t2 [i] [0] ) {
					case 'if': case 'ifany':
						if ( this.parseCondition ( t2 [i] ) === false ) return false;
						break;
						
					case 'set': case 'hide': case 'mark': case'blockfilters':
						if ( this.parseEvent ( t2 [i] ) === false ) return false;
						break;
						
					case '': case '//': //comments
						
						break;
						
					 default:
					 	// if we cannot recognise the command, die.
						return false;
						break;
				}
			}
			
		}
		
		this.parseCondition = function ( array ) {
			if ( array.length < 4 ) return false;
			
			// insert this condition
			var useId = this.conditions.length;
			this.conditions [useId] = {};
			
			// if vs ifany
			this.conditions [useId] ['type'] = array [0];
			
			if ( array [0] === 'if' ) {
				switch ( array [1] ) { default: return false; break; case 'summary': case 'oldsize': case 'newsize': case 'changesize': case 'tags': case 'title': case 'user': case 'score': break; }
				this.conditions [useId] ['target'] = array [1];
				if ( this.conditions [useId] ['negate'] = ( ( array [2] === 'NOT' ) || ( array [2] === '!' ) ) ) { var s = 3; } else { var s = 2; }
				switch ( array [s] ) { default: return false; break; case 'regexmatch': case 'contains': case '==': case '<': case '>': break; }
				// op limits
				if ( ( array [1] === 'tags' ) && ( ( array [s] !== 'regexmatch' ) && ( array [s] !== 'contains' ) && ( array [s] !== '==' ) ) ) return false;
				if ( ( ( array [s] === '<' ) || ( array [s] === '>' ) ) && ( ( array [1] !== 'oldsize' ) && ( array [1] !== 'newsize' ) && ( array [1] !== 'changesize' ) && ( array [1] !== 'score' ) ) ) return false;
				// fin
				this.conditions [useId] ['op'] = array [s];
				
				this.conditions [useId] ['value'] = '';
				for ( var i = s+1, l = array.length; i < l; i ++ ) {
					this.conditions [useId] ['value'] += array [i] + ' ';
				}
				this.conditions [useId] ['value'] = this.conditions [useId] ['value'].substr ( 0, this.conditions [useId] ['value'].length - 1 );
			}
			
			return true;
		}
		
		this.parseEvent = function ( array ) {
			var t = {};
			
			// the command
			t ['type'] = array [0];
			
			switch ( array [0] ) {
				default: return false; break;
				
				case 'set': // set [score/id] [absolute/relative] value
					// checks
					if ( ( array [1] !== 'score' ) && ( array [1] !== 'id' ) && ( array [1] !== 'comment' ) ) return false; // unrecognised
					if ( ( array [1] !== 'comment' ) && ( array [2] !== 'absolute' ) && ( array [2] !== 'relative' ) ) return false; // unrecognised
					if ( array [1] === 'score' ) {
						if ( isNaN ( array [3] = parseFloat ( array [3] ) ) === true ) return false; // not a number
						array [3] = Math.round ( array [3] * 10 ) / 10;
						t ['action'] = array [2];
						t ['value'] = array [3];
					} else if ( array [1] === 'id' ) {
						if ( isNaN ( array [3] = parseInt ( array [3], 10 ) ) === true ) return false; // not a number
						if ( ( array [3] > 99 ) || ( array [3] < 0 ) ) return false; // out of range
						t ['action'] = array [2];
						t ['value'] = array [3];
					} else {
						t ['value'] = '';
						for ( var i = 2, l = array.length; i < l; i ++ ) {
							t ['value'] += array [i] + ' ';
						}
						t ['value'] = t ['value'].substr ( 0, t ['value'].length - 1 );
					}
					
					// build
					t ['target'] = array [1]; // what part of the change data do we want to edit?
					
					break;
			}
			
			// insert this event
			var useId = this.events.length;
			this.events [useId] = t;
			
			return true;
		}
		
		
		this.applyFilter = function ( change ) {
			this.activeChange = change;
			if ( this.checkConditions () === true ) { this.executeActions (); return this.activeChange; } else { return change; }
		}
		
		this.checkConditions = function () {
			for ( var i = 0, l = this.conditions.length; i < l; i ++ ) {
				var m = this.conditions [i];
				
				switch ( m ['type'] ) {
					case 'if':
						// convert targets to id codes
						var target;
						switch ( m['target'] ) {
							case 'summary': target = 9; break;
							case 'changesize': target = 8; break;
							case 'oldsize': target = 6; break;
							case 'newsize': target = 7; break;
							case 'tags': target = 10; break;
							case 'title': target = 0; break;
							case 'user': target = 1; break;
							case 'score': target = 4; break;
							default: return false; break;
						}
						
						var meTrue = m['negate'] ? false : true; var meFalse = ! meTrue, r = false;
						
						switch ( m['op'] ) {
							case 'regexmatch':
								var extractReg = /^\/(.+?)\/([igm]*)$/ig;
								var regMatch = extractReg.exec ( this.dataReplace ( m['value'] ) );
								var regTest = new RegExp ( regMatch [1], regMatch [2] );
								extractReg.lastIndex = 0;
								
								if ( this.activeChange [target].match ( regTest ) !== null ) { r = meTrue; } else { r = meFalse; }
								
								break;
								
							case 'contains':
								if ( this.activeChange [target].indexOf ( this.dataReplace ( m['value'] ) ) > -1 ) { r = meTrue; } else { r = meFalse; }
								break;
								
							case '==':
								if ( this.activeChange [target] == m['value'] ) { r = meTrue; } else { r = meFalse; }
								break;
								
							case '<':
								if ( this.activeChange [target] < parseFloat ( m['value'] ) ) { r = meTrue; } else { r = meFalse; }
								break;
								
							case '>':
								if ( this.activeChange [target] > parseFloat ( m['value'] ) ) { r = meTrue; } else { r = meFalse; }
								break;
								
							default: r = false; break;
						}
						
						break;
				}
				
				if ( r === false ) return false;
			}
			
			return true;
		}
		
		this.executeActions = function () {
			for ( var i = 0, l = this.events.length; i < l; i ++ ) {
				var m = this.events [i];
				
				switch ( m ['type'] ) {
					case 'set':
						if ( m['target'] === 'comment' ) {
							if ( typeof this.activeChange [11] !== 'string' ) this.activeChange [11] = '';
							this.activeChange [11] += ' ' + this.dataReplace ( m['value'] );
							
							break;
						}
					
						var existing = this.activeChange [4]; var scoreStr = existing.toString ( 10 );
						if ( scoreStr.indexOf ( '.' ) === -1 ) scoreStr += '.0';
						
						var score = scoreStr.substr ( 0, scoreStr.indexOf ( '.' ) + 2 );
						var id = scoreStr.substr ( scoreStr.indexOf ( '.' ) + 2 );
						if ( id == '' ) id = '00';
						if ( id.length === 1 ) id += '0';
						
						if ( m['target'] === 'score' ) {
							var newScore = parseFloat ( score );
							if ( m['action'] === 'relative' ) {
								newScore += parseFloat ( m['value'] );
								newScore = Math.round ( newScore * 10 ) / 10;
								newId = id;
							} else {
								newScore = parseFloat ( m['value'] );
								newScore = Math.round ( newScore * 10 ) / 10;
								newId = id;
							}
						} else {
							var newId = parseInt ( id, 10 );
							if ( m['action'] === 'relative' ) {
								newId += parseInt ( m['value'], 10 );
								if ( newId > 99 ) newId = 99;
								if ( newId < 0 ) newId = 0;
								newId = newId.toString ( 10 );
								
								newScore = Math.round ( parseFloat ( score ) * 10 ) / 10;
							} else {
								newId = parseInt ( m['value'], 10 );
								newId = newId.toString ( 10 );
								
								newScore = Math.round ( parseFloat ( score ) * 10 ) / 10;
							}
						}
						
						if ( ( parseInt ( newId, 10 ) < 10 ) && ( newId.substr ( 0, 1 ) !== '0' ) ) newId = '0' + newId;
						
						var final = parseFloat ( newScore + '' + newId );
						this.activeChange [4] = final;
						break;
						
					case 'blockfilters':
						this.blockFilters = true;
						break;
				}
			}
		}
		
		
		this.dataReplace = function ( string ) {
			var regTest = /%DATA([0-9A-Z]+)%/g, c = this.activeChange;
			regTest.lastIndex = 0;
			var o = string.replace ( regTest, function ( m, m2 ) { 
				if ( m2 === 'ME' ) {
					return mw.config.get('wgUserName');
				} else {
					if ( c[m2] === null ) return false;
					return c[m2].toString();
				}
			 } );
			 return o;
		}
		
		// on create
		this.parseSuccess = this.parseFilter ( filter ); // return parse success
		return true;
	}
	
	function iglooSecurity () {
		this.userActivity = true;
		this.pingFails = 0;
		
		if ( (window.location.href.indexOf('&sessionkey=') == -1) || (window.location.href.indexOf('::::') == -1) ) { igloo.shutdown('invalid session provided', 'tryagain'); this.shutdown = true; return false; }
			this.session = window.location.href.substr(window.location.href.indexOf('&sessionkey=') + 12);
			this.session = this.session.substr(0, this.session.length - 4);
		
		this.verifySession = function ( type ) {
			this.internalCounter = 0;
			
			if ( this.userActivity == true ) { var alive = '&keep-alive=true'; } else { var alive = '&keep-alive=false'; }
			this.userActivity = false;
			iglooImport( iglooSettings.remoteHost + 'main.php?action=verify&pingfails=' + this.pingFails + '&user=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&verify=' + this.session + alive, true, 'iglooVerify' );
			
			this.checkVerification ( type );
		}
		this.checkVerification = function ( type ) {
			if ( typeof iglooSessionVerified != 'undefined' ) if ( iglooSessionVerified != 'unknown' ) if ( iglooSessionVerified == 'ok' ) {
				this.pingFails = 0;
				iglooSessionVerified = 'unknown';
				this.verificationTimer = setTimeout("igloo.iglooSession.verifySession();", 45 * 1000);
				if ( type == 'initial' ) {
					igloo.launch();
				}
				return true;
			} else {
				igloo.shutdown('session invalid or expired', 'tryagain');
				return false;
			}
			
			if ( this.internalCounter >= iglooSettings.serverTimeout * 2 ) {
				this.pingFails ++;
				if ( type != 'initial' ) igloo.iglooStatus.addStatus ( '<strong>Server connect failed (action: verify; pingfails ' + this.pingFails + ')</strong>' );
				if ( this.pingFails >= iglooSettings.permitPingfails ) {
					igloo.shutdown ( 'verification failed - lost connection to iglooNet', 'retry' );
				}
				return false;
			} else {
				this.internalCounter ++;
				setTimeout("igloo.iglooSession.checkVerification('"+type+"');", 1000);
			}
		}
	}
	
	function iglooManageSettings () {
		// the iglooManageSettings module retrieves settings from the igloo server, and replaces the defaults where applicable.
		this.settingsEnabled = true;
		
		this.retrieve = function () {
			this.internalCounter = 0;
			
			iglooImport ( iglooSettings.remoteHost + 'main.php?action=settings&me=' + encodeURIComponent(mw.config.get('wgUserName')) + '&do=get&session=' + igloo.iglooSession.session, true, 'iglooNetSettings' );
		
			this.retrieveMain ();
		}
		this.retrieveMain = function () {
			thisSetting = false;
			if ( typeof iglooNetSettingsDone != 'undefined' ) if ( iglooNetSettingsDone != 'unknown' ) if ( iglooNetSettingsDone == 'ok' ) {
				// success
				this.overwritelocal ();
				this.managefilters ();
				
				// continue launch
				igloo.launch ();
				return true;
			} else {
				igloo.shutdown( 'session invalid or expired', 'tryagain' );
				return false;
			}
			
			if ( this.internalCounter >= ( iglooSettings.serverTimeout * 2 ) ) {
				igloo.shutdown( 'settings failed - lost connection to iglooNet', 'retry' );
				return false;
			} else {
				this.internalCounter ++;
				thisSetting = this;
				setTimeout( "if ( thisSetting ) { thisSetting.retrieveMain (); }", 500 );
			}
		}
		
		this.overwritelocal = function () {
			for ( i in iglooNetSettings ) {
				if ( iglooSettings [i] !== undefined ) {
					iglooSettings [i] = iglooNetSettings [i];
				}
			}
			
			return true;
		}
		
		this.managefilters = function () {
			for ( var i = 0, l = iglooSettings.filterList.length; i < l; i ++ ) {
				iglooSettings.filterList[i][4] = new iglooFilter ( iglooSettings.filterList[i][3] );
				if ( iglooSettings.filterList[i][4].parseSuccess === false ) { alert ( 'Warning: parse error; igloo filter failed to parse, global filter ID ' + iglooSettings.filterList[i][0] ); }
			}
			
			return true;
		}
		
		this.set = function ( setting, value ) {
			if ( this.settingsEnabled === true ) {
				iglooImport ( iglooSettings.remoteHost + 'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=' + encodeURIComponent ( setting ) + '&value=' + encodeURIComponent ( value ) + '&session=' + igloo.iglooSession.session, true, 'iglooNetDoSet' );
				this.settingsEnabled = false;
				return true;
			} else {
				alert ( 'Could not alter setting - previous requests are still being processed. Please wait, then try again.' );
				return false;
			}
		}
		
		this.freeSettings = function () {
			this.settingsEnabled = true;
		}
		
		
		this.start = function () {
			this.mainContent = '<div id="igloo-settings-tabs" style="width: 790px; height: 14px; padding-left: 10px; "></div><div id="igloo-settings-content" style="width: 800px; height: 385px; border-top: 1px solid #000;"></div>';
			if ( igloo.canDebug === true ) console.log ( 'igloo: prepped settings main' );
		}
		
		this.showdisplay = function () {
			// generate interface
			igloo.iglooPopup.show ( this.mainContent );
			
			// add tabs
			this.addtab ( 'info', 'user info' );
			this.addtab ( 'general', 'general' );
			this.addtab ( 'interface', 'interface' );
			//this.addtab ( 'actions', 'actions' );
			this.addtab ( 'filters', 'filters' );
			if ( iglooSettings.mesysop ) this.addtab ( 'admin', 'admin' );
			this.addtab ( 'close', 'close' );
			
			// default tab
			this.switchtab ( 'info' );
			
			// dynamic key blocking
			iglooSettings.dynamicBlockKeys = 'settings';
			return true;
		}
		
		this.hidedisplay = function () {
			igloo.iglooPopup.hide ();
			iglooSettings.dynamicBlockKeys = 'default';
			return true;
		}
		
		this.addtab = function ( tabid, tabtext ) {
			if ( ( ! tabid ) || ( ! tabtext ) ) return false;
			var tabscont = document.getElementById ( 'igloo-settings-tabs' );
			
			tabscont.innerHTML += '<div id="igloo-settings-tab-' + tabid + '" style="float: left; position: relative; top: 1px; font-size: 10px; height: 12px; width: 50px; border: 1px solid #000; text-align: center; cursor: pointer; margin-right: 10px;" onclick="igloo.iglooManageSettings.switchtab (\'' + tabid + '\');"> ' + tabtext + '</div>';
		
			return tabscont;
		}
		
		this.genFilterText = function () {
			var filterText = '';
			for ( var i = 0, l = iglooSettings.filterList.length; i < l; i ++ ) {
				var t = iglooSettings.filterList [i], optionsString = 'this filter has a problem';
				
				var disp = t[3].replace ( /\\/g, '\\\\' );
				disp = disp.replace ( /\n/g, '\\n' );
				disp = disp.replace ( /\'/g, '\\\'' );
										
				//this.disEnString = new Array ();
				//this.disEnString[i] = ( t[1] === true ) ? 'iglooImport ( \''+iglooSettings.remoteHost+'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=disablefilter&value='+t[0]+'\', true, \'iglooFilter\' ); iglooSettings.filterList['+i+'][1] = false;' : 'iglooImport ( \''+iglooSettings.remoteHost+'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=enablefilter&value='+t[0]+'\', true, \'iglooFilter\' ); iglooSettings.filterList['+i+'][1] = true;';
				var disEnString = ( t[1] === true ) ? 'Disable' : 'Enable';
				//disEnString = disEnString.replace ( /'/g, '\\\'' );
				
				if ( t[2] !== 'default' ) {
					var saveString = '';
					optionsString = '<span style="cursor: pointer;" onclick="var d = document.getElementById (\'igloo-filter-details\'); d.innerHTML = \'<span id=\\\'igloo-filter-current-id\\\' style=\\\'display: none;\\\'>'+i+':'+t[0]+'</span><textarea id=\\\'igloo-filter-text\\\' style=\\\'width: 540px; height: 290px;\\\'>'+disp+'</textarea><br /><div><input disabled id=\\\'igloo-save-filter\\\' type=\\\'button\\\' value=\\\'Save filter changes\\\' style=\\\'width: 260px; margin-left: 5px;\\\' /><input id=\\\'igloo-toggle-filter-disable\\\' type=\\\'button\\\' value=\\\'' + disEnString + ' this filter\\\' style=\\\'width: 260px; margin-left: 10px;\\\' /></div>\'; igloo.iglooManageSettings.attachfilterbuttons ( '+i+' );" onmouseover="this.style.textDecoration=\'underline\';" onmouseout="this.style.textDecoration=\'none\';">edit</span>';
					optionsString += ' | <span style="cursor: pointer;" onclick="iglooImport ( \'' + iglooSettings.remoteHost + 'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=deletefilter&value=' + t[0] + '&session=' + igloo.iglooSession.session + '\', true, \'iglooFilter\' ); iglooSettings.filterList.splice ( ' + i + ', 1 ); igloo.iglooManageSettings.switchtab ( \'filters\' );" onmouseover="this.style.textDecoration=\'underline\';" onmouseout="this.style.textDecoration=\'none\';">delete</span>';
				
				} else {
					optionsString = '<span style="cursor: pointer;" onclick="var d = document.getElementById (\'igloo-filter-details\'); d.innerHTML = \'<span id=\\\'igloo-filter-current-id\\\' style=\\\'display: none;\\\'>'+i+':'+t[0]+'</span><textarea disabled style=\\\'width: 540px; height: 290px;\\\'>'+disp+'</textarea><br /><div><input type=\\\'button\\\' disabled value=\\\'You cannot edit this filter\\\' style=\\\'width: 260px; margin-left: 5px;\\\' /><input id=\\\'igloo-toggle-filter-disable\\\' type=\\\'button\\\' value=\\\'' + disEnString + ' this filter\\\' style=\\\'width: 260px; margin-left: 10px;\\\' /></div>\'; igloo.iglooManageSettings.attachfilterbuttons ( '+i+' );" onmouseover="this.style.textDecoration=\'underline\';" onmouseout="this.style.textDecoration=\'none\';">view filter (cannot edit)</span>';
				}
				
				var col = ( t[1] === true ) ? '' : 'background-color: #ffbbbb;';
				filterText += '<li style="'+col+' margin-left: -4px; list-style-type: none; list-style-image: none; marker-offset: 0px;">';
				filterText += '- filter ' + t[0] + ' (' + optionsString + ')';
				filterText += '</li>';
			}
			if ( i === 0 ) filterText = '<li style="margin-left: 0px; padding-left: 4px; list-style-type: none; list-style-image: none; marker-offset: 0px;">no filters</li>';
			
			return filterText;
		}
		
		this.togglefilterenabled = function ( to ) {
			var t = document.getElementById ( 'igloo-toggle-filter-disable' ), id = document.getElementById ( 'igloo-filter-current-id' ).innerHTML.split ( ':' );
			id = parseInt ( id[0], 10 );
			
			if ( to === 'enabled' ) {
				iglooImport ( iglooSettings.remoteHost+'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=enablefilter&value=' + iglooSettings.filterList[id][0] + '&session=' + igloo.iglooSession.session, true, 'iglooFilter' ); 
				iglooSettings.filterList[id][1] = true;
				t.value = 'Disable this filter'; 
				t.onclick = function () { igloo.iglooManageSettings.togglefilterenabled ( 'disabled' ); document.getElementById ( 'igloo-filter-list' ).innerHTML = igloo.iglooManageSettings.genFilterText (); }
			} else {
				iglooImport ( iglooSettings.remoteHost+'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=disablefilter&value=' + iglooSettings.filterList[id][0] + '&session=' + igloo.iglooSession.session, true, 'iglooFilter' ); 
				iglooSettings.filterList[id][1] = false; 
				t.value = 'Enable this filter'; 
				t.onclick = function () { igloo.iglooManageSettings.togglefilterenabled ( 'enabled' ); document.getElementById ( 'igloo-filter-list' ).innerHTML = igloo.iglooManageSettings.genFilterText (); }
			}
			
			return true;
		}
		
		this.attachfilterbuttons = function ( id ) {
			if ( id !== 'new' ) {
				var t = document.getElementById ( 'igloo-toggle-filter-disable' );
				if ( iglooSettings.filterList[id][1] === true ) {
					// enabled, so disable
					t.onclick = function () { igloo.iglooManageSettings.togglefilterenabled ( 'disabled' ); document.getElementById ( 'igloo-filter-list' ).innerHTML = igloo.iglooManageSettings.genFilterText (); }
				} else {
					// disabled, so enable
					t.onclick = function () { igloo.iglooManageSettings.togglefilterenabled ( 'enabled' ); document.getElementById ( 'igloo-filter-list' ).innerHTML = igloo.iglooManageSettings.genFilterText (); } 
				}
			}
				
			if ( document.getElementById ( 'igloo-filter-text' ) !== null ) {
				t = document.getElementById ( 'igloo-filter-text' );
				
				t.onkeyup = function () { 
					var t2 = new iglooFilter ( document.getElementById ( 'igloo-filter-text' ).value );
					if ( t2.parseSuccess === false ) {
						var t3 = document.getElementById ( 'igloo-save-filter' );
						t3.disabled = true;
						t3.value = 'Parse error: filter is invalid';
					} else {
						var t3 = document.getElementById ( 'igloo-save-filter' );
						t3.disabled = false;
						
						if ( document.getElementById ( 'igloo-filter-current-id' ).innerHTML.indexOf ( 'new' ) > -1 ) {
							t3.value = 'Create new filter';
						} else {
							t3.value = 'Save filter changes';
						}
					}
				}
			}
			
			if ( document.getElementById ( 'igloo-save-filter' ) !== null ) {
				t = document.getElementById ( 'igloo-save-filter' );
				
				t.onclick = function () {
					this.disabled = true; 
					
					var id = document.getElementById ( 'igloo-filter-current-id' ).innerHTML.split ( ':' );
					var data = document.getElementById ( 'igloo-filter-text' ).value;
					
					// if new
					if ( id[1] === 'new' ) {
						var t = [], n = true;
						t [0] = igloo.iglooNet.genCode ( 5 );
						t [1] = true;
						t [2] = mw.config.get('wgUserName');
						t [3] = data;
						t [4] = new iglooFilter ( t [3] );
						
						id [1] = t [0];
						
						iglooSettings.filterList.push ( t );
					} else {
						var t = iglooSettings.filterList [ parseInt ( id[0], 10 ) ], n = false;
						t [3] = data;
						t [4] = new iglooFilter ( t [3] );
						iglooSettings.filterList [ parseInt ( id[0], 10 ) ] = t;
					}
					
					iglooImport ( iglooSettings.remoteHost+'main.php?action=settings&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&do=set&setting=editfilter&id=' + id [1] + '&value=' + encodeURIComponent ( data ) + '&session=' + igloo.iglooSession.session, true, 'iglooFilter' );
					if ( n === true ) { igloo.iglooManageSettings.switchtab ( 'filters' ); } else { document.getElementById ( 'igloo-filter-list' ).innerHTML = igloo.iglooManageSettings.genFilterText (); }
				}
			}
			
		}
		
		this.switchtab = function ( tabid ) {
			if ( ! tabid ) { throw 'igloo: unexpected settings call, tab is missing'; return false; }
			var tabcont = document.getElementById ( 'igloo-settings-content' ), v;
			
			switch ( tabid ) {
				case 'info':
					tabcont.innerHTML = ''; // blank
					tabcont.innerHTML += '<div style="padding: 10px;">Welcome to the igloo settings panel. From here, you can update your igloo settings - there is no need to save your changes or restart igloo as any alterations will take place immediately. igloo currently has the following data regarding your account:<br /><br />- username: ' + mw.config.get('wgUserName') + '<br />- recognised usergroup: ' + iglooSettings.userGroup + '<br />- igloo user flags: ' + iglooSettings.iglooFlags + '<br />- igloo trust status: N/A<br />- total sessions: ' + iglooSettings.totalSessions + '<br />- last connected from: ' + iglooSettings.lastConnectIp + '<br />- last connected: ' + iglooSettings.lastConnectTime + '<br /></div>';
					break;
				
				case 'general':
					tabcont.innerHTML = ''; // blank
					var cont = '';
					cont += '<div style="padding: 10px;">Change general igloo settings here.<br /><table style="background-color: #ccccff; border: none; margin-top: 5px; margin-left: 15px; width: 550px;">'
						v = iglooSettings.updateTime;
						cont += '<tr style="margin-left: 15px;"><td>Update time</td><td><input onchange="if ( isNaN ( parseInt ( this.value ) ) === true ) this.value = iglooSettings.updateTime;" onblur="if ( igloo.iglooManageSettings.set ( \'updateTime\', this.value ) === false ) { this.value = iglooSettings.updateTime; } else { iglooSettings.updateTime = parseInt ( this.value ); }" type="text" value="'+v+'" /></td></tr>';
						v = iglooSettings.updateQuantity;
						cont += '<tr style="margin-left: 15px;"><td>Update quantity</td><td><input onchange="if ( isNaN ( parseInt ( this.value ) ) === true ) this.value = iglooSettings.updateQuantity;" onblur="if ( igloo.iglooManageSettings.set ( \'updateQuantity\', this.value ) === false ) { this.value = iglooSettings.updateQuantity; } else { iglooSettings.updateQuantity = parseInt ( this.value ); }" type="text" value="'+v+'" /></td></tr>';
						v = iglooSettings.updateLimit;
						cont += '<tr style="margin-left: 15px;"><td>Update list limit</td><td><input onchange="if ( isNaN ( parseInt ( this.value ) ) === true ) this.value = iglooSettings.updateLimit;" onblur="if ( igloo.iglooManageSettings.set ( \'updateLimit\', this.value ) === false ) { this.value = iglooSettings.updateLimit; } else { iglooSettings.updateLimit = parseInt ( this.value ); }" type="text" value="'+v+'" /></td></tr>';
						v = iglooSettings.promptRevertSelf ? 'checked' : '';
						cont += '<tr style="margin-left: 15px;"><td>Prompt on self revert</td><td><input onchange="if ( igloo.iglooManageSettings.set ( \'promptRevertSelf\', this.checked ) === false ) this.checked = ! this.checked;" type="checkbox" '+v+' /></td></tr>';
						v = iglooSettings.profFilter ? 'checked' : '';
						cont += '<tr style="margin-left: 15px;"><td>Enable profanity highlighting</td><td><input onchange="if ( igloo.iglooManageSettings.set ( \'profFilter\', this.checked ) === false ) this.checked = ! this.checked;" type="checkbox" '+v+' /></td></tr>';
						
					cont += '</table></div>';
					tabcont.innerHTML = cont;
					break;
					
				case 'interface':
					tabcont.innerHTML = ''; // blank
					var cont = '';
					cont += '<div style="padding: 10px;">Change igloo interface settings here.<br /><table style="background-color: #ccccff; border: none; margin-top: 5px; margin-left: 15px; width: 550px;">'
						v = iglooSettings.preloadInterface ? 'checked' : '';
						cont += '<tr style="margin-left: 15px;"><td>Preload interface elements</td><td><input onchange="if ( igloo.iglooManageSettings.set ( \'preloadInterface\', this.checked ) === false ) this.checked = ! this.checked;" type="checkbox" '+v+' /></td></tr>';
						v = iglooSettings.diffFontSize;
						cont += '<tr style="margin-left: 15px;"><td>Diff font size (px)</td><td><input onchange="if ( parseInt ( this.value ) == NaN ) this.value = iglooSettings.diffFontSize;" onblur="if ( igloo.iglooManageSettings.set ( \'diffFontSize\', this.value ) === false ) { this.value = iglooSettings.diffFontSize; } else { iglooSettings.diffFontSize = parseInt ( this.value ); }" type="text" value="'+v+'" /></td></tr>';
						v = iglooSettings.enableFeedColour ? 'checked' : '';
						cont += '<tr style="margin-left: 15px;"><td>Enable feed colouring</td><td><input onchange="if ( igloo.iglooManageSettings.set ( \'enableFeedColour\', this.checked ) === false ) { this.checked = ! this.checked; } else { iglooSettings.enableFeedColour = this.checked; }" type="checkbox" '+v+' /></td></tr>';
						v = iglooSettings.histWinTimeout;
						cont += '<tr style="margin-left: 15px;"><td>History window timeout</td><td><input onblur="if ( igloo.iglooManageSettings.set ( \'histWinTimeout\', this.value ) === false ) { this.value = iglooSettings.histWinTimeout; } else { iglooSettings.histWinTimeout = parseFloat ( this.value ); }" type="text" value="'+v+'" /></td></tr>';
					
					cont += '</table></div>';
					tabcont.innerHTML = cont;
					break;
				
				case 'admin':
					tabcont.innerHTML = ''; // blank
					var cont = '';
					cont += '<div style="padding: 10px;">This is the igloo admin settings panel - here, you can change settings related to performing administrative actions using igloo.<br /><table style="background-color: #ccccff; border: none; margin-top: 5px; margin-left: 15px; width: 550px;">'
						v = iglooSettings.preloadInterface ? 'checked' : '';
						cont += '<tr style="margin-left: 15px;"><td>Action after final warning</td><td><select onchange="if ( this.value == \'select\' ) return false; igloo.iglooManageSettings.set ( \'blockAction\', this.value );"><option value="select">Select...</option><option value="auto">Use autoblock</option><option value="standard">Choose block options</option><option value="report">Report to AIV</option></select></td></tr>';
						//v = iglooSettings.enableFeedColour ? 'checked' : '';
						//cont += '<tr style="margin-left: 15px;"><td>Enable feed colouring</td><td><input onchange="if ( igloo.iglooManageSettings.set ( \'enableFeedColour\', this.checked ) === false ) { this.checked = ! this.checked; } else { iglooSettings.enableFeedColour = this.checked; }" type="checkbox" '+v+' /></td></tr>';
						//v = iglooSettings.histWinTimeout;
						//cont += '<tr style="margin-left: 15px;"><td>History window timeout</td><td><input onblur="if ( igloo.iglooManageSettings.set ( \'histWinTimeout\', this.value ) === false ) { this.value = iglooSettings.histWinTimeout; } else { iglooSettings.histWinTimeout = parseFloat ( this.value ); }" type="text" value="'+v+'" /></td></tr>';
					
					cont += '</table></div>';
					tabcont.innerHTML = cont;
					break;
					
				case 'filters':
					// build filterText
					var filterText = this.genFilterText ();
					
					// build display
					var cont = ''; // blank
					cont += '<div style="padding: 10px;">To create a new filter, click the \'new filter\' button. Be aware that poorly designed filters can have an adverse effect on performance. You cannot edit the default filters.<br /><table style="background-color: #ccccff; border: 1px solid #000; margin-top: 5px; margin-left: 15px; width: 750px;">'
					cont += '<tr><td valign="top" style="width: 150px; height: 330px; border-right: 1px solid #000;">';
						cont += '<input type="button" style="width: 200px;" value="New Filter" onclick="var d = document.getElementById (\'igloo-filter-details\'); d.innerHTML = \'<span id=\\\'igloo-filter-current-id\\\' style=\\\'display: none;\\\'>0:new</span><textarea id=\\\'igloo-filter-text\\\' style=\\\'width: 540px; height: 290px;\\\'>// Type filter details below.</textarea><br /><div><input disabled id=\\\'igloo-save-filter\\\' type=\\\'button\\\' value=\\\'Create new filter\\\' style=\\\'width: 530px; margin-left: 5px;\\\' /></div>\'; igloo.iglooManageSettings.attachfilterbuttons ( \'new\' );" /><br /><ul style="overflow: auto;" id="igloo-filter-list">' + filterText + '</ul>';
					cont += '</td><td valign="top" id="igloo-filter-details" style="width: 600px; padding: 0px;">';
						cont += '<div style="padding: 10px;">Select \'edit\' from a filter on the left to begin...</div>';
					cont += '</td></tr></table></div>';
					tabcont.innerHTML = cont;
					break;
					
				case 'close':
					this.hidedisplay ();
					break;
				
				default:
					throw 'igloo: unexpected settings call, tab content undefined';
					break;
			}
		}
	}
	
	
	
	
	function iglooChanges () {
		// the iglooChanges object is the object that retrieves, sorts, manages and displays changes from Wikipedia
		this.recentChanges = [];	// main array, holding the changes
		this.viewedChanges = [];	// main array, holding diffs that should NOT be displayed (we've seen them already)
		this.serverDetails = [];	// main array, holding details from the server regarding users and pages
		
		this.start = function () {
			// start the changed program
			this.startInterface ();
			this.startTicker ();
		}
		
		this.startTicker = function () {
			// perform the refresh ticks that grab info from the server
			this.updateContent ();
			this.refreshTicker = setInterval ( "igloo.iglooChanges.updateContent();", iglooSettings.updateTime * 1000 );
		}
		
		this.startInterface = function () {
			// this creates the basic interface into which changes are placed when they have been received.
			// essentially, it is a div that will contain clickable list elements.
			
			this.changeDisplay = new wa_window ( igloo.iglooInterface );
			this.changeDisplay.win_width = 190;
			this.changeDisplay.win_height = parseInt ( igloo.iglooInterface.win_obj.style.height ) - 50;
			this.changeDisplay.win_bg = '#ffffff';
			this.changeDisplay.win_bd_rt = '1px solid #000000'; this.changeDisplay.win_bd_bt = '1px solid #000000';
			this.changeDisplay.win_padding = 0;
			this.changeDisplay.win_content = '<ul style="font-size: 10px; width: 100%; height: 100%; margin: 0px; padding: 0px; overflow-x: hidden; overflow-y: auto;" id="iglooChangesList"></ul>';
			this.changeDisplay.applyAll ();
			
			this.logoDisplay = new wa_window ( igloo.iglooInterface );
			this.logoDisplay.win_top = parseInt ( igloo.iglooInterface.win_obj.style.height ) - 50;
			this.logoDisplay.win_width = 190;
			this.logoDisplay.win_height= 50;
			this.logoDisplay.win_bg = '#ffffff';
			this.logoDisplay.win_bd_tp = '1px solid #000000'; 
			this.logoDisplay.win_padding = 0;
			this.logoDisplay.win_content = '<img style="position: relative; top: -15px; left: 20px;" src="' + iglooSettings.remoteHost + 'images/igloo-logo.png" />';
			this.logoDisplay.applyAll ();
		}
		
		this.addToViewed = function ( revid ) {
			// mark an oldid as viewed so we do not display it more than once in the display window.
			var sizeLimit = 30;
			
			this.viewedChanges.push ( revid );
			if ( this.viewedChanges.length > sizeLimit ) this.viewedChanges.shift ();
			
			return true;
		}
		
		this.markViewed = function ( revid ) {
			this.addToViewed ( revid );
			
			// next, remove it from the recentChanges array, and redisplay that.
			for ( var i = 0; i < this.recentChanges.length; i ++ ) {
				if ( this.recentChanges[i][2] == revid ) {
					this.recentChanges.splice(i, 1);
					var change = i;
				}
			}
			this.displayChanges ();
		}
		
		this.updateContent = function () {
			var update = new iglooUpdateChanges ();
		}
		
		this.displayChanges = function () {
			var visibleChangeOutput = '';
			for ( var i = 0; i < this.recentChanges.length; i ++ ) {
				if ( typeof this.recentChanges[i][4] == 'undefined' ) this.recentChanges[i][4] = 0.5;
				
				var backgroundColour = igloo.iglooNet.colourScore ( this.recentChanges[i][4] );
				if ( this.recentChanges[i][3] == true ) { var newPage = '<span style="font-weight: bold; font-size: 1.2em;">N</span> '; } else { var newPage = ''; }
				visibleChangeOutput += '<li onclick="igloo.iglooControls.action_loadFromFeed(\''+this.recentChanges[i][0].replace (/'/g, '\\\'')+'\', \''+this.recentChanges[i][1].replace (/'/g, '\\\'')+'\', \''+this.recentChanges[i][2].replace (/'/g, '\\\'')+'\', '+this.recentChanges[i][3]+', '+this.recentChanges[i][4]+', \''+this.recentChanges[i][11].replace (/'/g, '\\\'')+'\');" onmouseover="this.style.backgroundColor = \'#dddddd\';" onmouseout="this.style.backgroundColor = \''+backgroundColour+'\';" style="cursor: pointer; width: 186px; padding: 2px; border-bottom: 1px solid #000000; list-style-type: none; list-style-image: none; marker-offset: 0px; background-color: '+backgroundColour+';">'+newPage+this.recentChanges[i][0]+'</li>';
			}
			document.getElementById('iglooChangesList').innerHTML = visibleChangeOutput;
		}
		
		this.destroy = function () { // calling this will destroy the interface and connections of this object.
			this.changeDisplay.hide ();
			this.logoDisplay.hide ();
			if ( typeof this.refreshTicker != 'undefined' ) clearInterval ( this.refreshTicker );
		}
	}
	function iglooUpdateChanges () {
		// the iglooUpdate object handles making a request to the server, and returning an array of data.
		this.updateCounter 	= 0;
		
		this.update = function () {
			switch ( this.updateCounter ) {
				default: case 0:
					// first, get some recent changes
					var aRequest = this;
					
					this.ajax 				= new wa_ajaxcall ();
					this.ajax.requestUrl 	= iglooSettings.rootApi + '?format=xml&action=query&list=recentchanges&rcprop=user|title|ids|sizes|comment|tags&rctype=edit|new&rclimit=' + iglooSettings.updateQuantity;
					this.ajax.doRequest ( function () {
												 		aRequest.updateCounter ++;
														aRequest.update ();
												 });
					break;
					
				case 1:
					// recent changes are held such that:
						// [0] = title
						// [1] = user
						// [2] = oldid
						// [3] = new page?
						// [4] = user vandal score
						// [5] = priority note - a priority note is assigned to pages that have priority for some reason. These will be displayed at the top of the list, regardless of the score of the user.
				
						// [6] = old length
						// [7] = new length
						// [8] = changed length
						// [9] = comment
						// [10] = tag string
						
						// [11] - igloo change comments (can be set using filters)
						
					this.tempChanges = [];
					var data = this.ajax.response.getElementsByTagName ( 'rc' );
					
					for (var i = 0; i < data.length; i++) {
						this.tempChanges[i] 	= [];
						
						this.tempChanges[i][0]	= data[i].getAttribute ( 'title' );
						this.tempChanges[i][1]	= data[i].getAttribute ( 'user' );
						this.tempChanges[i][2]	= data[i].getAttribute ( 'revid' );
						if ( data[i].getAttribute ( 'type' ) == 'edit' ) {
							this.tempChanges[i][3]	= false;
						} else {
							this.tempChanges[i][3]	= true;
						}
						
						this.tempChanges[i][4]	= 0;
						this.tempChanges[i][5]	= 0;
						this.tempChanges[i][6]	= parseInt ( data[i].getAttribute ( 'oldlen' ) );
						this.tempChanges[i][7]	= parseInt ( data[i].getAttribute ( 'newlen' ) );
						this.tempChanges[i][8]	= this.tempChanges[i][7] - this.tempChanges[i][6];
						this.tempChanges[i][9]	= data[i].getAttribute ( 'comment' );
						
						var t = data[i].childNodes[0].childNodes, s = '::';
						for ( var j = 0, l = t.length; j < l; j ++ ) {
							s += t[j].childNodes[0].wholeText + '::';
						}
						this.tempChanges[i][10] = s;
						
						this.tempChanges[i][11] = '';
					}
					
					this.updateCounter ++;
					this.update ();
					
					break;
					
				case 2:
					// we have the details - build the temporary changes array
					var score = [];
					for ( var i = 0; i < this.tempChanges.length; i ++ ) {
						// if we have special data on this user, such as a priority score, append it here.
						
						// check for pages
						score.length = 0;
						score = igloo.iglooNet.scoreObject ( this.tempChanges[i][0] );
						
						// check for users
						if ( score[0] === false ) {
							score.length = 0;
							score = igloo.iglooNet.scoreObject ( this.tempChanges[i][1] );
						}
						
						// backup
						if ( score[0] === false ) score[0] = iglooSettings.defaultUserScore;
						
						this.tempChanges[i][4] = score[0];
						this.tempChanges[i][5] = score[1];
						
						// apply any relevant filters to the changes we've loaded
						if ( iglooSettings.enableFilters === true ) {
							for ( var j = 0, l = iglooSettings.filterList.length; j < l; j ++ ) {
								var t = iglooSettings.filterList [j];
								if ( ( t[1] === false ) || ( t[4].parseSuccess === false ) ) continue;
								this.tempChanges[i] = t[4].applyFilter ( this.tempChanges[i] );
								if ( t[4].blockFilters === true ) {
									t[4].blockFilters = false;
									break;
								}
							}
						}
					}
					
					
					// we want to remove all duplicates from the temp array, by copying them into
					// another temp array only if they are not already there.
					var tempArray = [];
					for ( var i = 0; i < this.tempChanges.length; i ++ ) {
						var t = false;
						for ( var j = 0; j < tempArray.length; j ++ ) {
							if ( this.tempChanges[i][0] == tempArray[j][0] ) {
								t = true; break;
							}
						}
						if ( t === false ) tempArray.push ( this.tempChanges[i] );
					}
					
					// now, remove any elements that are on the viewed list that we do not wish to display to the user OR that were made by our user
					var limit = tempArray.length
					for (var i = 0; i < limit; i ++) {
						if ( ( in_array ( tempArray[i][2], igloo.iglooChanges.viewedChanges ) == true ) || ( ( tempArray[i][1] == mw.config.get('wgUserName') ) && ( iglooSettings.hideOwn == true ) ) ) {
							tempArray.splice ( i, 1 ); i --; limit --;
						} else {
							if ( ( tempArray[i][0] == igloo.iglooDiff.currentDiff[0] ) && ( igloo.iglooActions.reversionEnabled !== 'pause' ) ) {
								// if this is the same title as the page we're viewing, update the display
								igloo.iglooChanges.addToViewed ( tempArray[i][2] );
								igloo.iglooDiff.display ( tempArray[i][0], tempArray[i][1], tempArray[i][2], tempArray[i][3], tempArray[i][4], tempArray[i][11] );
								igloo.iglooDiff.haschanged = true;
								tempArray.splice ( i, 1 ); i --; limit --;
							}
						}
					}
					
					// we now have the full array of scores
					// go through the main recent changes array, check if there are any pages that match 
					// here and there, delete them from the main array, then merge this one into the main one
					
					var i = 0;
					while ( typeof igloo.iglooChanges.recentChanges[i] != 'undefined' ) {
						for ( var j = 0; j < tempArray.length; j ++ ) {
							if ( typeof igloo.iglooChanges.recentChanges[i] == 'undefined' )  break;
							if ( igloo.iglooChanges.recentChanges[i][0] == tempArray[j][0] ) {
								igloo.iglooChanges.recentChanges.splice ( i, 1 );
								i --;
							}
						}
						i ++;
					}
					
					// recent changes array now holds all the data it did before EXCEPT titles that conflict with the new changes
					// merge the new changes into the old ones
					igloo.iglooChanges.recentChanges = tempArray.concat ( igloo.iglooChanges.recentChanges );
					
					// now, sort the array based on the float values :)
					igloo.iglooChanges.recentChanges = sort_array_multi ( igloo.iglooChanges.recentChanges, 4, 'descending' );
					
					// check that we aren't over the hard limit for number of changes. If we are, remove those least likely to be vandalism.
					if ( igloo.iglooChanges.recentChanges.length > iglooSettings.updateLimit ) {
						igloo.iglooChanges.recentChanges = igloo.iglooChanges.recentChanges.splice ( 0, iglooSettings.updateLimit ); }
					
					// now, we have to display the changes to the user! 
					igloo.iglooChanges.displayChanges ();
					
					break;
			}
		}
		
		this.update();
	}
	
	function iglooDiff () {
		// the iglooDiff module is the object that displays and styles Wikiepdia diffs in the main window
		this.viewingDiff	= false;
		this.previousDiff 	= [];
		this.currentDiff 	= [];
		this.displayCounter = 0;
		
		this.displayHistory = [];
		this.historyPosition = 0;
		this.canAddToHist = true;
		
		this.start = function () { // this functions creates the diff window for future use, and displays the latest news in it :)
			this.startInterface ();
			this.displayWelcome ();
			this.warnedParser = false;
		}
		
		this.startInterface = function () {
			// this creates the basic interface into which changes are placed when they have been received.
			// essentially, it is a div that will contain clickable list elements.
			
			this.scoreDisplay = new wa_window ( igloo.iglooInterface );
			this.scoreDisplay.win_left = 190;
			this.scoreDisplay.win_top = 79;
			this.scoreDisplay.win_width = parseInt ( igloo.iglooInterface.win_obj.style.width ) - 200;
			this.scoreDisplay.win_height= 11;
			this.scoreDisplay.win_bg = '#ffffff';
			this.scoreDisplay.win_bd_bt = '1px solid #000000';
			this.scoreDisplay.win_padding = 2;
			this.scoreDisplay.win_fontsize = 10;
			this.scoreDisplay.win_content = 'You are not yet viewing a diff';
			this.scoreDisplay.applyAll ();
			
			this.diffDisplay = new wa_window ( igloo.iglooInterface );
			this.diffDisplay.win_left = 190;
			this.diffDisplay.win_top = 95;
			this.diffDisplay.win_width = parseInt ( igloo.iglooInterface.win_obj.style.width ) - 200;
			this.diffDisplay.win_height= parseInt ( igloo.iglooInterface.win_obj.style.height ) - 255;
			this.diffDisplay.win_bg = '#ededff';
			this.diffDisplay.win_padding = 5;
			this.diffDisplay.win_content = '<div id="iglooDiffDisplay" style="width: 100%; height: 100%; overflow: auto;">DIFFS INIT</div>';
			this.diffDisplay.applyAll ();
		}
		
		this.displayWelcome = function () {
			// this function specifically displays the welcome message in the diff window
			var welcomeRequest = new wa_mediawikiApi ();
			welcomeRequest.onCompleteAction = function ( data ) { 
																	var regTest = /welcome:(.+?);;/i, o;
																	regResult = regTest.exec( this.data['page']['revisions'][0]['content'] );
																	o = regResult[1].replace ( '%CURRENTVERSION%', iglooSettings.version );
																	document.getElementById( 'iglooDiffDisplay' ).innerHTML = o;
															};
			welcomeRequest.getPage ( iglooSettings.localBase + 'config', 1, 'content' );
		}
		
		this.display = function ( title, user, revisionId, newPage, diffScore, iglooNetComment ) { // this function displays the diff of an edit on the screen for the user
			// the user has displayed activity by initiating a diff display.
			this.viewingDiff = true;
			this.haschanged = false;
			igloo.iglooSession.userActivity = true;
		
			// update the holding variables. Some of these may be absent based on the origin of the display call.
			if ( typeof this.currentDiff[0] != 'undefined' ) { this.previousDiff[0] = this.currentDiff[0]; this.previousDiff[1] = this.currentDiff[1]; this.previousDiff[2] = this.currentDiff[2]; this.previousDiff[3] = this.currentDiff[3]; this.previousDiff[4] = this.currentDiff[4]; this.previousDiff[5] = this.currentDiff[5]; }
			this.currentDiff[0] = title; this.currentDiff[1] = user; this.currentDiff[2] = revisionId; this.currentDiff[3] = newPage; this.currentDiff[4] = diffScore; this.currentDiff[5] = iglooNetComment;
			
			// add to history
			this.manageHist ();
			
			// investigate
			if ( this.currentDiff [1] ) igloo.iglooNet.investigate ( this.currentDiff [1] );
			
			// decide upon the request to perform - is this a specific request, or a general one?
			if ( ! revisionId ) {
				revisionId = false;
				var url = mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=' + encodeURIComponent ( title ) + '&diff=cur&diffonly=true&redirect=no';
			} else {
				var url = mw.config.get('wgServer') + mw.config.get('wgScript') + '?oldid=' + revisionId + '&diff=prev&diffonly=true&redirect=no';
			}
			
			var aDisplay = this;
			this.ajax = new wa_ajaxcall ();
			this.ajax.requestUrl = url;
			this.ajax.doRequest ( function () {  
										aDisplay.parseScreenScrape ();
									});
		}
		
		this.manageHist = function () {
			// add the PREVIOUS page to the display history.
			if ( ( this.currentDiff[0] ) && ( this.currentDiff[1] ) && ( this.currentDiff[2] ) ) if ( this.canAddToHist == true ) {
				// first, remove any history between the current position and 0.
				if ( this.historyPosition > 0 ) {
					var temp = [];
					temp.length = 0;
					temp = this.displayHistory.slice(this.historyPosition);
					this.displayHistory.length = 0;
					this.displayHistory = temp;
					this.historyPosition = 0;
				}
				
				// then add the page
				var histEntry = [];
				histEntry.length = 0;
				histEntry[0] = this.currentDiff[0]; histEntry[1] = this.currentDiff[1]; histEntry[2] = this.currentDiff[2];
				
				this.displayHistory.unshift ( histEntry );
				if ( this.displayHistory > iglooSettings.maxHistory ) this.displayHistory.length = iglooSettings.maxHistory;
			}
			this.canAddToHist = true;
			
			// handle greying of invalid options
			var backButton 		= document.getElementById('igloo-buttons-back-b');
			var forwardButton 	= document.getElementById('igloo-buttons-forward-b');
			var backUrl			= '' + iglooSettings.remoteHost + 'images/igloo-back';
			var forwardUrl		= '' + iglooSettings.remoteHost + 'images/igloo-forward';
			var grey 			= '-grey';
			var filetype		= '.png';
			
			if (this.displayHistory.length <= 1) { backButton.src = backUrl + grey + filetype; forwardButton.src = forwardUrl + grey + filetype; }
			else if ( (this.displayHistory.length > 1) && (this.historyPosition == 0) ) { backButton.src = backUrl + filetype; forwardButton.src = forwardUrl + grey + filetype; }
			else if ( (this.displayHistory.length > 1) && (this.historyPosition == (this.displayHistory.length - 1)) ) { backButton.src = backUrl + grey + filetype; forwardButton.src = forwardUrl + filetype; }
			else { backButton.src = backUrl + filetype; forwardButton.src = forwardUrl + filetype; }
		}
		
		this.goBack = function ( count ) {
			if ( this.displayHistory.length <= 0 ) return false;
			if ( ! count ) count = 1;
			if ( ( this.historyPosition + count ) > this.displayHistory.length ) count = this.displayHistory.length;
			
			this.historyPosition += count;
			var doView = this.displayHistory [this.historyPosition];
			
			this.canAddToHist = false;
			this.display ( doView[0], doView[1], doView[2] );
			return true;
		}
		
		this.goForward = function(count) {
			if (this.historyPosition <= 0) return false;
			if (!count) count = 1;
			
			if ( (this.historyPosition - count) < 0 ) { this.historyPosition = 0; } else { this.historyPosition -= count; }
			var doView = this.displayHistory[this.historyPosition];
			
			this.canAddToHist = false;
			this.display(doView[0], doView[1], doView[2]);
		}
		
		this.parseScreenScrape = function ( html ) {
			if ( html == null ) { 
				if ( typeof this.ajax.responseXML !== 'undefined' ) { html = this.ajax.responseXML; var tryParse = false; } else
				{ html = this.ajax.responseText; var tryParse = true; }
			}
			if ( ! html ) return false;
			
			// CSS details: object widths
			var markerwidth = 14;
			var contentwidth = ( this.diffDisplay.win_width / 2 ) - markerwidth;
			
			// step 0 - build an XML document from the HTML for IE - cos it's rubbish
			var ie = false, err = false, t = 'nothing';
			if ( ( ! html.getElementById ) && ( tryParse === false ) ) {
				// we're hacking for IE
				ie = true;
				// get, then clean, the responseText
				html = this.ajax.pageRequest.responseText;
				html = html.substr ( html.indexOf ( '<html' ) ); 
				html = html.replace ( /&nbsp;/g, '&#160;' );
				
				var doc = ie_create_document ();
				var test = doc.loadXML ( html );
				if ( doc.parseError.errorCode != 0 ) {
					alert ( 'Internet Explorer failed to parse the incoming page because igloo sanitised it incorrectly. XML parser returned:'+"\n"+doc.parseError.reason );
					return false;
				}
				html = doc;
			} else {
				// perform error checking on the retrieved document
				if ( html.getElementsByTagName ) {
					var err = html.getElementsByTagName ( 'parsererror' ), attempt;
					err = err.length;
				} else { var err = 1, attempt; }
				if ( err > 0 ) {
					if ( typeof this.ajax.responseText !== 'undefined' ) {
						attempt = this.ajax.responseText;
						
						// sanitise XML
						attempt = attempt.replace ( /&nbsp([^;])/g, '&nbsp;$1' );
						attempt = attempt.replace ( /&amp([^;])/g, '&amp;$1' );
						
						if ( window.DOMParser ) {
							var parser = new DOMParser ();
  							html = parser.parseFromString ( attempt, 'text/xml' );
							if ( html.getElementsByTagName ( 'parsererror' ).length > 0 ) { err = true; t = html.getElementsByTagName ( 'parsererror' )[0].firstChild.nodeValue; } else {
								if ( this.warnedParser !== true ) igloo.iglooStatus.addStatus ( '<strong>Warning: igloo was forced to sanitise the incoming page because it contained invalid XML. This is most commonly caused by a faulty, recent change to the Wikipedia page layout and may require reporting if you frequently receive this error.</strong>' );
								this.warnedParser = true;
							}
						} else {
							err = true;
						}
					} else {
						err = true;
					}
				}
			}
			
			if ( err === true ) {
				// clean
				document.getElementById('iglooDiffDisplay').innerHTML = '';
				
				// the container div
				var innerDiv = document.createElement ( 'div' );
				innerDiv.setAttribute ( 'id', 'iglooInnerDiv' );
				innerDiv.style.margin = 'auto'; 
				
				t = t.replace ( '<', '&lt;' ); t = t.replace ( '>', '&gt;' ); t = t.replace ( "\n", '<br />' );
				innerDiv.innerHTML = '<strong>igloo error: an unrecoverable XML parsing error occured on the page you attempted to view. This is most commonly caused by a faulty, recent change to the Wikipedia page layout. If you are receiving this error frequently, you should stop using igloo and report the problem to User:Ale_jrb.</strong><br />Details:</br />' + t;

				
				// display everything in the window
				document.getElementById('iglooDiffDisplay').appendChild ( innerDiv );
				document.getElementById('iglooDiffDisplay').innerHTML += ' ';
				
				return false;
			}
			
			// step 1 - build an array of the data that is displayed by the diff screen
				// this.diffData[0] = old
				// this.diffData[1] = new
				// #[0] = revision as of
				// #[1] = username
				// #[2] = comment/summary
			this.diffData = [];
			this.diffData[0] = []; this.diffData[1] = [];
			for ( var i = 0; i < 3; i ++ ) {
				if ( html.getElementById ) { // good browsers
					if ( html.getElementById ( 'mw-diff-otitle' + ( i + 1 ) ) != null ) { this.diffData[0][i] = echo_nodes_recursive ( html.getElementById ( 'mw-diff-otitle' + ( i + 1 ) ) ); }
					if ( html.getElementById ( 'mw-diff-ntitle' + ( i + 1 ) ) != null ) { this.diffData[1][i] = echo_nodes_recursive ( html.getElementById ( 'mw-diff-ntitle' + ( i + 1 ) ) ); }
				} else { // the trash that is IE
					this.diffData[0][i] = ie_getElementById ( html, 'mw-diff-otitle' + ( i + 1 ) ).text;
					this.diffData[1][i] = ie_getElementById ( html, 'mw-diff-ntitle' + ( i + 1 ) ).text;
				}
			}
			
			// step 2 - remove that from the visible diff
			var allTables = html.getElementsByTagName ( 'table' );
			for ( var i = 0; i < allTables.length; i ++ ) {
				if ( allTables[i].className == 'diff' ) break;
			}
			if ( i >= allTables.length ) i = ( allTables.length - 1 );
			if  (i < 0 ) { var newPage = true; } else { var newPage = false; }
			
			if ( newPage == false ) {
				var table = document.createElement ( 'table' );
				table.className = 'diff';
				
				// in order to add content to the table in IE, we must first add it to a diff. We also handle styling IE output here.
				if ( ie ) {
					var tempIEDiv 		= document.createElement ( 'div' );
					var tempIETable 	= ie_cloneNode ( allTables[i] );
					tempIEDiv.appendChild ( tempIETable );
					//tempIEDiv.innerHTML = this.flag(tempIEDiv.innerHTML);
					
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/class='?diff-context'?/ig, 			'style="background-color: #ddddff;"');
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/class='?diff-addedline'?/ig, 		'style="background-color: #ccddcc;"');
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/class='?diff-deletedline'?/ig, 		'style="background-color: #ffffaa;"');
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/class='?diff['>]/ig, 				'style="width: 100%; font-size: 12px; background-color: #ededff;"');
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/(del |ins )class=['"]?.*?diffchange.*?['"]?(>)?/ig,	'$1style="text-decoration: none; font-weight: bold; color: #ff0000;"$2');
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/class='?diff-marker'?/ig, 			'style="width: '+markerwidth+'px; font-size: 14px; font-weight: bold;"');
					tempIEDiv.innerHTML = tempIEDiv.innerHTML.replace(/class='?diff-content'?/ig, 			'style="width: '+contentwidth+'px;"');
					
					table.appendChild ( tempIEDiv );
				} else {
					table.innerHTML = allTables[i].innerHTML.toString();
					var size = iglooSettings.diffFontSize;
					table.innerHTML = table.innerHTML.replace ( /class="diff-addedline"/ig, 'class="diff-addedline" style="font-size: '+size+'px;"' );
					table.innerHTML = table.innerHTML.replace ( /class="diff-context"/ig, 'class="diff-context" style="font-size: '+size+'px;"' );
					table.innerHTML = table.innerHTML.replace ( /class="diff-deletedline"/ig, 'class="diff-deletedline" style="font-size: '+size+'px;"' );
					table.innerHTML = this.flagProfanity(table.innerHTML);
				}
				
				// remove the built in rubbish
				if ( table.getElementsByTagName ( 'tr' )[0] != null ) table.getElementsByTagName ( 'tr' )[0].parentNode.removeChild ( table.getElementsByTagName ( 'tr' )[0] );
				
				// add our own (basically rubbish) stuff
				// first, users
				var newTr = document.createElement ( 'tr' );
				if ( this.diffData[0][1] ) {
					var oldEditor = this.diffData[0][1].substr( 0, this.diffData[0][1].indexOf('(talk') );
				} else { var oldEditor = '? missing - report ?'; }
				if ( this.diffData[1][1] ) {
					var newEditor = this.diffData[1][1].substr( 0, this.diffData[1][1].indexOf('(talk') );
				} else { var newEditor = '? missing - report ?'; }
																							 
				// if we don't have the user for this diff, we might as well set it, even if something goes wrong; it's better than nothing
				if ( typeof this.currentDiff[1] == 'undefined' ) this.currentDiff[1] = newEditor;
				if ( this.currentDiff[1] === true ) /* will be true if a revert fails; we should still try and score it */ this.currentDiff[1] = newEditor;
				
				var newTd1 = document.createElement ( 'td' );
				newTd1.innerHTML = ''+oldEditor+'';
				newTd1.setAttribute ( 'colspan', '2' );
				newTd1.setAttribute ( 'align', 'center' );
				newTd1.setAttribute ( 'width', '50%' );
				var newTd2 = document.createElement ( 'td' );
				newTd2.innerHTML = ''+newEditor+'';
				newTd2.setAttribute ( 'colspan', '2' );
				newTd2.setAttribute ( 'align', 'center' );
				newTd2.setAttribute ( 'width', '50%' );
				
				newTr.appendChild(newTd1); newTr.appendChild(newTd2);
				table.insertBefore(newTr, table.firstChild);
				
				// second, summaries
				var newTr = document.createElement('tr');
				if ( ( typeof this.diffData[0][2] != 'undefined' ) && ( this.diffData[0][2] != false ) ) { var oldSummary = this.diffData[0][2]; } else { var oldSummary = ''; }
				if ( ( typeof this.diffData[1][2] != 'undefined' ) && ( this.diffData[1][2] != false ) ) { var newSummary = this.diffData[1][2]; } else { var newSummary = ''; }
				
				if ( oldSummary.indexOf ( '(del/undel)' ) > -1 ) oldSummary = oldSummary.substr ( 0, oldSummary.indexOf ( '(del/undel)' ) );
				if ( newSummary.indexOf ( '(del/undel)' ) > -1 ) newSummary = newSummary.substr ( 0, newSummary.indexOf ( '(del/undel)' ) );
				
				var newTd1 = document.createElement('td');
				newTd1.innerHTML = ''+oldSummary+'';
				newTd1.setAttribute('colspan','2');
				newTd1.setAttribute('align','center');
				var newTd2 = document.createElement('td');
				newTd2.innerHTML = ''+newSummary+'';
				newTd2.setAttribute('colspan','2');
				newTd2.setAttribute('align','center');
				
				newTr.appendChild(newTd1); newTr.appendChild(newTd2);
				table.insertBefore(newTr, table.childNodes[1]);
			} else {
				// step 2 - get the page content
				var table = document.createElement ( 'div' );
				table.setAttribute ( 'style', 'font-size: 12px;' );
				table.className = 'diff';
				
				var text = this.ajax.pageRequest.responseText;
				text = text.substring ( text.indexOf ( '</h2>' ), text.indexOf ( '<div class="printfooter"' ) );
				
				table.innerHTML = text;
			}
			
			// step 3 - remove old details
			if (document.getElementById('iglooPageTitle') != null) document.getElementById('iglooPageTitle').parentNode.removeChild(document.getElementById('iglooPageTitle'));
			if (document.getElementById('iglooInnerDiv') != null) document.getElementById('iglooInnerDiv').parentNode.removeChild(document.getElementById('iglooInnerDiv'));
			document.getElementById('iglooDiffDisplay').innerHTML = '';
			
			// step 4 - output the page title to the diff screen
			var pageTitle = document.createElement('div');
			pageTitle.setAttribute('id', 'iglooPageTitle');
			pageTitle.style.fontSize = '16px';
			pageTitle.style.fontWeight = 'bold';
			pageTitle.style.width = '100%';
			pageTitle.style.marginBottom = '5px';
			pageTitle.style.borderBottom = '1px solid #000';
			if ( this.currentDiff[3] ) { var extra = '(NEW PAGE) '; } else { var extra = ''; }
			var pageTitleContent = document.createTextNode(extra + this.currentDiff[0]);
			pageTitle.appendChild(pageTitleContent);
			document.getElementById('iglooDiffDisplay').appendChild(pageTitle);
			
			// step 5 - output the table in question to the diff screen
			// here, we handle styling for NON-IE browsers
			if ( ! ie ) {
				var style = document.createElement('style');
				style.setAttribute('type', 'text/css');
			
				var cssString = 'span.iglooProfanity { background-color: #ff99ff; font-weight: bold; text-decoration: underline; } table.diff { width: 100%; font-size: 12px; background-color: #ededff; } col.diff-marker { width: '+markerwidth+'px; font-size: 14px; font-weight: bold; } col.diff-content { width: '+contentwidth+'px; } td.diff-lineno { font-weight: bold; } td.diff-addedline { background-color: #ccffcc; } td.diff-deletedline { background-color: #ffffaa; } td.diff-context { background-color: #ddddff; } .diffchange { text-decoration: none; font-weight: bold; color: #ff0000; }';
				var cssText = document.createTextNode(cssString);
				style.appendChild(cssText);
				
				document.getElementById('iglooDiffDisplay').appendChild(style);
			}
			
			// the container div
			var innerDiv = document.createElement ( 'div' );
			innerDiv.setAttribute ( 'id', 'iglooInnerDiv' );
			innerDiv.style.margin = 'auto'; 
			
			// stick the table of stuff into the div
			innerDiv.appendChild ( table );
			
			// display everything in the window
			document.getElementById('iglooDiffDisplay').appendChild(innerDiv);
			document.getElementById('iglooDiffDisplay').innerHTML += ' ';
			
			// we can now revert this edit
			if ( igloo.iglooActions.reversionEnabled == 'pause' ) igloo.iglooActions.reversionEnabled = 'yes';
			
			// handle the diff scoring window
			this.updateScoreDisplay ( this.currentDiff[4] );
			
		}
		
		this.updateScoreDisplay = function ( score ) {
			if ( score == null ) {
				score = igloo.iglooNet.scoreObject ( this.currentDiff[1] );
				score = score[0];
				
				if ( score === false ) score = iglooSettings.defaultUserScore;
			}
			
			if ( typeof this.currentDiff[5] !== 'string' ) this.currentDiff[5] = '';
			
			if ( score != null ) {
				if ( score >= 0.8 ) {
					this.scoreDisplay.win_content = 'igloo asserts that this edit is <strong>PROBABLE VANDALISM</strong>.';
				} else if ( score >= 0.6 ) {
					this.scoreDisplay.win_content = 'igloo asserts that this edit is <strong>possible vandalism</strong>.';
				} else if ( score >= 0.4 ) {
					this.scoreDisplay.win_content = 'igloo has little or no data on this diff, and cannot make a risk assertion.';
				} else if ( score >= 0.2 ) {
					this.scoreDisplay.win_content = 'igloo asserts that this edit is <strong>unlikely to be vandalism</strong>.';
				} else if ( score >= 0 ) {
					this.scoreDisplay.win_content = 'igloo asserts that this edit is <strong>free from vandalism</strong>.';
				} else {
					this.scoreDisplay.win_content = 'iglooNet data for this diff is missing.';
				}
			} else {
					score = iglooSettings.defaultUserScore;
					this.scoreDisplay.win_content = 'iglooNet data for this diff is missing.';
			}
			this.scoreDisplay.win_content += '' + this.currentDiff[5];
			this.scoreDisplay.win_bg = igloo.iglooNet.colourScore ( score, '#ffffff' );
			this.scoreDisplay.applyAll();
		}
		
		this.flagProfanity = function(html) {
			// this function flags profanity in the diff window
			if ( iglooSettings.profFilter !== true ) return html;
			
			var profanity = new Array(
											/\b((?:moth[era]*)?[ -]*f+(?:u|oo)[ck]{2,}(?:ing?|er|hole)?s*|s[e3]+x+[iey]*|r[a4]p(?:e+d*|i+s+t+s*)|su[ck]+(?:s+|ed+|i+n+g+)?|gang[- ]?bang(?:er|ing)?|(?:(?:t+h+|f+)r[e3]{2,}|four)+s[o0]+me+)\b/ig, /* sexual intercourse */
											/\b(h[o0]+m[o0]+(?:sexual(?:it(?:y|e)+)?)?|(?:is +)?ga+[iy]+|lesb(?:ian|[o0]*))\b/ig, /* homosexuality */
											
											/\b([ck][o0]+[ck]{2,}(?:head|face|(?:su[ck]{2,}(?:er|ing)?))?|d[o0]+n+g|p[3e]+n[iu]+s+[3e]*s*|p+[3e]{2,}|d[i1]+[ck]{2,}s*(?:su[ck]{2,}(?:er|ing)?)?|manh(?:[o0]{2,}|u+)d+|b[o0]+n+e*r+s*|ball[sz]+(?:a[ck]{2,})?)\b/ig, /* male genitalia */
											/\b(cun+(?:t|[iey]+)s*|vag(?:ina)?s*|puss+[yie]+s*|fann+[yie]+s*)\b/ig, /* female genitalia */
											/\b(t+i+t+(?:s*|[iey]+[sz]*)|breasts*|b+[o0]{2,}b+[ieys]*)\b/ig, /* breasts */
											/\b(anal+|ar*ss+e*|ar*se+s+|(?:bum+|butt+) ?(?:h[o0]+le|cr+a[ck]+|o(?:[ck]+s|x+))?)\b/ig, /* anus */
											
											/\b((?:bull)?(?:s+hite*|ass+)(?:holes?|he[a4]+d+s*)?|cr[a4*]+p+[iy]*|cru+d+|poo+)\b/ig, /* excretion */
											/\b(w*h+[o0]+r*e+s*|prostitutes*|s+l+u+t+s*|slags*|cu+m+(?:ing)?|d[il]{2,}d[o0]+[o0e]*s*|(?:b+l+[o0]+w+|h+[a4]+n+d+|t[i1]+t+[iey]*)j+[o0]+b+[sz]*|c[o0]+ndom[sz]*|p[o0]+rn)\b/ig, /* sexual */
											/\b(nigg+(?:er|a+)s*|naz+i+[sz]+|ped[o0]+(?:[phf]+ile)?[sz]*)\b/ig, /* racism & libel */
											/\b(ret[a4]+r+d+(?:ed|s)?|f+[a4]+g+([o0]+t+)?s*|d[ou]+che?(ba+g)?s*|bast[ea]rds*|bit*ch[iey]*|[you]*r+ ?m+[ou]+m+)\b/ig, /* other insults */
											
											/('{3,}bold text'{3,}|'{2,}italic text'{2,}|\[{2,}link title\]{2,}|\[http:\/\/www\.example\.com link title\]|={2,} *headline text *={2,})/ig, /* wiki test */
											/\b(q[qwerty]{5,}|[asdf]{8,}|[ghjkl]{8,}|[uiop]{8,})\b/ig, /* nonsense */
											/\b(lol(?:l*ol|cat[sz]*)*|li+e+k)\b/ig, /* lolz */
											/\b(ha?i+(?=\/)|he+ll+o+|(?:ha+|hee+|ho+)+|l[ou]+v+|ya|ye+h+)\b/ig, /* laughs/greetz */
											/([!?;]{3,}|[.|]{4,}|={6,}|([a-z0-9])\2{6,})/ig /* repeating characters */
									  );
			
			for ( var i = 0, l = profanity.length; i < l; i ++ ) {
				html = html.replace(profanity[i], '<span class="iglooProfanity">$1</span>');
			}
			
			return html;
		}
		
		this.destroy = function () { // calling this will destroy the interface and connections of this object.
			this.diffDisplay.hide ();
			this.scoreDisplay.hide ();
		}
	}
	
	function iglooHist () {
		// the iglooHist object handles the retrieval and display of the history of a page, in order
		// that it can be displayed to the user.
		
		// timer var
		this.timer = null;
		
		this.startInterface = function() {
			if (document.getElementById('igloo-buttons-history') == null) return false;
			
			this.histCatcher = new wa_window ( document.getElementById ( 'igloo-buttons-history' ) );
			this.histCatcher.win_top = 71;
			this.histCatcher.win_left = -100;
			this.histCatcher.win_width = 170;
			this.histCatcher.win_height= 80;
			this.histCatcher.win_alpha = 0;
			this.histCatcher.win_cursor = 'pointer';
			this.histCatcher.win_disp = 'none';
			this.histCatcher.applyAll ();
			
			this.histDisplay = new wa_window ( document.getElementById ( 'igloo-buttons-history' ) );
			this.histDisplay.win_top = 77;
			this.histDisplay.win_left = -100;
			this.histDisplay.win_width = 170;
			this.histDisplay.win_bg = '#d0d0d0';
			this.histDisplay.win_bd = '#000000';
			this.histDisplay.win_bd_wd = 1;
			this.histDisplay.win_padding = 2;
			this.histDisplay.win_fontsize = 10;
			this.histDisplay.win_cursor = 'pointer';
			this.histDisplay.win_disp = 'none';
			this.histDisplay.win_content = '<div id="iglooPageHistory-note" style="width: 100%;">loading page history - wait...</div><ul style="display: none; width: 100%; height: 100%; margin: 0px; padding: 0px; overflow-x: hidden; overflow-y: auto;" id="iglooPageHistory-cont"></ul>';
			this.histDisplay.applyAll ();
			
			// unfortunately, these pseudo events cannot be detatched. See waLib.js for implementation.
			wa_attach ( document.getElementById ( 'igloo-buttons-history' ), 'mouseenter', igloo.iglooHist.mouseOver );
			wa_attach ( document.getElementById ( 'igloo-buttons-history' ), 'mouseleave', igloo.iglooHist.mouseOut );
		}
		
		this.mouseOver = function () {
			if ( igloo.iglooDiff.viewingDiff === true ) {
				if ( igloo.iglooHist.timer ) { clearTimeout ( igloo.iglooHist.timer ); igloo.iglooHist.timer = false; } else {
					igloo.iglooHist.histDisplay.show (); igloo.iglooHist.histCatcher.show ();
					
					igloo.iglooHist.getHistory ();
				}
			}
		}
		
		this.mouseOut = function () {
			igloo.iglooHist.timer = setTimeout(function() { igloo.iglooHist.histDisplay.hide(); igloo.iglooHist.histCatcher.hide(); igloo.iglooHist.timer = false; }, iglooSettings.histWinTimeout * 1000);
		}
		
		this.getHistory = function ( callback, data ) {
			// the get history module retrieves a page history and displays it to the user
			switch ( callback ) {
				default: case 0:
					document.getElementById ( 'iglooPageHistory-cont' ).innerHTML = 'loading page history - wait...';
				
					var pageHist = new wa_mediawikiApi ();
					pageHist.onCompleteAction = function ( data ) { igloo.iglooHist.getHistory ( 1, data ); };
					pageHist.getPage ( igloo.iglooDiff.currentDiff[0], 15, 'ids|user' );
					break;
					
				case 1:
					document.getElementById('iglooPageHistory-cont').style.display = 'block';
					document.getElementById('iglooPageHistory-note').style.display = 'none';
					
					var pageHistory = '';
					for (var i = 0; i < data['page']['revisions'].length; i ++ ) {
						var revision = data['page']['revisions'][i];
						var user = revision['user'];
						var score = igloo.iglooNet.scoreObject(user);
						var backgroundColour = igloo.iglooNet.colourScore(score[0], '#ffffff');
						
						pageHistory += '<li id="iglooHist_'+revision['id']+'" onclick="igloo.iglooControls.action_loadFromFeed(\''+igloo.iglooDiff.currentDiff[0].replace ('\'', '\\\'')+'\', \''+revision['user']+'\', \''+revision['id']+'\');" onmouseover="this.style.backgroundColor = \'#dddddd\';" onmouseout="this.style.backgroundColor = \''+backgroundColour+'\';" style="cursor: pointer; width: 186px; padding: 2px; border-bottom: 1px solid #000000; list-style-type: none; list-style-image: none; marker-offset: 0px; background-color: '+backgroundColour+';">'+revision['user']+'</li>';
					}
					pageHistory += '<li style="width: 100%; list-style-type: none; list-style-image: none; text-align: center;" onclick="igloo.iglooActions.sendLinkToParent(mw.config.get(\'wgServer\') + mw.config.get(\'wgScript\') + \'?title=\' + igloo.iglooDiff.currentDiff[0] + \'&action=history\');">- full history -</li>';
					document.getElementById('iglooPageHistory-cont').innerHTML = pageHistory;
					
					break;
			}
		}
	}
	
	
	
	
	function iglooControls () {
		// the igloo controls module handles attaching to, and watching, keyboard events, and performing the requested function
		this.blockActions = false;
		
		this.start = function () { // this functions creates the diff window for future use, and displays the latest news in it :)
			this.startInterface ();
			this.bindKeys ();
		}
		
		this.startInterface = function () {
			this.controlDisplay = new wa_window ( igloo.iglooInterface );
			this.controlDisplay.win_left = 190;
			this.controlDisplay.win_width = parseInt ( igloo.iglooInterface.win_obj.style.width ) - 192;
			this.controlDisplay.win_height= 78;
			this.controlDisplay.win_bg = '#fdfdff';
			this.controlDisplay.win_bd_tp = '1px solid #000000';
			this.controlDisplay.win_bd_bt = '1px solid #000000';
			this.controlDisplay.win_padding = 1;
			this.controlDisplay.win_content = '<div id="iglooControlsDisplay" style="width: 100%; height: 100%;">CONTROLS INIT</div>';
			this.controlDisplay.applyAll ();
			
			this.attachButtons ();
			
			igloo.iglooHist = new iglooHist ();
			igloo.iglooHist.startInterface ();
		}
		
		this.attachButtons = function() {
			var parent = document.getElementById('iglooControlsDisplay'), buttons = 0;
			
			var panelButton = '<div id="igloo-buttons-revertwarn" style="position: relative; float: left; padding-left: -1px; padding-top: -1px; width: 73px; height: 73px; margin-top: 7px; margin-left: 5px; cursor: pointer;"><img src="' + iglooSettings.remoteHost + 'images/igloo-revert.png" onclick="if (typeof igloo.iglooDiff.currentDiff[0] != \'undefined\') { igloo.iglooControls.action_revertDiff(); }" /></div>';
			panelButton += '<div id="igloo-buttons-back" style="position: relative; float: left; padding-left: -1px; padding-top: -1px; width: 50px; height: 50px; margin-top: 2px; margin-left: 20px; cursor: pointer;"><img id="igloo-buttons-back-b" src="' + iglooSettings.remoteHost + 'images/igloo-back-grey.png" onclick="igloo.iglooControls.action_historyBack();" /></div>'; buttons ++;
			panelButton += '<div id="igloo-buttons-forward" style="position: relative; float: left; padding-left: -1px; padding-top: -1px; width: 50px; height: 50px; margin-top: 2px; margin-left: 10px; cursor: pointer;"><img id="igloo-buttons-forward-b" src="' + iglooSettings.remoteHost + 'images/igloo-forward-grey.png" onclick="igloo.iglooControls.action_historyForward();" /></div>'; buttons ++;
			if ( iglooSettings.mesysop === true ) { panelButton += '<div id="igloo-buttons-block" style="position: relative; float: left; padding-left: -1px; padding-top: -1px; width: 50px; height: 50px; margin-top: 2px; margin-left: 10px; cursor: pointer;"><img id="igloo-buttons-block-b" src="' + iglooSettings.remoteHost + 'images/igloo-block.png" onclick="igloo.iglooControls.action_block();" /></div>'; buttons ++; }
			
			var browsePos = ( 62 * buttons ) - 15;
			panelButton += '<div id="igloo-input-browse" style="position: relative; float: left; top: 53px; left: -' + browsePos + 'px; width: 230px; height: 20px;"><input id="igloo-browse-to" type="text" style="width: 200px; height: 14px; " onfocus="igloo.iglooControls.action_browseFocus();" onblur="igloo.iglooControls.action_browseBlur();" /> <img style="position: relative; top: -3px; cursor: pointer;" src="' + iglooSettings.remoteHost + 'images/igloo-go.png" onclick="igloo.iglooControls.action_browseBarDo();" /></div>';
			
			panelButton += '<div id="igloo-buttons-settings" style="position: relative; float: right; padding-left: -1px; padding-top: -1px; width: 73px; height: 73px; margin-top: 3px; margin-right: 5px; cursor: pointer;"><img src="' + iglooSettings.remoteHost + 'images/igloo-settings.png" onclick="igloo.iglooManageSettings.showdisplay ();" /></div>';
			panelButton += '<div id="igloo-buttons-history" style="position: relative; float: right; padding-left: -1px; padding-top: -1px; width: 73px; height: 73px; margin-top: 3px; margin-right: 5px; cursor: pointer;"><img src="' + iglooSettings.remoteHost + 'images/igloo-hist.png" /></div>';
		
			parent.innerHTML = panelButton;
		}
		
		this.bindKeys = function() {
			wa_attach ( document, 'keydown', igloo.iglooControls.killKeys );
			//wa_attach ( document, 'keypress', igloo.iglooControls.killKeys );
			wa_attach ( document, 'keyup', igloo.iglooControls.handleKeys );
		}
		
		this.handleKeys = function ( e ) {
			if ( ! e ) e = window.event;
			if ( e.keyCode ) {
				var keyPress = e.keyCode;
				var use = 'key';
			} else {
				var keyPress = e.charCode;
				var use = 'char';
			}
			
			var result = igloo.iglooControls.manageKeys ( keyPress, use, false );
			if ( result === true ) return true;
			
			if ( e.preventDefault ) { e.preventDefault (); } else { return false; }
			return true;
		}
		
		this.killKeys = function ( e ) {
			// check whether to prevent the default action
			if ( iglooSettings.blockKeys === false ) return true;
			
			if ( ! e ) e = window.event;
			if ( e.keyCode ) {
				var keyPress = e.keyCode;
				var use = 'key';
			} else {
				var keyPress = e.charCode;
				var use = 'char';
			}
			
			var result = igloo.iglooControls.manageKeys ( keyPress, use, true );
			if ( result === true ) return true;
			
			if ( e.preventDefault ) { e.preventDefault (); } else { return false; }
			return true;
		}
		
		this.manageKeys = function ( code, use, killcheck ) {
			var key = 	{ 'default' :
							{ 'key' : { 'backspace' : 8, 'space' : 32, 'q' : 81, 'f5' : 116 }, 'char' : { 'backspace' : 8, 'space' : 32, 'q' : 113, 'f5' : 0 } },
						  'browsebar' :
							{ 'key' : { 'f5' : 116, 'enter' : 13 }, 'char' : { 'f5' : 0, 'enter' : 0 } },
						  'settings' :
						  	{ 'key' : { 'f5' : 116 }, 'char' : { 'f5' : 0 } }
						};
			
			// the active key set
			var key = key [iglooSettings.dynamicBlockKeys] [use];
			
			if ( killcheck === true ) {
				for ( var i in key ) {
					if ( key [i] == code ) { 
						return false; 
					} // if exists, block
				}
				return true; // otherwise, don't
			}
			switch ( iglooSettings.dynamicBlockKeys ) {
				case 'default':
					switch ( code ) {
						case key ['backspace']: // backspace
							igloo.iglooControls.action_historyBack ();
							break;
						
						case key ['space']: // spacebar
							if (typeof igloo.iglooChanges.recentChanges[0] != 'undefined') {
								igloo.iglooControls.action_loadFromFeed(igloo.iglooChanges.recentChanges[0][0], igloo.iglooChanges.recentChanges[0][1], igloo.iglooChanges.recentChanges[0][2], igloo.iglooChanges.recentChanges[0][3], igloo.iglooChanges.recentChanges[0][4], igloo.iglooChanges.recentChanges[0][11]); 
							}
							break;
							
						case key ['q']: // q
							if ( typeof igloo.iglooDiff.currentDiff[0] != 'undefined' ) {
								igloo.iglooControls.action_revertDiff();
							}
							break;
							
						case key ['f5']: // F5
							igloo.iglooControls.getPermission ( 'You just pressed the F5 key. By default, this causes the page to refresh in most browsers. To prevent you losing your work, igloo therefore agressively blocks this key. Do you wish to reload the page?', function () { location.reload (); } );
							break;
							
						default: // if the key is unrecognised, do nothing
							return true;
							break;
					}
					break;
					
				case 'browsebar':
					switch ( code ) {
						case key ['enter']: // enter
							igloo.iglooControls.action_browseBarDo ();
							break;
						
						case key ['f5']: // F5
							igloo.iglooControls.getPermission ( 'You just pressed the F5 key. By default, this causes the page to refresh in most browsers. To prevent you losing your work, igloo therefore agressively blocks this key. Do you wish to reload the page?', function () { location.reload (); } );
							break;
							
						default: // if the key is unrecognised, do nothing
							return true;
							break;
					}
					break;
				
				case 'settings':
					switch ( code ) {
						case key ['f5']: // F5
							igloo.iglooControls.getPermission ( 'You just pressed the F5 key. By default, this causes the page to refresh in most browsers. To prevent you losing your work, igloo therefore agressively blocks this key. Do you wish to reload the page?', function () { location.reload (); } );
							break;
							
						default: // if the key is unrecognised, do nothing
							return true;
							break;
					}
					break;
			}
			
			return false;
		}
		
		this.getPermission = function ( message, escalateFunction, nobuttons, owntitle ) {
			this.blockActions = true;
			
			if ( ! nobuttons ) {
				var button = '<br /><br /><div style="width: 100%; text-align: center;"><span id="igloo-permission-challenge-cont" style="cursor: pointer;">continue</span> | <span id="igloo-permission-challenge-canc" style="cursor: pointer;">cancel</span></div>';
			} else { var button = ''; }
			if ( ! owntitle ) {
				var title = 'igloo needs your permission to continue...';
			} else { var title = owntitle; }
			
			
			if ( ! this.userMessage ) {
				this.userMessage = new wa_window ();
				this.userMessage.win_alpha = 0.7;
				this.userMessage.win_bg = '#000000';
				this.userMessage.win_fill = true;
				this.userMessage.applyAll ();
			} else {
				this.userMessage.show ();
			}
			
			if ( ! this.userMessageContent ) {
				this.userMessageContent = new wa_window ();
				this.userMessageContent.win_bg = '#ccccff';
				this.userMessageContent.win_width = 500;
				this.userMessageContent.win_height = 130;
				this.userMessageContent.win_padding = 5;
				this.userMessageContent.win_fontsize = 10;
				this.userMessageContent.win_alpha = 1;
				this.userMessageContent.win_bd = '2px solid #666688';
				this.userMessageContent.win_content = '<div><span style="width: 100%; border-bottom: 1px solid #000;"><strong>' + title + '</strong></span><br /><div style="text-align: left; margin-left: 10px; width: 90%; color: #222222;">' + message + '</div>' + button + '</div>';
				this.userMessageContent.applyAll ();
				this.userMessageContent.center ( 'both', true );
				
			} else {
				this.userMessageContent.show ();
				this.userMessageContent.win_content = '<div><span style="width: 100%; border-bottom: 1px solid #000;"><strong>' + title + '</strong></span><br /><div style="text-align: left; margin-left: 10px; width: 90%; color: #222222;">' + message + '</div>' + button + '</div>';
				this.userMessageContent.applyAll ();
				this.userMessageContent.center ( 'both', true );
			}
			if ( ! nobuttons ) {
				document.getElementById ( 'igloo-permission-challenge-cont' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide(); igloo.iglooControls.userMessageContent.hide(); escalateFunction(); }
				document.getElementById ( 'igloo-permission-challenge-canc' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide(); igloo.iglooControls.userMessageContent.hide(); };
			}
		}
		
		this.action_historyBack = function () {
			if ( igloo.iglooControls.blockActions ) return false;
			igloo.iglooDiff.goBack ( 1 );
		}
		
		this.action_historyForward = function () {
			if ( igloo.iglooControls.blockActions ) return false;
			igloo.iglooDiff.goForward ( 1 );
		}
		
		this.action_revertDiff = function () {
			if ( igloo.iglooControls.blockActions ) return false;
			igloo.iglooActions.revertDiff ();
		}
		
		this.action_block = function () {
			if ( igloo.iglooControls.blockActions ) return false;
			iglooSettings.dynamicBlockKeys = 'settings';
			var user = ( typeof igloo.iglooDiff.currentDiff [1] !== 'undefined' ) ? igloo.iglooDiff.currentDiff [1] : '';
			var tb = new iglooRevert ( 0, user );
			tb.blockUser ( 5 );
		}
		
		this.action_loadFromFeed = function ( a, b, c, d, e, f ) {
			if ( igloo.iglooControls.blockActions ) return false;
			igloo.iglooActions.reversionEnabled = 'pause';
			
			igloo.iglooDiff.display ( a, b, c, d, e, f );
			igloo.iglooChanges.markViewed ( c );
		}
		
		this.action_browseBarDo = function () {
			if ( igloo.iglooControls.blockActions ) return false;
			igloo.iglooActions.reversionEnabled = 'pause';
			
			var browseTo = document.getElementById ( 'igloo-browse-to' ).value;
			document.getElementById ( 'igloo-browse-to' ).value = '';
			
			igloo.iglooDiff.display ( browseTo );
		}
		
		this.action_browseFocus = function () {
			iglooSettings.dynamicBlockKeys = 'browsebar'; // stop blocking/overwriting key actions when we activate the text box
		}
		
		this.action_browseBlur = function () {
			iglooSettings.dynamicBlockKeys = 'default'; // start blocking/overwriting key actions when we activate the text box
		}
		
		this.destroy = function () { // calling this will destroy the interface and connections of this object.
			this.controlDisplay.hide ();
		}
	}
	
	function iglooActions () {
		// the iglooActions module handles the actual editing of the wiki, such as reversions and warnings
		this.reversionEnabled 		= 'yes';
		this.lastRevertedRevision 	= false;
		this.revertedPage 			= false;
		this.warnWho 				= false;
		this.warningLevel			= false;
		
		this.revertDiff = function () {
			this.lastRevertedRevision = igloo.iglooDiff.currentDiff [2];
			
			var page = igloo.iglooDiff.currentDiff [0];
			var user = igloo.iglooDiff.currentDiff [1];
			var revid = igloo.iglooDiff.currentDiff [2];
			
			var rev = new iglooRevert ( page, user, revid );
		}
		
		this.sendLinkToParent = function ( linkText, activateAfter ) {
			if ( activateAfter == null ) activateAfter = true;
			
			if ( ( window.opener != null ) && ( ! window.opener.closed ) ) {
				window.opener.location.href = linkText;
				window.opener.focus ();
				
				return true;
			}
			return false;
		}
		
		this.cancelActivity = function () {
			
		}
	}
	function iglooRevert ( page, user, revid ) {
		var thisRevert 		= this;
		this.revertPage		= page;
		this.revertUser		= user;
		this.revertRevid	= revid;
		
		this.performRollback = function ( callback, details ) {
			switch ( callback ) {
				default: case 0:
					// check that reversion is switched on
					if ( igloo.iglooActions.reversionEnabled == 'no' ) { alert ( 'You cannot revert this edit to ' + this.revertPage + ', because you made it using igloo' );  return false; }
					if ( igloo.iglooActions.reversionEnabled == 'pause' ) { alert ( 'You cannot revert this edit to ' + this.revertPage + ', because a diff is still loading' ); return false; }
					
					// notify user
					igloo.iglooStatus.addStatus ( 'Attempting to revert the change to <strong>' + thisRevert.revertPage + '</strong> made by <strong>' + thisRevert.revertUser + '</strong>...' );
					
					// prevent interference with this page while we are reverting it
					igloo.iglooActions.reversionEnabled = 'pause';
					
					// let the user know we're working...
					document.getElementById ( 'iglooPageTitle' ).innerHTML = document.getElementById ( 'iglooPageTitle' ).innerHTML + ' - reverting edit...';
					
					// build the reversion summary
					var summary = iglooSettings.rollbackSummary;
					
					// attempt the actual rollback
					var thisReversion = new wa_mediawikiApi ();
					thisReversion.onCompleteAction = function ( success ) { thisRevert.performRollback ( 1, success ); };
					thisReversion.rollbackPage ( this.revertPage, this.revertUser, summary );
					
					// mark the edit we're reverting as bad
					var url = iglooSettings.remoteHost + 'main.php?action=markbad&session=' + igloo.iglooSession.session + '&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) +'&user=' + this.revertUser + '&revid=' + this.revertRevid;
					iglooImport ( url, true, 'iglooMark' );
					break;
					
				case 1:
					if ( details == false ) {
						igloo.iglooStatus.addStatus ( 'Will not revert the edit to <strong>' + thisRevert.revertPage + '</strong> by <strong>' + thisRevert.revertUser + '</strong> because another user has already done so.' );
						//alert('igloo will not revert the edit to '+this.revertPage+', because another user has already done so');
						if ( this.revertPage == igloo.iglooDiff.currentDiff[0] ) {
							igloo.iglooDiff.display ( this.revertPage, true );
							igloo.iglooActions.reversionEnabled = 'no';
						}
					} else {
						var thisReversion = new wa_mediawikiApi ();
						thisReversion.onCompleteAction = function ( data ) { thisRevert.performRollback ( 2, data ); };
						thisReversion.getPage ( this.revertPage, 1, 'ids' );
					}
					break;
					
				case 2:
					// retrieve page ID, so we do not redisplay in the feed
					igloo.iglooChanges.addToViewed ( details['page']['revisions'][0]['id'] );
					if ( this.revertPage == igloo.iglooDiff.currentDiff[0] ) igloo.iglooDiff.display ( this.revertPage, mw.config.get('wgUserName') );
					
					// notify user
					igloo.iglooStatus.addStatus ( 'Successfully reverted the change to <strong>' + thisRevert.revertPage + '</strong> made by <strong>' + thisRevert.revertUser + '</strong>!' );
					
					this.warnUser();
					break;
			}
		}
		
		this.warnUser = function( callback, details ) {
			switch ( callback ) {
				default: case 0:
					// don't warn self
					if ( thisRevert.revertUser == mw.config.get('wgUserName') ) break;
				
					// notify user
					igloo.iglooStatus.addStatus( 'Attempting to warn <strong>' + thisRevert.revertUser + '</strong> for vandalism on <strong>' + thisRevert.revertPage + '</strong>...' );
				
					// get the user talk page
					var getUserPage = new wa_mediawikiApi ();
					getUserPage.onCompleteAction = function ( data ) { thisRevert.warnUser ( 1, data ); };
					getUserPage.getPage ( 'User_talk:' + this.revertUser, 1, 'content' );
					break;
					
				case 1:
					// set up the time management systems
					var months = new Array ( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' );
					var currentDate = new Date ();
					var currentMonth = currentDate.getMonth ();
					var currentYear = currentDate.getFullYear ();
					var currentTime = currentDate.getTime ();
					
					// check for warnings on the user's talk page
					var warnings = [];
					
					// if the page already exists, we must analyse it for warnings
					if ( typeof details['page']['revisions'] != 'undefined' ) {
						var pageData = thisRevert.userTalk = details['page']['revisions'][0]['content'];
						var regTest = /<!-- ?(?:template:?)?uw-([a-z]+?)([0-9](?:im)?)? ?-->(?:.+?([0-9]{2}):([0-9]{2}), ([0-9]{1,2}) ([a-z]+?) ([0-9]{4}))?/gi;
						
						// get all the warnings on the page
						var i = 0;
						while ( true ) {
							var t = regTest.exec ( pageData );
							if ( t == null ) break;
							warnings[i] = [];
							warnings[i][0] = t[1]; // template
							warnings[i][1] = t[2]; // level
							warnings[i][2] = t[3]; // hour
							warnings[i][3] = t[4]; // minute
							warnings[i][4] = t[5]; // day
							warnings[i][5] = t[6]; // month
							warnings[i][6] = t[7]; // year
							
							//alert(t[1]);
							
							i ++;
						}
						
						// we are only interested in the latest one
						if ( typeof warnings[0] == 'undefined' ) { warnings[0] = []; warnings[0][0] = false; warnings[0][1] = 0; }
						var useWarning = warnings.length-1;
						if ( typeof warnings[useWarning][0] === 'string' ) {
							var t = warnings[useWarning][0];
							if ( t.indexOf ( 'block' ) > -1 ) { useWarning --; warnings[useWarning][1] = 0; }
						}
						
						// check when this warning was given
						for ( var compareMonth = 0; compareMonth < months.length; compareMonth ++ ) {
							if ( months[compareMonth] == warnings[useWarning][5] ) break;
						}
						var compareDate = new Date ();
						compareDate.setFullYear ( parseInt ( warnings[useWarning][6] ), compareMonth, parseInt ( warnings[useWarning][4] ) );
						compareDate.setHours ( parseInt ( warnings[useWarning][2] ) );
						compareDate.setMinutes ( parseInt ( warnings[useWarning][3] ) );
						var compareTime = compareDate.getTime ();
						
						// check if it is old enough to ignore for the purposes of incremental warnings
						var timeDiff = ( currentTime + ( currentDate.getTimezoneOffset () * 60 * 1000 ) ) - compareTime;
						if ( timeDiff > ( iglooSettings.warningsOldAfter * 24 * 60 * 60 * 1000 ) ) { warnings[useWarning][1] = 0; }
						
						// check whether a header already exists for the current month. if not, create one
						var currentHeader = new RegExp ( '={2,4} *' + months[currentMonth] + ' *' + currentYear + ' *={2,4}', 'gi' );
						if ( currentHeader.test ( pageData ) != true ) { var header = '== '+months[currentMonth]+' '+currentYear+' =='; } else { var header = false; }
					}
					
					// if the page does not  exist, we can simply set warnings at the default (lowest) levels
					else {
						// set up the warning and date header for addition to the user's page
						warnings[0] = []; warnings[0][0] = false; warnings[0][1] = 0;
						var header = '== '+months[currentMonth]+' '+currentYear+' ==';
						var useWarning = 0;
					}
					
					// decide upon which warning level to issue
					switch ( warnings[useWarning][1] ) {
						default: case 0:
							// use first level warning
							this.warningLevel = 1; break;
							
						case '1':
							// use second level warning
							this.warningLevel = 2; break;
							
						case '2':
							// use third level warning
							this.warningLevel = 3; break;
							
						case '3':
							// use final warning
							this.warningLevel = 4; break;
							
						case '4': case '4im':
							// perform report/block logic
							igloo.iglooStatus.addStatus ( 'Will not warn <strong>' + thisRevert.revertUser + '</strong> because they have already recieved a final warning.' );
							this.handleFinalWarning ();
							this.warningLevel = false; break;
					}
					
					// add the message to their talk page
					if (this.warningLevel == false) return false;
					
					var userPage = 'User_talk:' + this.revertUser;
					var message = iglooSettings.warningMessage;
						message = message.replace ( /%LEVEL%/g, this.warningLevel );
						message = message.replace ( /%PAGE%/g, this.revertPage );
						message = message.replace ( /%DIFF%/g, mw.config.get('wgServer') + mw.config.get('wgScript') + '?diff=' + this.revertRevid + '' );
						message = message.replace ( /%MESSAGE%/g, iglooSettings.vandalTemplate );
					var summary = iglooSettings.warningSummary;
						summary = summary.replace ( /%LEVEL%/g, this.warningLevel );
						summary = summary.replace ( /%PAGE%/g, this.revertPage );
					
					if ( header != false ) message = header + '\n' + message;
					
					var userWarning = new wa_mediawikiApi ();
					if ( iglooSettings.notifyWarningDone == true ) userWarning.onCompleteAction = function () { igloo.iglooStatus.addStatus( 'Successfully issued a level <strong>' + thisRevert.warningLevel + '</strong> warning to <strong>' + thisRevert.revertUser + '</strong> for vandalism on <strong>' + thisRevert.revertPage + '</strong>!' ); };
					userWarning.editPage ( userPage, message, summary, false, 'appendtext' );
					
					break;
					
				case 2:
					igloo.iglooStatus.addStatus ( 'Notifying <strong>' + thisRevert.revertUser + '</strong> of block (duration: ' + thisRevert.useduration + ')...' );
					var message = thisRevert.usetemplate;
						message = message.replace ( /%DURATION%/g, thisRevert.useduration );
					
						message = '\n\n{{' + message + '}}';
					var summary = 'Notifying user of block (['+'['+iglooSettings.localBase.substr(0, iglooSettings.localBase.length-1)+'|GLOO]'+'])';
					
					wa ( ':api' ).edit ( 'notifyblock', 'User_talk:' + thisRevert.revertUser, 0, message, summary, 'append' ).wait ( function () { 
						igloo.iglooStatus.addStatus ( 'Successfully notified <strong>' + thisRevert.revertUser + '</strong> of block!' );
					} ).run ();
					
					break;
			}
		}
		
		this.reportUser = function( callback, details ) {
			// handle reporting of the user to AIV
			//return false;
			switch ( callback ) {
				default: case 0:
					// notify user
					igloo.iglooStatus.addStatus ( 'Attempting to report <strong>' + thisRevert.revertUser + '</strong> to <strong>' + iglooSettings.aiv + '</strong> for vandalism after final warning...' );
				
					// get the aiv page
					var getAivPage = new wa_mediawikiApi ();
					getAivPage.onCompleteAction = function ( data ) { thisRevert.reportUser ( 1, data ); };
					getAivPage.getPage ( iglooSettings.aiv.replace ( / /g, '_' ), 1, 'content' );
					break;
					
				case 1:
					// check whether they are already reported (by a human)
					if ( typeof details['page']['revisions'] == 'undefined' ) {
						igloo.iglooStatus.addStatus ( 'Will not report <strong>' + thisRevert.revertUser + '</strong> because the report page does not appear to exist.' );
						return false; // error
					}
					var pageData = details['page']['revisions'][0]['content'];
					
					if ( pageData.indexOf ( '|' + thisRevert.revertUser + '}}' ) > -1 ) {
						igloo.iglooStatus.addStatus ( 'Will not report <strong>' + thisRevert.revertUser + '</strong> because they have already been reported.' );
						return false; // error
					}
					
					// check bots
					var getAivPage = new wa_mediawikiApi ();
					getAivPage.onCompleteAction = function ( data ) { thisRevert.reportUser ( 2, data ); };
					getAivPage.getPage ( iglooSettings.aiv.replace ( / /g, '_' ), 1, 'content' );
					break;
					
				case 2:
					// check whether they are already reported (by a bot)
					if ( typeof details['page']['revisions'] == 'undefined' ) {
						igloo.iglooStatus.addStatus( 'Will not report <strong>' + thisRevert.revertUser + '</strong> because the report page does not appear to exist.' );
						return false; // error
					}
					var pageData = details['page']['revisions'][0]['content'];
					
					if ( pageData.indexOf ( '|' + thisRevert.revertUser + '}}' ) > -1 ) {
						igloo.iglooStatus.addStatus( 'Will not report <strong>' + thisRevert.revertUser + '</strong> because they have already been reported.' );
						return false; // error
					}
					
					// build page link
					var aivLink = iglooSettings.aiv.replace ( / /g, '_' );
					
					// build the report
					var myReport = iglooSettings.aivMessage;
					if ( thisRevert.isIp === true ) { myReport = myReport.replace ( /%TEMPLATE%/g, iglooSettings.aivIp ); } else { myReport = myReport.replace ( /%TEMPLATE%/g, iglooSettings.aivUser ); }
					myReport = myReport.replace ( /%USER%/g, thisRevert.revertUser );
					
					// build the summary
					var mySummary = iglooSettings.aivSummary;
					mySummary = mySummary.replace ( /%USER%/g, thisRevert.revertUser );
					
					// perform the edit
					var userReport = new wa_mediawikiApi ();
					userReport.onCompleteAction = function () { igloo.iglooStatus.addStatus ( 'Successfully reported <strong>' + thisRevert.revertUser + '</strong> to AIV!' ); };
					userReport.editPage ( aivLink, myReport, mySummary, false, iglooSettings.aivWhere );
					
					break;
			}
		}
		
		this.blockUser = function ( callback, details ) {
			// handle blocking of users
			if ( typeof thisRevert.customsettings === 'undefined' ) thisRevert.customsettings = false;
			if ( iglooSettings.mesysop === false ) return false;
			
			switch ( callback ) {
				default: case 0:
					// notify user
					igloo.iglooStatus.addStatus( 'Attempting to block <strong>' + thisRevert.revertUser + '</strong> for vandalism after final warning...' );
					
					// analyse user
					wa ( ':api' ).command ( 'getblocks' , 'format=xml&action=query&list=logevents&letype=block&letitle=User:' + thisRevert.revertUser ).wait ( function ( details ) { thisRevert.blockUser ( 1, details ); } ).run ();
					
					break;
					
				case 1:
					// we now have access to the talk page data and the block log - redirect depending on whether we're autoblocking or not.
					if ( details ) if ( details === 'standard' ) { thisRevert.blockUser ( 5, 'userlock' ); break; }
					if ( thisRevert.isIp !== true ) { thisRevert.checked = { 'auto': 'checked ','talk': '','anon': '','email': '','create': 'checked ' }; thisRevert.useduration = 'indefinite'; thisRevert.usetemplate = 'subst:uw-voablock|time=%DURATION%|sig=yes'; thisRevert.blockReason = '[[WP:Vandalism-only account|Vandalism-only account]]'; thisRevert.blockUser ( 5, 'userlock' ); break; }
					thisRevert.blockUser ( 2 );
					break;
					
				case 2:
					// autoblocking - decide on the best warning
					// pick the best warning length
					
					var lastlength = 'neverblocked';
					thisRevert.useduration = iglooSettings.blockIncrement [ iglooSettings.blockDefault ];
					if ( wa ( ':api' ).results['getblocks']['query']['logevents'] !== null ) {
						if ( wa ( ':api' ).results['getblocks']['query']['logevents']['item'].length === undefined ) {
							// one result
							//alert('oneresult');
							if ( wa ( ':api' ).results['getblocks']['query']['logevents']['item-attr']['action'] == 'block' ) {
								lastlength = wa ( ':api' ).results['getblocks']['query']['logevents']['item']['block-attr']['duration'];
							} else { /* use default */ }
						} else {
							// many results
							//alert('many results');
							for ( var i = 0, l = wa ( ':api' ).results['getblocks']['query']['logevents']['item-attr'].length; i < l; i ++ ) {
								if ( wa ( ':api' ).results['getblocks']['query']['logevents']['item-attr'][i]['action'] == 'block' ) break;
							}
							if ( i === l ) { /* failed  alert('faaiiilllled');*/ thisRevert.blockUser ( 5, 'error' ); return false; }
							lastlength = wa ( ':api' ).results['getblocks']['query']['logevents']['item'][i]['block-attr']['duration'];
							//alert('check done, ll: ' + lastlength);
						}
					} else { /* use default */ }
					
					if ( lastlength !== 'neverblocked' ) {
						//alert('valid last length');
						var u = iglooSettings.blockIncrement.getPosition ( lastlength );
						if ( u !== false ) {
							if ( u < iglooSettings.blockDefault ) { 
								thisRevert.useduration = iglooSettings.blockIncrement [ iglooSettings.blockDefault ];
							} else {
								if ( u >= ( iglooSettings.blockIncrement.length - 1 ) ) u = iglooSettings.blockIncrement.length - 2;
								thisRevert.useduration = iglooSettings.blockIncrement [ u + 1 ];
							}
						} else { /* failed */ thisRevert.blockUser ( 5, 'error' ); return false; }
					}
					thisRevert.lastlength = lastlength;
					//alert('last length done');
					
					thisRevert.blockReason = '[[WP:Vandalism|Vandalism]]';
					
					// check for relevant templates on the user page
					thisRevert.usetemplate = iglooSettings.blockTypes ['default'];
					for ( var i in iglooSettings.blockTypes ) {
						if ( typeof i !== 'string' ) continue;
						
						var regTest = new RegExp ( '{{ *' + i, 'ig' );
						if ( ( thisRevert.userTalk !== undefined ) && ( regTest.test ( thisRevert.userTalk ) === true ) && ( i !== 'default' /* just in case there's a weird default template O_o */ ) ) {
							// show an error is misconfigured OR if the duration doesn't exist and hasn't caused an error yet
							if ( ( iglooSettings.blockIncrement.getPosition ( thisRevert.useduration ) === false ) || ( iglooSettings.blockIncrement.getPosition ( iglooSettings.blockSpecTemp ) === false ) ) { thisRevert.blockUser ( 5, 'error' ); return false; }
							if ( iglooSettings.blockIncrement.getPosition ( thisRevert.useduration ) >= iglooSettings.blockIncrement.getPosition ( iglooSettings.blockSpecTemp ) ) {
								//alert('using special template!');
								thisRevert.usetemplate = iglooSettings.blockTypes [i]; 
								if ( thisRevert.usetemplate.indexOf ( '|' ) > -1 )  {
									thisRevert.blockReason = '{{' + thisRevert.usetemplate.substr ( 0, thisRevert.usetemplate.indexOf ( '|' ) ) + '}}';
								} else { thisRevert.blockReason = '{{' + thisRevert.usetemplate + '}}'; }
								break;
							} else { /*alert('NOT using special template, as the block is too short!');*/ break; }
						}
					}
					
					// we now know the template to use, and we know the block length to use.
					//alert ( "igloo would block this user!!!\n\nduration: " + thisRevert.useduration + "\ntemplate: " + thisRevert.usetemplate );
					
					// manage the prompt
					var message = 'This user has already received a final warning. igloo intends to block the user with the following settings:<br /><br /> ' +
									'<span style="padding-left: 10px;">user: ' + thisRevert.revertUser + '<br />duration: ' + thisRevert.useduration + ' (last blocked for: ' + lastlength + ')<br />block template: ' + thisRevert.usetemplate +
									'</span><br /><br /><span id="igloo-just-do-block" style="cursor: pointer;">perform this block (recommended)</span> | <span id="igloo-adjust-block" style="cursor: pointer;">adjust block settings</span> | <span id="igloo-abort-block" style="cursor: pointer;">abort block (report user)</span>'
					igloo.iglooControls.getPermission ( message, false, true );
					
					// set the functions
					document.getElementById( 'igloo-just-do-block' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide (); igloo.iglooControls.userMessageContent.hide(); thisRevert.blockUser ( 6 ); }
					document.getElementById( 'igloo-adjust-block' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide (); igloo.iglooControls.userMessageContent.hide(); thisRevert.blockUser ( 5, 'adjusting' ); igloo.iglooStatus.addStatus( 'Adjusting block of <strong>' + thisRevert.revertUser + '</strong>: launching block interface...' ); }
					document.getElementById( 'igloo-abort-block' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide (); igloo.iglooControls.userMessageContent.hide(); thisRevert.reportUser (); igloo.iglooStatus.addStatus( 'Aborted block of <strong>' + thisRevert.revertUser + '</strong>: user aborted! Will now report...' ); }
					
					break;
					
				case 5:
					if ( details === 'error' ) {
						// something went wrong. Abort and report user.
						igloo.iglooStatus.addStatus ( 'Aborted block of <strong>' + thisRevert.revertUser + '</strong>: an error occurred! Will now report...' );
						thisRevert.reportUser (); 
						break;
					}
					
					// nothing went wrong. Display the adjust block system.
					// generate the dispaly elements
					if ( details === 'adjusting' ) {
						var disabled = 'disabled', lastlength = ' (last blocked for <strong>' + thisRevert.lastlength + '</strong>)';
						thisRevert.checked = { 'auto': '','talk': '','anon': 'checked','email': '','create': 'checked' };
					} else {
						var disabled = '', duration = '', lastlength = '';
						if ( thisRevert.revertUser == null ) thisRevert.revertUser = '';
						if ( thisRevert.useduration == null ) thisRevert.useduration = '';
						if ( thisRevert.usetemplate == null ) thisRevert.usetemplate = 'subst:uw-block|time=%DURATION%|sig=yes';
						if ( typeof thisRevert.blockReason == 'undefined' ) thisRevert.blockReason = '[[WP:Vandalism|Vandalism]]';
					}
					if ( details === 'userlock' ) disabled = 'disabled';
					
					var t = ''; // duration select
					for ( var i = 0; i < iglooSettings.blockIncrement.length; i ++ ) {
						t += '<option ';
						if ( iglooSettings.blockIncrement [i] == thisRevert.useduration ) t += 'selected ';
						t += 'value="' + iglooSettings.blockIncrement [i] + '">' + iglooSettings.blockIncrement [i] + '</option>';
					}
					
					var t2 = '';
					
					if ( typeof thisRevert.checked === 'undefined' ) { thisRevert.checked = { 'auto': '','talk': '','anon': '','email': '','create': '' }; }
					
					// output the display
					var content = '';
					content += '<div style="padding-left: 15px;"><span style="width: 100%; border-bottom: 1px solid #000; font-size: 16px; font-weight: bold;">Block user</span><br />You are blocking a user - select the block options from below. Remember that you are responsible for all blocks made using your account.';
					content += '<br /><br />';
					//content += '<div style="float: right; width: 200px; height: 100px;">' + t2 + '</div>';
					content += '<table style="border: none; width: 700px; background-color: #ccccff;">';
					content += '<tr><td width="140px">Username:</td><td><input id="iglooBlock-username" style="width: 200px;" type="text" ' + disabled + ' value="' + thisRevert.revertUser + '" />' + lastlength + '</td></tr>';
					content += '<tr><td width="140px">Duration:</td><td><select style="width: 205px;" id="iglooBlock-duration-a">' + t + '</select> (or type) <input id="iglooBlock-duration-b" style="width: 200px;" type="text" /></td></tr>';
					content += '<tr><td width="140px">Reason:</td><td><input id="iglooBlock-reason" style="width: 200px;" type="text" value="' + thisRevert.blockReason + '" /></td></tr>';
					content += '<tr><td width="140px">Notify with template (igloo will automatically add \'{{\' and \'}}\'): </td><td><input id="iglooBlock-template" style="width: 200px;" type="text" value="' + thisRevert.usetemplate + '" /></td></tr>';
					content += '<tr><td colspan="2" height="30px" style="margin-top: 30px; font-weight: bold;">Details:</td></tr>';
					content += '<tr><td colspan="2">';
						content += '<table width="150px" style="margin-left: 10px; background-color: #ccccff;"><tr>';
						content += '<td width="120px">Autoblock: </td><td><input  ' + thisRevert.checked ['auto'] + ' id="iglooBlock-autoblock" type="checkbox" /></td></tr><tr><td width="120px">Anon only: </td><td><input  ' + thisRevert.checked ['anon'] + ' id="iglooBlock-anononly" type="checkbox" /></td></tr><tr><td width="120px">Block acc create: </td><td><input  ' + thisRevert.checked ['create'] + ' id="iglooBlock-blockcreate" type="checkbox" /></td></tr><tr>';
						content += '<td width="120px">Block talk: </td><td><input  ' + thisRevert.checked ['talk'] + ' id="iglooBlock-blocktalk" type="checkbox" /></td></tr><tr><td width="120px">Block email: </td><td><input  ' + thisRevert.checked ['email'] + ' id="iglooBlock-blockemail" type="checkbox" /></td>';
						content += '</tr></table>';
					content += '</td></tr>';
					content += '<tr><td colspan="2" height="50px" style="margin-top: 50px; font-weight: bold;"><input id="igloo-finish-block" type="button" value="Block" onclick="igloo.iglooPopup.hide(); iglooSettings.dynamicBlockKeys = \'default\';" /> | <input type="button" value="Cancel" onclick="igloo.iglooPopup.hide(); iglooSettings.dynamicBlockKeys = \'default\';" /></td></tr>';
					content += '</table></span>';
					
					igloo.iglooPopup.show ( content );
					
					wa_attach ( document.getElementById ( 'igloo-finish-block' ), 'click', function () { 
						// set settings
						if ( document.getElementById ( 'iglooBlock-duration-b' ).value === '' ) { thisRevert.useduration = document.getElementById ( 'iglooBlock-duration-a' ).value; } else { thisRevert.useduration = document.getElementById ( 'iglooBlock-duration-b' ).value; }
						thisRevert.revertUser = document.getElementById ( 'iglooBlock-username' ).value;
						thisRevert.usetemplate = document.getElementById ( 'iglooBlock-template' ).value;
						thisRevert.blockReason = document.getElementById ( 'iglooBlock-reason' ).value;
						
						thisRevert.customsettings = '';
						if ( document.getElementById ( 'iglooBlock-autoblock' ).checked == true ) thisRevert.customsettings += '&autoblock=';
						if ( document.getElementById ( 'iglooBlock-blocktalk' ).checked != true ) thisRevert.customsettings += '&allowusertalk=';
						if ( document.getElementById ( 'iglooBlock-anononly' ).checked == true ) thisRevert.customsettings += '&anononly=';
						if ( document.getElementById ( 'iglooBlock-blockemail' ).checked == true ) thisRevert.customsettings += '&noemail=';
						if ( document.getElementById ( 'iglooBlock-blockcreate' ).checked == true ) thisRevert.customsettings += '&nocreate=';
						
						thisRevert.blockUser ( 6 );
						return true; // done!
					} );
					
					break;
					
				case 6:
					// DO THE BLOCK! Note that this function can be called even without reverting any page.
					if ( ( ! thisRevert.useduration ) || ( ! thisRevert.usetemplate ) || ( ! thisRevert.revertUser ) ) { if ( ! thisRevert.revertUser ) thisRevert.revertUser = 'no user supplied'; igloo.iglooStatus.addStatus( 'Aborted block of <strong>' + thisRevert.revertUser + '</strong>: an error occurred!' ); return false; }
					//alert (thisRevert.customsettings);
					if ( thisRevert.customsettings === false ) { // the block 'settings' haven't been altered (e.g. autoblock, block talk etc.), so use the default block settings.
						thisRevert.customsettings = ( thisRevert.isIp === true ) ? iglooSettings.anonBlockSettings : iglooSettings.userBlockSettings;
					}
					
					// do the actual block!
					igloo.iglooStatus.addStatus( 'Performing block of <strong>' + thisRevert.revertUser + '</strong>...' );
					wa ( ':api' ).block ( 'ig_doblock', thisRevert.revertUser, 0, thisRevert.useduration, thisRevert.blockReason, thisRevert.customsettings ).wait ( function () {
						var result = wa ( ':api' ).results['ig_doblock'];
						if ( typeof result['block'] !== 'undefined' ) { // success
							igloo.iglooStatus.addStatus( 'Successfully blocked <strong>' + thisRevert.revertUser + '</strong>!' );
							thisRevert.warnUser ( 2 );
						} else { // failure
							igloo.iglooStatus.addStatus( 'Failed to block <strong>' + thisRevert.revertUser + '</strong> - reason: ' + result['error']['info'] );
							return false;
						}
					}).run ();
					
					return true;
					break;
			}
		}
		
		this.handleFinalWarning = function ( callback ) {
			// this helper function is called when a user who already has a final warning is reverted. It decides what to do based on the user settings, and
			// is able to prompt for input if it is unsure.
			
			switch ( callback ) {
				default: case 0:
					// If we reach a final warning, remember that no further action is required if the user is already blocked!
					wa ( ':api' ).command ( 'currentlyblocked' , 'format=xml&action=query&list=blocks&bkusers=' + thisRevert.revertUser ).wait ( function () { thisRevert.handleFinalWarning ( 1 ); } ).run ();
					break;
			
				case 1:
					// If already blocked, tell and exit.
					if ( wa ( ':api' ).results['currentlyblocked']['query']['blocks'] !== null ) {
						igloo.iglooStatus.addStatus( 'igloo will take no further action because <strong>' + thisRevert.revertUser + '</strong> is currently blocked.' );
						return false;
					}
				
					// If you're not an admin, igloo won't let you choose. :) Also report if that's the preferred setting.
					if ( ( iglooSettings.mesysop === false ) || ( iglooSettings.blockAction === 'report' ) ) { thisRevert.reportUser (); return true; }
					
					// handle settings
					if ( iglooSettings.blockAction === 'prompt' ) {
						iglooSettings.blockAction = 'report'; // temporarily, so we don't see this screen again this session
						
						// manage the prompt
						var title = 'igloo needs you to choose your block settings'; 
						var message = 'Because you are an administrator, igloo can automatically block users on your behalf when you revert someone who has received a final warning. ' +
										'The igloo autoblocker will automatically choose and block with the most appropriate settings based on the user in question (you will still be prompted for permission to continue). ' +
										'If you want more control, the standard block setting will show you relevant data about the user and allow you to make the block yourself. Alternatively, igloo can simply report the user to AIV. What do you want to do?<br /><br />' +
										'&gt;&gt; <span id="igloo-set-block-auto" style="cursor: pointer;">igloo autoblock (recommended)</span> | <span id="igloo-set-block-standard" style="cursor: pointer;">standard block</span> | <span id="igloo-set-block-report" style="cursor: pointer;">report</span>'
						igloo.iglooControls.getPermission ( message, false, true, title );
						
						// set the functions
						document.getElementById( 'igloo-set-block-auto' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide (); igloo.iglooControls.userMessageContent.hide(); thisRevert.blockUser (); iglooSettings.blockAction = 'auto'; var url = iglooSettings.remoteHost + 'main.php?action=settings&do=set&session=' + igloo.iglooSession.session + '&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&setting=blockAction&value=auto'; iglooImport( url, true, 'iglooSettings' ); }
						document.getElementById( 'igloo-set-block-standard' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide (); igloo.iglooControls.userMessageContent.hide(); thisRevert.blockUser ( 0, 'standard' ); iglooSettings.blockAction = 'standard'; var url = iglooSettings.remoteHost + 'main.php?action=settings&do=set&session=' + igloo.iglooSession.session + '&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&setting=blockAction&value=standard'; iglooImport( url, true, 'iglooSettings' ); }
						document.getElementById( 'igloo-set-block-report' ).onclick = function () { igloo.iglooControls.blockActions = false; igloo.iglooControls.userMessage.hide (); igloo.iglooControls.userMessageContent.hide(); thisRevert.reportUser (); iglooSettings.blockAction = 'report'; var url = iglooSettings.remoteHost + 'main.php?action=settings&do=set&session=' + igloo.iglooSession.session + '&me=' + encodeURIComponent ( mw.config.get('wgUserName') ) + '&setting=blockAction&value=report'; iglooImport( url, true, 'iglooSettings' ); }
						
					} else if ( iglooSettings.blockAction === 'standard' ) {
						this.blockUser ( 0, 'standard' ); return true;
					} else if ( iglooSettings.blockAction === 'auto' ) {
						this.blockUser (); return true;
					}
					
					return false;
					break;
			}
		}
		
		// score object
		var userScore = igloo.iglooNet.scoreObject ( this.revertUser );
		
		// check whether this user is an IP address, or a registered user
		if ( this.revertUser.match ( /^[0-9]+\.[0-9]+\.[0-9]+\.?[0-9]*$/i ) !== null ) { this.isIp = true; this.checked = { 'auto': '','talk': '','anon': 'checked','email': '','create': 'checked' }; } else { this.isIp = false; this.checked = { 'auto': 'checked','talk': '','anon': '','email': '','create': 'checked' }; }
		
		// if we're using this for blocking, for example, don't perform default actions
		if ( this.revertPage === 0 ) return true;
		
		// checks
		if ( userScore[0] === false ) userScore[0] = iglooSettings.defaultUserScore;
		if ( ( this.revertUser == mw.config.get('wgUserName') ) && ( iglooSettings.promptRevertSelf === true ) ) {
			igloo.iglooControls.getPermission ( 'You are attempting to revert yourself. Ensure you wish to perform this action. igloo will not warn or report users who are reverting themselves.', function ( thisRevert ) { thisRevert.performRollback ( 0, 'cont' ); } ); 
		} else if ( userScore[0] < 0.4 ) { 
			igloo.iglooControls.getPermission ( 'You are attempting to revert a change that iglooNet believes is free from vandalism. Remember that igloo should only be used to revert <strong>blatant</strong> vandalism.', function ( page, user ) { thisRevert.performRollback ( page, user ); } ); 
		} else if ( igloo.iglooDiff.haschanged === true ) {
			igloo.iglooControls.getPermission ( 'The page you are viewing has changed since it was first loaded. Ensure that the change you were reverting has not already been fixed.', function ( page, user ) { thisRevert.performRollback ( page, user ); } ); 
		} else { this.performRollback ( page, this.revertUser ); }
	}
	
	
	
	
	function iglooPopup () {
		// igloo popup handles the general background interace for internal windows, such as the settings window, block adjustment window, custom warnings window, deletion window etc.
		this.start = function () {
			this.popupMenu = new wa_window ();
			this.popupMenu.win_alpha = 0.7;
			this.popupMenu.win_bg = '#000000';
			this.popupMenu.win_disp = 'none';
			this.popupMenu.win_maintfill = false;
			this.popupMenu.win_fill = true;
			this.popupMenu.applyAll ();
			if ( igloo.canDebug === true ) console.log ( 'igloo: created popup background' );
			
			this.popupMenuContent = new wa_window ();
			this.popupMenuContent.win_bg = '#ccccff';
			this.popupMenuContent.win_width = 800;
			this.popupMenuContent.win_height = 400;
			this.popupMenuContent.win_padding = 0;
			this.popupMenuContent.win_disp = 'none';
			this.popupMenuContent.win_bd = '#000000';
			this.popupMenuContent.win_bd_wd = 2;
			this.popupMenuContent.win_content = '<div>POPUP INIT</div>';
			this.popupMenuContent.applyAll ();
			if ( igloo.canDebug === true ) console.log ( 'igloo: created popup main' );
			
			return true;
		}
		
		this.show = function ( content ) {
			if ( content !== null ) {
				// content
				this.popupMenuContent.win_content = '<div>' + content + '</div>';
				this.popupMenuContent.applyAll ();
			}
			
			// show
			this.popupMenu.show ();
			this.popupMenuContent.show ();
			
			// center
			this.popupMenuContent.center ( 'both' );
		}
		
		this.hide = function () {
			this.popupMenu.hide ();
			this.popupMenuContent.hide ();
		}
	}
	
	function iglooStatus () {
		// the iglooStatus module handles the display and updating of the status window where we keep track of the actions we've taken for the user
		this.idCounter = 1;
		
		this.start = function() {
			this.startInterface();
		}
		
		this.startInterface = function() {
			var iglooCount = igloo.iglooNet.iglooCount;
			var iglooLoadTime = igloo.endload - igloo.startload;
			if ( iglooSettings.lastConnectIp !== 0 ) { var iglooLastConnection = 'You last connected from ' + iglooSettings.lastConnectIp + ', ' + iglooSettings.lastConnectTime; } else { var iglooLastConnection = ''; }
			
			this.intDisplay = new wa_window(igloo.iglooInterface);
			this.intDisplay.win_left = 190;
			this.intDisplay.win_top = parseInt(igloo.iglooInterface.win_obj.style.height) - 150;
			this.intDisplay.win_width = parseInt(igloo.iglooInterface.win_obj.style.width) - 200;
			this.intDisplay.win_height= 140;
			this.intDisplay.win_bg = '#ddddee'; // #'ededff';
			this.intDisplay.win_bd_tp = '1px solid #000000'; this.intDisplay.win_bd_lf = '1px solid #000000';
			this.intDisplay.win_padding = 5;
			this.intDisplay.win_content = '<div id="iglooStatusDisplay" style="width: 100%; height: 100%; overflow: auto; font-size: 12px;"><div id="statusObj_0" class="statusObj">Welcome to igloo! This is your status window, where you see the actions that igloo is taking on your behalf.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>stats:</strong> iglooNet currently has data on ' + iglooCount + ' unique entities.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>stats:</strong> igloo loaded in: ' + ( iglooLoadTime / 1000 ) + ' seconds.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>stats:</strong> ' + iglooLastConnection + '</div></div>'; //
			this.intDisplay.applyAll();
			
		}
		
		this.destroy = function() {
			this.intDisplay.hide();
		}
		
		this.addStatus = function(message) {
			var curDate = new Date();
				var sec = curDate.getSeconds();
				if ( sec < 10 ) sec = '0' + sec;
				var mins = curDate.getMinutes();
				if ( mins < 10 ) mins = '0' + mins;
				var hours = curDate.getHours();
				if ( hours < 10 ) hours = '0' + hours;
				var dateString = hours + ':' + mins + ':' + sec;
			
			var statusId = this.idCounter; this.idCounter ++;
			
			var newStatus = document.createElement('div');
			newStatus.id = 'statusObj_' + statusId;
			newStatus.className = 'statusObj';
			newStatus.innerHTML = '<span>' + dateString + ' - ' + message + '</span>';
			//var newStatusText = document.createTextNode(dateString + ' - ' + message);
			//newStatus.appendChild(newStatusText);
			
			var statusObj = document.getElementById('iglooStatusDisplay');
			statusObj.insertBefore(newStatus, statusObj.firstChild);
			
			return statusId;
		}
		
		this.updateStatus = function(id, message, action) { // action = 'append', 'prepend', 'overwrite' or 'destroy' - default 'append'
			if (document.getElementById('statusObj_' + id)) { var statusObj = document.getElementById('statusObj_' + id); } else { return false; }
			
			switch (action) {
				default: case 'append':
					statusObj.innerHTML = statusObj.innerHTML + message;
					return statusObj.innerHTML;
					break;
					
				case 'prepend':
					statusObj.innerHTML = message + statusObj.innerHTML ;
					return statusObj.innerHTML;
					break;
					
				case 'overwrite':
					statusObj.innerHTML = message;
					return statusObj.innerHTML;
					break;
					
				case 'destroy':
					statusObj.parentElement.removeChild(statusObj);
					return true;
					break;
			}
		}
	}
	
	
	
	
	// launch!
	var igloo = new iglooMain();
	igloo.launch();
//</nowiki>