function RevisionTracker(oOptions)
{
	this.opt = oOptions;
	this.bInReviseMode = false;
	this.sCurMessageId = '';
	this.oCurMessageDiv = null;
	this.oCurSubjectDiv = null;
	this.oCurIcon = null;
	this.oRevisionSubject = null;
	this.oRevisionDiv = null;
	this.bXmlHttpCapable = this.isXmlHttpCapable();
	
	this.initTracker();
	
	this.oMotion = new Motion({iTimerLength: 5, iSlideTime: 100});
}

RevisionTracker.prototype.initTracker = function()
{
	if (!this.bXmlHttpCapable)
		return;
	
	// Create the div and insert it into the page (this is great as it will work on ANY theme), and is easily customizeable in the display template
	document.write(this.opt.sRevisionBox);	
	document.write(this.opt.sCompareBox);

	this.oRevisionSubject = document.getElementById('revision_subject');
	this.oRevisionDiv = document.getElementById('revisions');
}

// Determine whether the Revisioner can actually be used.
RevisionTracker.prototype.isXmlHttpCapable = function ()
{
	if (typeof(window.XMLHttpRequest) == 'undefined')
		return false;

	// Opera didn't always support POST requests. So test it first.
	if (typeof(window.opera) != 'undefined')
	{
		var test = new XMLHttpRequest();
		if (typeof(test.setRequestHeader) != 'function')
			return false;
	}
	
	if (is_ie6down)
		return false;

	return true;
}

// Function called when the user hits the revisions link/icon
RevisionTracker.prototype.showRevisions = function(iMessageId)
{
	if (!this.bXmlHttpCapable)
		return;
	
	if (this.bInReviseMode)
		return this.showRevisionsCancel();
	
	this.bInReviseMode = true;
	
	ajax_indicator(true);

	this.tmpMethod = getXMLDocument;
	this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=revision;topic=' + this.opt.iTopicId + '.0;msg=' + iMessageId + ';xml', this.onRevisionsRecieved);
	delete this.tmpMethod;
	
	return false;
}

RevisionTracker.prototype.onRevisionsRecieved = function(oXMLDoc)
{
	ajax_indicator(false);
	
	if(oXMLDoc.getElementsByTagName('errors').length != 0 || oXMLDoc.getElementsByTagName('revision').length == 0)
		return this.showRevisionsCancel();
	
	// Grab the message ID.
	this.sCurMessageId = oXMLDoc.getElementsByTagName('message')[0].getAttribute('id');

	// If this is not valid then simply give up.
	if (!document.getElementById(this.sCurMessageId))
		return this.showRevisionsCancel();

	// Setup our variables...
	this.oCurMessageDiv = document.getElementById(this.sCurMessageId);
	this.oCurSubjectDiv = document.getElementById('subject_' + this.sCurMessageId.substr(4));
	this.oCurIcon = document.getElementById('msg_icon_' + this.sCurMessageId.substr(4));
	var sRevisionList = '';
	var id_revision, prev_id_revision, id_member, poster, email, ip, timestamp, show_revise_icon;
	var oRevisions = oXMLDoc.getElementsByTagName('revision');
	
	sRevisionList += '<div><input id="compare_button" type="button" onclick="oRevisionTracker.compareRevision(\'' + this.sCurMessageId.substr(4) + '\',oRevisionTracker.getSelectedVersion(\'old\'), oRevisionTracker.getSelectedVersion(\'new\'))" value="' + this.opt.sCompareSelected + '" style="margin-bottom: 5px;" class="smalltext" /></div><ul id="revision_list">';
	
	for (i = 0; i < oRevisions.length; i++)
	{
		id_revision = oRevisions[i].getAttribute('id');
		prev_id_revision = (oRevisions[i+1]) ? oRevisions[i+1].getAttribute('id') : null;
		id_member = oRevisions[i].getElementsByTagName('id_member')[0].childNodes[0].nodeValue;
		poster = oRevisions[i].getElementsByTagName('poster')[0].childNodes[0].nodeValue;
		email = oRevisions[i].getElementsByTagName('email')[0].childNodes[0].nodeValue;
		ip = oRevisions[i].getElementsByTagName('ip')[0].childNodes[0].nodeValue;
		timestamp = oRevisions[i].getElementsByTagName('timestamp')[0].childNodes[0].nodeValue;
		show_revise_icon = in_array(this.sCurMessageId.substr(4), this.opt.aCanModify);
		
		sRevisionList += '<li class="revision" id="rev_' + id_revision + '"><ul>';
		if (show_revise_icon)
			sRevisionList += '<li><img alt="Restore" title="Restore" class="pointer" src="' + smf_images_url + '/restore.gif" onclick="oRevisionTracker.restoreRevision(\'' + this.sCurMessageId.substr(4) + '\',\'' + id_revision + '\')" /></li>';
		sRevisionList += '<li><img alt="Compare with current" title="Compare with current" class="pointer" src="' + smf_images_url + '/compare_curr.gif" onclick="oRevisionTracker.compareRevision(\'' + this.sCurMessageId.substr(4) + '\',\'' + id_revision + '\', null)" /></li>';
		sRevisionList += '<li>' + (prev_id_revision != null ? '<img alt="Compare with previous" title="Compare with previous" class="pointer" src="' + smf_images_url + '/compare_prev.gif" onclick="oRevisionTracker.compareRevision(\'' + this.sCurMessageId.substr(4) + '\',\'' + prev_id_revision + '\',\'' + id_revision + '\')" />' : '&nbsp;') + '</li>';
		sRevisionList += '<li><input type="radio" name="oldid" value="' + id_revision + '" ' + (i == 1 ? 'checked="checked" ' : '') + '/></li>';
		sRevisionList += '<li><input type="radio" name="newid" value="' + id_revision + '" ' + (i == 0 ? 'checked="checked" ' : '') + '/></li>';
		sRevisionList += '<li class="time smalltext">' + timestamp + '</li>';
		sRevisionList += '<li class="user smalltext">' + (id_member != 0 ? '<a href="' + smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=profile;u=' + id_member + '">' + poster + '</a>' : poster) + '</li>';
		sRevisionList += '</ul></li>';
	}

	sRevisionList += '</ul>';
	
	// Insert the revision list
	setInnerHTML(this.oRevisionDiv, sRevisionList);
	// Update the title of the box
	setInnerHTML(this.oRevisionSubject, this.opt.sRevisionBoxTitle.replace(/%subject%/, getInnerHTML(this.oCurSubjectDiv)));
	
	this.oMotion.slideUp('revision_tracker');
	this.compareInit();
}

RevisionTracker.prototype.showRevisionsCancel = function()
{
	// Empty everything out and close 'er down
	ajax_indicator(false);
	this.oMotion.slideDown('revision_tracker');
	setInnerHTML(this.oRevisionDiv, '');
	setInnerHTML(this.oRevisionSubject, '');
	this.bInReviseMode = false;
	this.sCurMessageId = '';
	this.oCurMessageDiv = null;
	this.oCurSubjectDiv = null;
	this.oCurIcon = null;
	
	return false;
}

RevisionTracker.prototype.restoreRevision = function (iMessageId, iRevisionId)
{
	// This should be easy enough...
	ajax_indicator(true);

	this.tmpMethod = getXMLDocument;
	this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=revision;sa=restore;topic=' + this.opt.iTopicId + '.0;msg=' + iMessageId + ';revision=' + iRevisionId + ';' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';xml', this.onRestoreReceived);
	delete this.tmpMethod;
}

RevisionTracker.prototype.onRestoreReceived = function (oXMLDoc)
{
	// We've finished the loading stuff.
	ajax_indicator(false);

	// If we didn't get a valid document, just cancel.
	if (!oXMLDoc || !oXMLDoc.getElementsByTagName('smf')[0])
		return;

	var message = oXMLDoc.getElementsByTagName('smf')[0].getElementsByTagName('message')[0];
	var body = message.getElementsByTagName('body')[0];
	var error = message.getElementsByTagName('error')[0];

	if (error)
		return;
	else if (body)
	{
		// Show new body.
		var bodyText = '';
		for (i = 0; i < body.childNodes.length; i++)
			bodyText += body.childNodes[i].nodeValue;

		this.sMessageBuffer = this.opt.sTemplateBodyNormal.replace(/%body%/, bodyText.replace(/\$/g, '{&dollarfix;$}')).replace(/\{&dollarfix;\$\}/g,'$');
		setInnerHTML(this.oCurMessageDiv, this.sMessageBuffer);

		// Show new subject.
		var oSubject = message.getElementsByTagName('subject')[0];
		var sSubjectText = oSubject.childNodes[0].nodeValue.replace(/\$/g, '{&dollarfix;$}');
		this.sSubjectBuffer = this.opt.sTemplateSubjectNormal.replace(/%msg_id%/g, this.sCurMessageId.substr(4)).replace(/%subject%/, sSubjectText).replace(/\{&dollarfix;\$\}/g,'$');
		setInnerHTML(this.oCurSubjectDiv, this.sSubjectBuffer);
		
		// Update the icon
		if (this.oCurIcon != null)
			this.oCurIcon.src = message.getElementsByTagName('icon')[0].childNodes[0].nodeValue;

		// If this is the first message, also update the topic subject.
		if (oSubject.getAttribute('is_first') == '1')
			setInnerHTML(document.getElementById('top_subject'), this.opt.sTemplateTopSubject.replace(/%subject%/, sSubjectText).replace(/\{&dollarfix;\$\}/g, '$'));

		// Show this message as 'modified on x by y'.
		if (this.opt.bShowModify)
			setInnerHTML(document.getElementById('modified_' + this.sCurMessageId.substr(4)), message.getElementsByTagName('modified')[0].childNodes[0].nodeValue);
	}
	
	this.showRevisionsCancel();
}

RevisionTracker.prototype.compareRevision = function (iMessageId, iOldId, iNewId)
{
	// First off, if iOldId is null, we'll just cancel this...
	if (iOldId == null)
		return;

	// This should be easy enough...
	ajax_indicator(true);
	
	this.tmpMethod = getXMLDocument;
	this.tmpMethod(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=revision;sa=compare;topic=' + this.opt.iTopicId + '.0;msg=' + iMessageId + ';old=' + iOldId + (iNewId != null ? ';new=' + iNewId : '') + ';xml', this.onCompareReceived);
	delete this.tmpMethod;
}

RevisionTracker.prototype.onCompareReceived = function (oXMLDoc)
{
	ajax_indicator(false);

	var lines = oXMLDoc.getElementsByTagName('line');
	var error = oXMLDoc.getElementsByTagName('errors')[0];

	if (error)
		return;
	else if(lines)
	{
		// Get the times for the title box...
		var sOldTime = oXMLDoc.getElementsByTagName('old')[0].childNodes[0].nodeValue;
		var sNewTime = oXMLDoc.getElementsByTagName('new')[0].childNodes[0].nodeValue;
		
		// Update the title of the compare box
		var oCompareTitle = document.getElementById('compare_subject');
		setInnerHTML(oCompareTitle, this.opt.sCompareBoxTitle.replace(/%old%/, '<em>' + sOldTime + '</em>').replace(/%new%/, '<em>' + sNewTime + '</em>'));
		
		// Put all of the data into a string
		var sCompareData = '<dl id="comparison">';
		for (i = 0; i < lines.length; i++)
		{
			sCompareData += '<dt>' + (lines[i].getAttribute('id') != '' ? lines[i].getAttribute('id') : '&nbsp;') + '</dt>'
			sCompareData += '<dd class="' + lines[i].getAttribute('class') + '">' + lines[i].childNodes[0].nodeValue + '</dd>';
		}
		sCompareData += '</dl>';
		
		// Update the compare box...
		var oCompareDiv = document.getElementById('compare');
		setInnerHTML(oCompareDiv, sCompareData);
		
		// Hide the revision tracker, and show the comparison box
		this.oMotion.slideDown('revision_tracker');
		setTimeout('oRevisionTracker.oMotion.slideUp(\'compare_box\')', this.oMotion.iSlideTime);
	}
}

RevisionTracker.prototype.showCompareCancel = function  ()
{
	// Update the compare box...
	var oCompareDiv = document.getElementById('compare');
	var oCompareTitle = document.getElementById('compare_subject');
	setInnerHTML(oCompareDiv, '');
	setInnerHTML(oCompareTitle, '');
	this.oMotion.slideDown('compare_box');
	setTimeout('oRevisionTracker.oMotion.slideUp(\'revision_tracker\')', this.oMotion.iSlideTime);

}

RevisionTracker.prototype.getSelectedVersion = function (sName)
{
	if (sName != 'old' && sName != 'new')
		return false;
	
	var inputs = document.getElementById('revision_list').getElementsByTagName('input');
	for (var i = 0; i < inputs.length; i++)
		if (inputs[i].type == 'radio' && (inputs[i].name == sName + 'id') && inputs[i].checked)
			return inputs[i].value;
}

// The following three functions fall under GPL.  They have been taken from MediaWiki and modified to work properly
// and for this mod.
RevisionTracker.prototype.compareRadios = function (parent)
{
	var inputs = parent.getElementsByTagName('input');
	var radios = [];
	for (var i = 0; i < inputs.length; i++) {
		if (inputs[i].name == "oldid" || inputs[i].name == "newid") {
			radios[radios.length] = inputs[i];
		}
	}
	return radios;
}

// check selection and tweak visibility/class onclick
RevisionTracker.prototype.compareCheck = function ()
{
	var newId = false; // the li where the diff radio is checked
	var oldId = false; // the li where the oldid radio is checked

	var revisions = document.getElementById('revision_list').getElementsByTagName('ul');
	for (var i = 0; i < revisions.length; i++) {
		var inputs = oRevisionTracker.compareRadios(revisions[i]);
		if (inputs[0] && inputs[1])
		{
			// this row has a checked radio button
			if (inputs[0].checked || inputs[1].checked)
			{
				// If they're both checked, and their input is the same, return false
				if (inputs[1].checked && inputs[0].checked && inputs[0].value == inputs[1].value)
					return false;
				// We've already found the first checked one (on the right), let's work the second
				if (oldId)
				{
					if (inputs[1].checked)
					{
						if ((typeof oldId.className) != 'undefined')
							oldId.parentNode.classNameOriginal = oldId.parentNode.className.replace('active ', '');
						else
							oldId.parentNode.classNameOriginal = 'revision';
						
						oldId.parentNode.className = 'active ' + oldId.parentNode.classNameOriginal;
						return false;
					}
				}
				else if (inputs[0].checked)
					return false;

				if (inputs[0].checked)
					newId = revisions[i];

				if (!oldId)
					inputs[0].style.visibility = 'hidden';

				if (newId)
					inputs[1].style.visibility = 'hidden';

				if ((typeof revisions[i].parentNode.className) != 'undefined')
					revisions[i].parentNode.classNameOriginal = revisions[i].parentNode.className.replace('active ', '');
				else
					revisions[i].parentNode.classNameOriginal = 'revision';
						
				revisions[i].parentNode.className = 'active ' + revisions[i].parentNode.classNameOriginal;
				oldId = revisions[i];
			}
			else
			{
				if (!oldId)
					inputs[0].style.visibility = 'hidden';
				else
					inputs[0].style.visibility = 'visible';
				if (newId)
					inputs[1].style.visibility = 'hidden';
				else
					inputs[1].style.visibility = 'visible';

				revisions[i].parentNode.className = 'revision';
			}
		}
	}
	return true;
}

// Revision history... 
RevisionTracker.prototype.compareInit = function ()
{
	document.getElementById('compare_button').style.display = '';
	var inputs = document.getElementById('revision_list').getElementsByTagName('input');
	for (var i = 0; i < inputs.length; i++)
		if (inputs[i].type == 'radio')
		{
			inputs[i].onclick = oRevisionTracker.compareCheck;
			inputs[i].style.display = '';
		}

	this.compareCheck();
}

function Motion(oOptions)
{
	this.iTimerLength = oOptions.iTimerLength;
	this.iSlideTime = oOptions.iSlideTime;
	this.aTimerId = new Array();
	this.aStartTime = new Array();
	this.aObject = new Array();
	this.aEndHeight = new Array();
	this.aMoving = new Array();
	this.aDirection = new Array();
}

Motion.prototype.slideDown = function(sObject)
{
  if(this.aMoving[sObject])
    return;

  if(document.getElementById(sObject).style.display == 'none')
    return; // cannot slide down something that is already hidden
 
  this.aMoving[sObject] = true;
  this.aDirection[sObject] = 'down';
  this.startSlide(sObject);
}

Motion.prototype.slideUp = function (sObject)
{
  if(this.aMoving[sObject])
    return;
 
  if(document.getElementById(sObject).style.display != 'none')
    return; // cannot slide up something that is already visible
 
  this.aMoving[sObject] = true;
  this.aDirection[sObject] = 'up';
  this.startSlide(sObject);
}

Motion.prototype.startSlide = function (sObject)
{
  this.aObject[sObject] = document.getElementById(sObject);
 
  this.aEndHeight[sObject] = parseInt(this.aObject[sObject].style.height);
  this.aStartTime[sObject] = (new Date()).getTime();
 
  if(this.aDirection[sObject] == 'up'){
    this.aObject[sObject].style.height = '1px';
  }
 
  this.aObject[sObject].style.display = 'block';
 
  this.aTimerId[sObject] = setInterval('oRevisionTracker.oMotion.slideTick(\'' + sObject + '\');', this.iTimerLength);
}

Motion.prototype.slideTick = function (sObject)
{
  var elapsed = (new Date()).getTime() - this.aStartTime[sObject];
 
  if (elapsed > this.iSlideTime)
    this.endSlide(sObject)
  else {
    var d = Math.round(elapsed / this.iSlideTime * this.aEndHeight[sObject]);
    if(this.aDirection[sObject] == 'down')
      d = this.aEndHeight[sObject] - d;
 
    this.aObject[sObject].style.height = d + 'px';
  }
 
  return;
}

Motion.prototype.endSlide = function (sObject)
{
  clearInterval(this.aTimerId[sObject]);
 
  if(this.aDirection[sObject] == 'down')
    this.aObject[sObject].style.display = 'none';
 
  this.aObject[sObject].style.height = this.aEndHeight[sObject] + 'px';
 
  delete(this.aMoving[sObject]);
  delete(this.aTimerId[sObject]);
  delete(this.aStartTime[sObject]);
  delete(this.aEndHeight[sObject]);
  delete(this.aObject[sObject]);
  delete(this.aDirection[sObject]);
 
  return;
}
