	
// debug utils in a separate namespace.
var debug = {}
debug.log = function(msg) {
	try {
		console.log(msg);
	} catch(e) {
	}
}

/************************************************************************
 * A pair of functions to encrypt/decrypt strings. Put these in 
 * their own namespace so that the password is accessible but not
 * global.
 ************************************************************************/
var Crypt = {}
Crypt.pwd = null;
Crypt.newpwd = null;
Crypt.encrypt = function(value) {
	if (! Crypt.pwd || Crypt.pwd === '')
		return value;

	// Put the supplied string between square brackets and check when we
	// decrypt that they are there.
	encString = Tea.encrypt("[" + value + "]", Crypt.pwd);
	result = 'E:' + encString.length + ':' + encString;
	//debug.log("encrypt(" + value + ") -> " + result);
	return result;
}
Crypt.decrypt = function(value) {
	//	If the string was encrypted it will have a E: prefix. If it doesn't
	//	then just return it as it was supplied.
	var encString = value.replace(/^E:/, "");
	if (encString === value)
		return value;
	encStringLength = encString.replace(/:.*$/, '');
	encString = encString.replace(/^[^:]*:/, '');
	//debug.log('str = ' + encString + ' len = ' + encStringLength);

	if (Crypt.pwd)
		encString = Tea.decrypt(encString, Crypt.pwd);

	//	Check for square brackets. Fail if missing, remove if present.
	if (! encString.match(/^\[.*\]$/))
		return null;
	encString = encString.replace(/^\[/, '');
	encString = encString.replace(/\]$/, '');
	return encString;
}
Crypt.findEncryptedText = function(remainder) {
	var prefix = remainder.replace(/E:[0-9][0-9]*:.*$/, '');
	if (prefix === remainder)
		return null;
	var remainder = remainder.substring(prefix.length + 2);
	//var [lenStr, remainder] = remainder.split(':', 2);
	var lenAndRem = remainder.split(':', 2);
	var lenStr =lenAndRem[0];
	var remainder =lenAndRem[1];
	var len = parseInt(lenStr);
	var encString = remainder.substring(0, len);
	remainder = remainder.substring(len);
	encString = "E:" + lenStr + ":" + encString;
	return([prefix, encString, remainder]);
}
Crypt.isInputEncrypted = function(inputNode) {
	//return false;
	if (inputNode.name.match(/-cse$/)) {
		//debug.log("name match: " + inputNode.name);
		return true;
	}
	if (inputNode.id.match(/-cse$/)) {
		//debug.log("id match: " + inputNode.id);
		return true;
	}
	return false;
}


/************************************************************************
 * TextFilter class
 ************************************************************************/
var TextFilter = function(root, findMatch, transform, defaultText, log) {
	this._textNodes = [];

	/**
	* populate the supplied nodeList with all the text nodes
	* that lie under the supplied element and then recurse down.
	*/
	var getTextNodes = function(el, nodeList) {
		// skip TEXTAREA tags --- we clone them as inputs
		if (el.tagName === "TEXTAREA")
			return;
		var i = 0;
		var n = null;
		while (!!(n = el.childNodes[i++])) {
			if (n.nodeType !== 3) {
				getTextNodes(n, nodeList);
			} else {
				nodeList.push(n);
			}
		}
	}

	/**
	* Pushes pairs of matched text nodes and their unfiltered values onto
	* resultList.
	*/
	var findMatchingTextNodes = function(nodeList, resultList, findMatch,
		defaultText) {
		for (var i in nodeList) {
			var n = nodeList[i];
			var remainder = n.nodeValue;
			while (true) {
				var matches = findMatch(remainder);
				if (! matches)
					break;
				remainder = matches[0];

				var encNode = document.createTextNode(
					defaultText ? defaultText : matches[1]);
				n.nodeValue = remainder;
				n.parentNode.insertBefore(encNode, n);
				n.parentNode.insertBefore(
					document.createTextNode(matches[2]), encNode);
				resultList.push([encNode, matches[1]]);
			}
		}
	}

	// Apply the supplied transform function to all nodes and nodeValues
	// provided it succeeds for all text nodes.
	// @return true if all text fields updated correctly, false otherwise.
	this.transformAll = function() {
		var newValues = [];
		for (var i=0; i<this._textNodes.length; i++) {
			var val = transform(this._textNodes[i][1]);
			if (val === null) {
				//log("transform failed for string " + i + " (" + this._textNodes[i][1] + ")");
				return false;
			}
			newValues.push(val);
		}
		for (var i=0; i<this._textNodes.length; i++)
			//this._textNodes[i][0].nodeValue = '[' + newValues[i] + ']';
			this._textNodes[i][0].nodeValue = newValues[i];
		return true;
	}

	// Prepare a list of all the text nodes in the BODY of the page.
	var allTextNodes = [];
	getTextNodes(root, allTextNodes);

	// Filter this to get the nodes whose text is to be filtered.
	findMatchingTextNodes(allTextNodes, this._textNodes, findMatch, defaultText);
	//log("found " + this._textNodes.length + " strings");
}

/************************************************************************
 * FormFilter class
 ************************************************************************/
var FormFilter = function(root, transform, revert, filterInput, log) {
	this._inputs = [];

	var getTextInputNodes = function(el, nodeList) {
		var i = 0;
		var n = null;
		while (!!(n = el.childNodes[i++])) {
			if (n.nodeType === 1 && 
				(n.tagName === "TEXTAREA" ||
				(n.tagName === "INPUT" && n.type === "text")))
				// If a filter is specified and this input doesn't pass,
				// don't add it in the nodeList.
				if (! filterInput || filterInput(n))
					nodeList.push(n);
			if (n.nodeType !== 3)
				getTextInputNodes(n, nodeList);
		}
	}

	var cloneInput = function(input, transform, revert) {
		// Clone the supplied input, clear its name so it won't get
		// submitted, clear its id so it doesn't conflict with the original
		// input, give it a default value, disable it and insert it before
		// the original in the DOM.
		var clone = input.cloneNode(true);
		clone.setAttribute('name', '');
		clone.id = null;
		clone.value = 'ENCRYPTED';
		clone.disabled = true;
		clone.className += " cse-form-filter";
		if (clone.addEventListener) {
			clone.addEventListener('blur', function() {
				input.value = transform(input.clone.value);
				}
			, false);
		} else if (clone.attachEvent) {
			clone.attachEvent('onblur', function() {
				input.value = transform(input.clone.value);
				});
		}

		input.parentNode.insertBefore(clone, input);

		input.clone = clone;
		// make the input not display. Unfortunately we can't disable it
		// or it won't get included when we submit the form.
		input.style.display = "none";
		//input.disabled = true;
		return clone;
	}

	//	Get a list of all the text _inputs that we want to clone...
	getTextInputNodes(root, this._inputs);
	// TODO apply a filter to reduce these to just those we want to 

	//	and clone them giving each one a reference to the supplied
	//	transform and revert functiions.
	for (var i=0; i<this._inputs.length; i++)
		cloneInput(this._inputs[i], transform, revert);

	//log("cloned " + this._inputs.length + " inputs");

	// Apply the supplied function to all _inputs provided it succeeds for
	// all _inputs.
	// @return true if all _inputs updated correctly, false otherwise.
	this.revertAll = function() {
		var newValues = [];
		for (var i=0; i<this._inputs.length; i++) {
			var val = revert(this._inputs[i].value);
			if (val === null) {
				//log("revert failed for input " + i);
				return false;
			}
			newValues.push(val);
		}
		for (var i=0; i<this._inputs.length; i++)
			this._inputs[i].clone.value = newValues[i];
		return true;
	}
	this.transformAll = function() {
		var newValues = [];
		for (var i=0; i<this._inputs.length; i++) {
			var val = transform(this._inputs[i].clone.value);
			if (val === null) {
				//log("transform failed for input " + i);
				return false;
			}
			newValues.push(val);
		}
		for (var i=0; i<this._inputs.length; i++)
			this._inputs[i].value = newValues[i];
		return true;
	}
	this.enableAll = function(on) {
		for (var i=0; i<this._inputs.length; i++)
			this._inputs[i].clone.disabled = (! on);
	}
};

/************************************************************************
 * Cryptor class
 ************************************************************************/
var Cryptor = function() {
	this.statusMsg = document.createTextNode("");
	this.debugMessage = document.createTextNode("");

	var dmsg = function(textNode) {
		return function(msg) {
			textNode.nodeValue += ":" + msg;
		}}(this.debugMessage);

	//	Build a TextFilter...
	var body = document.getElementsByTagName('BODY')[0];
	var textFilter = new TextFilter(body, Crypt.findEncryptedText,
			Crypt.decrypt, "ENCRYPTED", dmsg);

	//	and a FormFilter...
	var area = document.getElementById('content-area');
	var formFilter = new FormFilter(area, Crypt.encrypt, Crypt.decrypt,
		Crypt.isInputEncrypted, dmsg);

	//	If there are no encyrpted elements on this page, just return.
	if (formFilter._inputs.length === 0 &&
		textFilter._textNodes.length === 0)
		return;

	//	and try to revert all the fields straight away. If we can
	//	then enableAll() the fields in the formFilter.
	if (textFilter.transformAll() && formFilter.revertAll()) {
		formFilter.enableAll(true);
		Crypt.pwd = '';
	} else {
		if (textFilter._textNodes.length > 0 &&
			formFilter._inputs.length > 0)
			this.statusMsg.nodeValue =
				"Enter the encryption password to view or edit this page";
		else if (textFilter._textNodes.length > 0)
			this.statusMsg.nodeValue =
				"Enter the encryption password to view this page";
		else if (formFilter._inputs.length > 0)
			this.statusMsg.nodeValue =
				"Enter the encryption password to edit this page";
	}

	/**
	 * General purpose handler for keypress events that will call the
	 * supplied handler if the key pressed was Enter.
	 */
	var handleEnterKey = function(e, control, handler) {
		if (! handler)
			return;
		key = window.event ? window.event.keyCode : e.which;
		if (key != 13)
			return;
		handler(control);
	}

	/**
	 * Handle newly entered password.
	 */
	this.submitPassword = function(password, allowChanges) {
		// If no password is set at all then we need one to get started.
		if (Crypt.pwd == null) {
			Crypt.pwd = password;
			if (textFilter.transformAll() && formFilter.revertAll()) {
				formFilter.enableAll(true);
				this.statusMsg.nodeValue = "OK";
			} else {
				this.statusMsg.nodeValue = "Password incorrect";
				Crypt.pwd = null;
			}
			return;
		}
		// If we don't allow password changes then just return here.
		if (! allowChanges)
			return;
		// OK, this is a password change. But if it's the same as before
		// just ignore it.
		if (Crypt.pwd === password)
			return;

		// If we don't have a newpwd yet, save it and prompt for it to be
		// reentered.
		if (Crypt.newpwd == null) {
			this.statusMsg.nodeValue =
				"New password? Please retype to confirm";
			Crypt.newpwd = password;
			return;
		}

		if (Crypt.newpwd !== password) {
			this.statusMsg.nodeValue =
				"Second password didn't match - not changing";
			Crypt.newpwd = null;
			return;
		}
		var oldpwd = Crypt.pwd;
		Crypt.pwd = password;
		Crypt.newpwd = null;
		if (! formFilter.transformAll()) {
			this.statusMsg.nodeValue = "Password change failed";
			Crypt.pwd = oldpwd;
			return;
		}
		this.statusMsg.nodeValue =
			"New password accepted - I'll use it when you submit this form";
	}

	// This is a bit poor --- ie6 won't let us define listeners for
	// elements that haven't been fully constructed as 'this' is undefined.
	// So we have to create the INPUT first...
	var pwdInput = createTag(
			'INPUT', [
			[ 'type', 'password' ],
			[ 'style', 'color: red;' ],
			[ /*'keypress',
				function(dialog) {
					return function(e) {
						if (this.value.length === 0)
							dialog.statusMsg.nodeValue = "";
						handleEnterKey(e, this,
							function(control) {
								dialog.submitPassword(control.value,
									formFilter._inputs.length > 0);
								control.value = '';
							}
						)
					}}(this) */ ]
			]);

	// Then create a listener...
	var pwdListener = function(dialog) {
		return function(e) {
			if (pwdInput.value.length === 0)
				dialog.statusMsg.nodeValue = "";
			handleEnterKey(e, pwdInput,
				function(control) {
					dialog.submitPassword(control.value,
						formFilter._inputs.length > 0);
					control.value = '';
				}
			)
		}}

	// And then tie them together.
	if (pwdInput.addEventListener) {
		pwdInput.addEventListener("keypress", pwdListener(this), false);
	} else if (pwdInput.attachEvent) {
		pwdInput.attachEvent("onkeypress", pwdListener(this));
	}

	// Create and style the elements of the authentication bar.
	var authenticationBar = createTag(
		'DIV', [
		[ 'id', 'cse-authentication-bar' ] ], [
		pwdInput,
		createTag(
			'SPAN', [ ], [
				this.statusMsg,
				this.debugMessage
			]
		)
	]);

	var body = document.getElementsByTagName('BODY')[0];
	body.appendChild(authenticationBar);

	return;
}


	var createTag = function(tagStr, attrList, childList) {
		var tag = document.createElement(tagStr); // e.g. a DIV
		if (! attrList)
			return tag;
		for (var i=0; i<attrList.length; i++) {
			if (typeof(attrList[i][1]) === 'function') {
				if (tag.addEventListener)
					tag.addEventListener(attrList[i][0], attrList[i][1], false);
				else if (tag.attachEvent)
					tag.attachEvent("on" + attrList[i][0], attrList[i][1]);

			} else
				tag.setAttribute(attrList[i][0], attrList[i][1]);
		}
		if (! childList)
			return tag;
		for (i=0; i<childList.length; i++) {
			tag.appendChild(childList[i]);
		}
		return tag;
	}

if (Drupal.jsEnabled) {
  //$(document).ready(CSE.attachjs);
  $(document).ready(Cryptor);
}
function initializePage() {
	Cryptor();
}


