/* 
** reshuffle.js
**
**  author: Adam Saponara
**    date: 12 Aug 2007
** version: 0.2
** license: Attribution-Noncommercial-Share Alike 3.0 United States
**          See http://creativecommons.org/licenses/by-nc-sa/3.0/us/
**
**   usage: reshuffle.apply('id_of_an_element_holding_a_textnode', 'new_text_here')
**
*/

var reshuffle = {

	/* Reshuffles element with new text */
	apply : function (id, new_text) {

		// 0. Make sure parameters are okay
		var el = this.dom.$(id);
		if (!el || !el.firstChild) return false; // element not found or element empty
		if (el.firstChild.nodeType != 3) return false; // element contents != text node

		// 1. Remember original text
		var orig_text = el.firstChild.nodeValue;
		var new_text_replaced = new Array(new_text.length);
		var el_temp = new Array();
		for (var i = 0; i < new_text_replaced.length; i++) new_text_replaced[i] = -1;
		this.parentel = el.parentNode;
		
		// 2. Clear contents of element
		this.dom.clear_el(el);

		// 3. Create 2 divs
		//    Div 1: spans of each characters of original text
		//    Div 2: spans of each characters of new text
		var el_div = null, el_span = null;

		// 4. Build Div 1 (original text)
		el_div = document.createElement('div');
		el_temp.push(el_div);
		el_div.id = id + '_orig';
		el_div.style.position = 'absolute';
		this.dom.set_float(el_div, 'left');
		for (var i = 0; i < orig_text.length; i++) {
			el_span = document.createElement('span');
			el_span.id = id + '_orig_' + i;
			el_span.appendChild(document.createTextNode(orig_text.charAt(i)));
			el_div.appendChild(el_span);
		}
		el.appendChild(el_div);

		// 5. Build Div 2 (new text)
		el_div = document.createElement('div');
		el_temp.push(el_div);
		el_div.id = id + '_new';
		el_div.style.position = 'absolute';
		this.dom.set_float(el_div, 'left');
		el_div.style.visibility = 'hidden';
		for (var i = 0; i < new_text.length; i++) {
			el_span = document.createElement('span');
			el_span.id = id + '_new_' + i;
			el_span.appendChild(document.createTextNode(new_text.charAt(i)));
			el_div.appendChild(el_span);
		}
		el.appendChild(el_div);

		// 6. Move matching characters from original text into positions of new text
		for (var i = 0; i < orig_text.length; i++) {
			var c = orig_text.charAt(i);
			var new_index = -1;
			var new_offset = -1;
			do {
				new_index = new_text.indexOf(c, new_offset + 1);
			}
			while(new_index != -1 && (new_offset = new_text_replaced[new_index]) != -1);
			if (new_index != -1 && new_text_replaced[new_index] == -1) {
				// slide copy of old character into position of new character
				el_span = document.createElement('span');
				el_temp.push(el_span);
				el_span.className = el.className;
				el_span.appendChild(document.createTextNode(c));
				el_span.style.position = 'absolute';
				el_span.style.left = this.dom.get_x(this.dom.$(id + '_orig_' + i)) + 'px';
				el_span.style.top = this.dom.get_y(this.dom.$(id + '_orig_' + i)) + 'px';
				this.parentel.appendChild(el_span);
				this.move_char(el_span, this.dom.get_x(this.dom.$(id + '_new_' + new_index)), this.dom.get_y(this.dom.$(id + '_new_' + new_index)), 0.17, this.anim_end); this.anim_start();
				new_text_replaced[new_index] = new_index;
			}
			this.dom.$(id + '_orig_' + i).style.visibility = 'hidden';
		}

		// 7. Fade in characters that were not present in the original text
		for (var i = 0; i < new_text_replaced.length; i++) {
			if (new_text_replaced[i] != -1) continue;
			// fade in a new character at this position
			el_span = document.createElement('span');
			el_temp.push(el_span);
			el_span.className = el.className;
			el_span.appendChild(document.createTextNode(new_text.charAt(i)));
			el_span.style.position = 'absolute';
			el_span.style.left = this.dom.get_x(this.dom.$(id + '_new_' + i)) + 'px';
			el_span.style.top = this.dom.get_y(this.dom.$(id + '_new_' + i)) + 'px';
			if (this.fade_enabled) { this.dom.set_alpha(el_span, 0); }
			this.parentel.appendChild(el_span);
			if (this.fade_enabled) { this.fade_char(el_span, 100, 0.1, this.anim_end); this.anim_start(); }
		}

		// 8. Wait until all animations finish
		var check_done_timer = setInterval(check_done, 10);
		var self = this;
		var el_el = el;
		function check_done() {
			if (self.anim_stack == 0 && el_el) {
				clearInterval(check_done_timer);
				self.dom.clear_el(el_el);
				el_el.appendChild(document.createTextNode(new_text));
				for (var el_bye in el_temp) if (el_temp[el_bye] && el_temp[el_bye].parentNode) el_temp[el_bye].parentNode.removeChild(el_temp[el_bye]);
				el_temp = new Array();
			}
		}

	},
	
	/* Pause between animation frames */
	pause: 30,
	
	/* Original parent element */
	parentel : null,
	
	/* Alpha effect? */
	fade_enabled : (document.all == undefined ? true : false),

	/* Animation stack */
	anim_stack: 0,
	anim_start : function () {
		this.anim_stack++;
	},
	anim_end : function () {
		this.anim_stack--;
	},

	/* Incrementally moves an element to a new coordinate */
	move_char : function(el, x, y, k, cbfn) {
		var curx = parseInt(el.style.left);
		var cury = parseInt(el.style.top);
		if (curx == x && cury == y) {
			if (cbfn) cbfn.call(this);
			return;
		}
		if (curx > x) {
			curx -= Math.ceil((curx - x) * k);
		}
		else {
			curx += Math.ceil((x - curx) * k);
		}
		if (cury > y) {
			cury -= Math.ceil((cury - y) * k);
		}
		else {
			cury += Math.ceil((y - cury) * k);
		}
		el.style.left = curx + 'px';
		el.style.top = cury + 'px';
		setTimeout(function () { reshuffle.move_char(el, x, y, k, cbfn) }, this.pause);
	},

	/* Incrementally fades an element to a new level of opacity */
	fade_char : function (el, a, k, cbfn) {
		var cura = this.dom.get_alpha(el);
		if (cura == a) {
			if (cbfn) cbfn.call(this);
			return;
		}
		if (cura > a) {
			cura -= Math.ceil((cura - a) * k);
		}
		else {
			cura += Math.ceil((a - cura) * k);
		}
		this.dom.set_alpha(el, cura);
		setTimeout(function () { reshuffle.fade_char(el, a, k, cbfn) }, this.pause);
	},
	
	/* DOM helper functions */
	dom : {
		$ : function (id) {
			return document.getElementById(id);
		},

		set_float : function (el, val) {
			if (el.style.cssFloat != undefined) {
				el.style.cssFloat = val;
			}
			else if (el.style.float != undefined) {
				el.style.float = val;
			}
			else {
				return false;
			}
		},

		get_float : function (el) {
			if (el.style.cssFloat != undefined) {
				return el.style.cssFloat;
			}
			else if (el.style.float != undefined) {
				return el.style.float;
			}
			else {
				return false;
			}
		},

		set_alpha : function (el, val) {
			if (el.style.opacity != undefined) {
				el.style.opacity = val / 100.0;
			}
			else if (el.style.MozOpacity != undefined) {
				el.style.MozOpacity = val / 100.0;
			}
			else if (el.style.KhtmlOpacity != undefined) {
				el.style.KhtmlOpacity = val / 100.0;
			}
			else if (el.style.filter != undefined) {
				el.style.filter = 'alpha(opacity=' + val + ')';
			}
			else {
				return false;
			}
		},

		get_alpha : function (el) {
			if (el.style.opacity != undefined) {
				return Math.floor(el.style.opacity * 100.0);
			}
			else if (el.style.MozOpacity != undefined) {
				return Math.floor(el.style.MozOpacity * 100.0);
			}
			else if (el.style.KhtmlOpacity != undefined) {
				return Math.floor(el.style.KhtmlOpacity * 100.0);
			}
			else if (el.style.filter != undefined) {
				var alpha_index = el.style.filter.indexOf('opacity=');
				if (alpha_index == -1) return 100;
				alpha_index += 8;
				alpha_end_index = el.style.filter.indexOf(')', alpha_index);
				if (alpha_end_index == -1) return 100;
				return parseInt(el.style.filter.substring(alpha_index, alpha_end_index));
			}
			else {
				return false;
			}
		},

		get_x : function (el) {
			if (!el) return false;
			if (el.x != undefined) return el.x;
			var x = el.offsetLeft;
			while ((el = el.offsetParent) != null) x += el.offsetLeft;
			return x;
		},

		get_y : function (el) {
			if (!el) return false;
			if (el.y != undefined) return el.y;
			var y = el.offsetTop;
			while ((el = el.offsetParent) != null) y += el.offsetTop;
			return y;
		},

		clear_el : function (el) {
			while (el && el.hasChildNodes()) el.removeChild(el.childNodes[0]);
		}

	}

}

