/*
 * Bork.js
 *
 * The Swedish Chef-alizer
 *
 * Based on John Hagerman's original lex description.
 *
 * Copyright 2005, 2006, Anthony Howe <achowe@snert.com>.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 *   - Neither the name of the copyright holder nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

function Bork(string)
{
	this.string = string;
}

function Bork_transformWindow(rootWindow)
{
	if (rootWindow == null)
		rootWindow = window;

	var f = rootWindow.frames;
	for (var i = 0; i < f.length; i++) {
		Bork.transformPage(f[i].document);
	}

	Bork.transformPage(rootWindow.document);
}

function Bork_transformPage(rootDocument)
{
	if (rootDocument == null)
		rootDocument = document;

	var theBody = rootDocument.getElementsByTagName('body').item(0);

	/* Prevent repeated transformations of the same document
	 * triggered by multiple onLoad events, typically as a
	 * result of loading a framed document. This variable
	 * never changes. Its assumed that a new page load will
	 * result in a new document object.
	 */
	if (theBody == null || (typeof(theBody.borkMarked) == 'boolean' && theBody.borkMarked))
		return;

	theBody.borkMarked = true;

//	if (document.implementation.hasFeature("Traversal", 2.0)) {
//		var node;
//		var iter = document.createNodeIterator(theBody, NodeFilter.SHOW_TEXT, null, false);
//		while ((node = iter.nextNode()) != null)
//			node.nodeValue = Bork.transformString(node.nodeValue);
//	} else {
		Bork_treeWalkNodes(theBody);
//	}
}

function Bork_treeWalkNodes(node)
{
	/* A prefix tree walk. */
	for ( ; node != null; node = node.nextSibling) {
		switch (node.nodeType) {
		case 3:
		case node.TEXT_NODE:
			node.nodeValue = Bork.transformString(node.nodeValue);
			/* A text node is a leaf node. */
			continue;
		case 1:
		case node.ELEMENT_NODE:
			/* Skip these special tags. */
			if (node.nodeName == 'STYLE')
				continue;
			if (node.nodeName == 'SCRIPT')
				continue;

			/* Process this tag specially. */
			if (node.nodeName == 'IFRAME') {
				Bork.transformPage(node.contentDocument);

				/* This catches future targeted page loads. */
				node.addEventListener('load', Bork.loadHandler, true);
			}
			break;

		case 9:
		case node.DOCUMENT_NODE:
		case 11:
		case node.DOCUMENT_FRAGMENT_NODE:
			break;
		default:
			/* Ignore other node types. In particular, DOM
			 * level 3 defines Entity nodes to be read-only.
			 */
			continue;
		}

		/* Dive, dive, dive... */
		Bork_treeWalkNodes(node.firstChild);
	}
}

function Bork_loadHandler(e)
{
	if (e.currentTarget.nodeName == 'IFRAME') {
//Components.utils.reportError('loadHandler');
		Bork.transformPage(e.currentTarget.contentDocument);
	}
}

function Bork_transformString(string)
{
	return (new Bork(string)).getString();
}

/**
 * @return
 * 	The transformed string.
 */
function Bork_getString()
{
	var result = '';

	if (this.string != null) {
		this.i_seen = 0;
		this.in_word = false;

		for (this.index = 0; this.index < this.string.length; this.index++) {
			result += this.transformCharacter(this.string.charAt(this.index));
		}
	}

	return result;
}

/**
 * This is ment to be a private stateful method.
 *
 * @return
 *	The transformed character string.
 */
function Bork_transformCharacter(ch)
{
	var nc, was_in_word = this.in_word;

	this.in_word = true;

	switch (ch.toLowerCase()) {
	case '.':
	case '!':
	case '?':
		/*
		 * [.!?]$	{ BEGIN NIW; i_seen = 0;
		 * 		  printf("%c\nBork Bork Bork!",yytext[0]); }
		 */
		this.in_word = false;
		switch (this.string.charAt(this.index+1)) {
		case '\r': case '\n':
			this.i_seen = 0;
			return ch+"\nBork Bork Bork!";
		}
		return ch;
	case 'a':
		/*
		 * "an"		{ BEGIN INW; printf("un"); }
		 * "An"		{ BEGIN INW; printf("Un"); }
		 * "au"		{ BEGIN INW; printf("oo"); }
		 * "Au"		{ BEGIN INW; printf("Oo"); }
		 * "a"/{WC}	{ BEGIN INW; printf("e"); }
		 * "A"/{WC}	{ BEGIN INW; printf("E"); }
		 */
		switch (nc = this.string.charAt(++this.index)) {
		case 'n':
			return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'U'.charCodeAt(0))+nc;
		case 'u':
			return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'O'.charCodeAt(0))+'o';
		}

		this.index--;
		if (nc == '' || Bork.wc.indexOf(nc) == -1)
			return ch;

		return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'E'.charCodeAt(0));
	case 'b':
		/*
		 * <NIW>"bork"/{NW} 	ECHO;
		 * <NIW>"Bork"/{NW} 	ECHO;
		 *
		 * The above rule is replaced by:
		 *
		 * <NIW>[bB]o		{ BEGIN INW; printf("%co", yytext[0]); }
		 */
		if (this.string.charAt(this.index+1) == 'o') {
			this.index++;
			return ch+'o';
		}
		return ch;
	case 'e':
		/*
		 * "en"/{NW}		{ BEGIN INW; printf("ee"); }
		 * <INW>"ew"		{ BEGIN INW; printf("oo"); }
		 * <INW>"e"/{NW}	{ BEGIN INW; printf("e-a"); }
		 * <NIW>"e"		{ BEGIN INW; printf("i"); }
		 * <NIW>"E"		{ BEGIN INW; printf("I"); }
		 */
		if (was_in_word) {
			if (ch == 'e') {
				nc = this.string.charAt(this.index+1);
				if (nc == 'w') {
					this.index++;
					return "oo";
				}

				if (nc == '' || Bork.wc.indexOf(nc) == -1) {
					return "e-a";
				}

				if (nc != 'n') {
					return ch;
				}

				nc = this.string.charAt(this.index+2);
				if (Bork.wc.indexOf(nc) == -1) {
					this.index++;
					return "ee";
				}

				return ch;
			}

			return ch;
		}

		if (ch == 'E' || (nc = this.string.charAt(this.index+1)) != 'n') {
			return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'I'.charCodeAt(0));
		}

		if (Bork.wc.indexOf(this.string.charAt(++this.index)) != -1)
			return ch+ch;

		return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'I'.charCodeAt(0))+nc;
	case 'f':
		/*
		 * <INW>"f"	{ BEGIN INW; printf("ff"); }
		 */
		if (was_in_word && ch == 'f')
			return "ff";
		return ch;
	case 'i':
		/*
		 * <INW>"ir"	{ BEGIN INW; printf("ur"); }
		 * <INW>"i"	{ BEGIN INW; printf(i_seen++ ? "i" : "ee"); }
		 */
		if (was_in_word && ch == 'i') {
			nc = this.string.charAt(this.index+1);
			if (nc == 'r') {
				this.index++;
				return "ur";
			}

			if (this.i_seen++)
				return ch;

			return "ee";
		}
		return ch;
	case 'o':
		/*
		 * <INW>"ow"	{ BEGIN INW; printf("oo"); }
		 * <NIW>"o"	{ BEGIN INW; printf("oo"); }
		 * <NIW>"O"	{ BEGIN INW; printf("Oo"); }
		 * <INW>"o"	{ BEGIN INW; printf("u"); }
		 */
		if (!was_in_word) {
			return ch+'o';
		}

		if (ch == 'O')
			return ch;

		nc = this.string.charAt(this.index+1);
		if (nc == 'w') {
			this.index++;
			return "oo";
		}

		return 'u';
	case 't':
		/*
		 * "the"	{ BEGIN INW; printf("zee"); }
		 * "The"	{ BEGIN INW; printf("Zee"); }
		 * "th"/{NW}	{ BEGIN INW; printf("t"); }
		 * <INW>"tion"	{ BEGIN INW; printf("shun"); }
		 */
		switch (nc = this.string.charAt(++this.index)) {
		case 'h':
			nc = this.string.charAt(this.index+1);
			if (nc == 'e') {
				this.index++;
				return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'Z'.charCodeAt(0))+"ee";
			}

			if (nc == '' || Bork.wc.indexOf(nc) == -1)
				return ch;

			return ch+'h';
		case 'i':
			nc = this.string.charAt(++this.index);
			if (nc != 'o') {
				this.index--;
				return "ti";
			}
			nc = this.string.charAt(++this.index);
			if (nc != 'n') {
				this.index--;
				return "tio";
			}
			return "shun";
		}
		this.index--;
		return ch;
	case 'u':
		/*
		 * <INW>"u"	{ BEGIN INW; printf("oo"); }
		 * <INW>"U"	{ BEGIN INW; printf("Oo"); }
		 */
		if (was_in_word) {
			return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'O'.charCodeAt(0))+'o';
		}
		return ch;
	case 'v':
		/*
		 * "v"		{ BEGIN INW; printf("f"); }
		 * "V"		{ BEGIN INW; printf("F"); }
		 */
		return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'F'.charCodeAt(0));
	case 'w':
		/*
		 * "w"		{ BEGIN INW; printf("v"); }
		 * "W"		{ BEGIN INW; printf("V"); }
		 */
		return String.fromCharCode((ch.charCodeAt(0) & 0x20) | 'V'.charCodeAt(0));
	}

	/*
	 * {NW}		{ BEGIN NIW; i_seen = 0; ECHO; }
	 */
	if (Bork.wc.indexOf(ch) == -1) {
		this.in_word = false;
		this.i_seen = 0;
	}

	/*
	 * .		{ BEGIN INW; ECHO; }
	 */

	return ch;
}

new Bork();

// Class elements
Bork.version = '1.0';
Bork.author = 'achowe@snert.com';
Bork.wc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'"
Bork.loadHandler = Bork_loadHandler;
Bork.transformPage = Bork_transformPage;
Bork.transformWindow = Bork_transformWindow;
Bork.transformString = Bork_transformString;

// Instance elements
Bork.prototype.getString = Bork_getString;
Bork.prototype.transformCharacter = Bork_transformCharacter;

