dojo.provide("dojox.fx.text");

// dojo.require("dojox.fx._split");
dojo.require("dojo.fx");
dojo.require("dojo.fx.easing");

dojox.fx.text._split = function(/*Object*/ args){
	// summary: Split a block of text into words or letters
	//
	// description:
	//		Returns an animation that will split the node into a grid
	//		of pieces that move independently.
	//
	// NOTE:
	//		In some rendering engines, the text will appear to "jump" from its initial position
	//		when the animation begins.	To work around this bug, enclose the node's text in a <p> or <div>.
	//
	//	args:
	//		args.crop: Boolean - If true, pieces will be positioned relatively rather than absolutely
	//		args.text: String - Text to place inside the node (otherwise node.innerHTML is used)
	//		args.words: Boolean - If true, the text will be split into words rather than characters
	//		args.pieceAnimation: Function(piece, pieceCoords, nodeCoords, number, numPieces)
	//			- Returns either the dojo._Animation or an array of dojo._Animation objects for the piece;
	//			pieceCoords is the result of dojo.coords(piece, true);
	//			nodeCoords is the result of dojo.coords(args.node, true);
	//			number is the piece's position in the array of pieces, and numPieces is the array.length

	var node = args.node = dojo.byId(args.node),
		s = node.style,
		cs = dojo.getComputedStyle(node),
		nodeCoords = dojo.coords(node, true);
		
	args.duration = args.duration || 1000;
	args.words = args.words || false;
	
	var originalHTML = (args.text && typeof(args.text) == "string") ? args.text : node.innerHTML,
		originalHeight = s.height,
		originalWidth = s.width,
		animations = [];

	dojo.style(node, {
		height: cs.height,
		width: cs.width
	});

	// The following regular expression courtesy of Phil Haack
	// http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx
	var tagReg = /(<\/?\w+((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)\/?>)/g;

	// Translation: /(HTML tag plus spaces)|(word/letter without '<' plus spaces)/g
	var reg = (args.words ?
		/(<\/?\w+((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)\/?>)\s*|([^\s<]+\s*)/g :
		/(<\/?\w+((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[^'">\s]+))?)+\s*|\s*)\/?>)\s*|([^\s<]\s*)/g
	);

	// Split the text into pieces
	var pieces = (typeof args.text == "string") ? args.text.match(reg) : node.innerHTML.match(reg);
	var html = "";
	var numPieces = 0;
	var number = 0;
	for(var i = 0; i < pieces.length; i++){
		var piece = pieces[i];
		if(!piece.match(tagReg)){
			html += "<span>" + piece + "</span>";
			numPieces++;
		}else{
			html += piece;
		}
	}
	node.innerHTML = html;

	// Find the newly-created spans and create their animations
	function animatePieces(piece){
		var next = piece.nextSibling;
		if(piece.tagName == "SPAN" && piece.childNodes.length == 1 && piece.firstChild.nodeType == 3){
			var pieceCoords = dojo.coords(piece, true);
			number++;
			dojo.style(piece, {
				padding: 0,
				margin: 0,
				top: (args.crop ? "0px" : pieceCoords.t + "px"),
				left: (args.crop ? "0px" : pieceCoords.l + "px"),
				display: "inline"
			});
			var pieceAnimation = args.pieceAnimation(piece, pieceCoords, nodeCoords, number, numPieces);
			if(dojo.isArray(pieceAnimation)){
				// if pieceAnimation is an array, append its elements
				animations = animations.concat(pieceAnimation);
			}else{
				// otherwise, append it
				animations[animations.length] = pieceAnimation;
			}
		}else if(piece.firstChild){
			animatePieces(piece.firstChild);
		}

		if(next){
			animatePieces(next);
		}
	}

	animatePieces(node.firstChild);
	var anim = dojo.fx.combine(animations);
	dojo.connect(anim, "onEnd", anim, function(){
		node.innerHTML = originalHTML;
		dojo.style(node, {
			height: originalHeight,
			width: originalWidth
		});
	});
	if(args.onPlay){
		dojo.connect(anim, "onPlay", anim, args.onPlay);
	}
	if(args.onEnd){
		dojo.connect(anim, "onEnd", anim, args.onEnd);
	}
	return anim; // dojo._Animation
};

dojox.fx.text.explode = function(/*Object*/ args){
	// summary: Explode a block of text into words or letters
	//
	// description:
	//		Returns an animation that will split the text into a spans
	//		of words or characters that fly away from the center.
	//
	//	args:
	//		args.crop: Boolean - If true, pieces will be positioned relatively rather than absolutely
	//		args.words: Boolean - If true, text will be split into words rather than characters
	//		args.random: Float - If set, pieces fly to random distances, for random durations,
	//							   and in slightly random directions. The value defines how much
	//							   randomness is introduced.
	//		args.distance: Float - Multiplier for the distance the pieces fly (even when random)
	//		args.fade: Boolean - If true, pieces fade out while in motion (default is true)
	//		args.fadeEasing: Function - If args.fade is true, the fade animations use this easing function
	//		args.unhide: Boolean - If true, the animation is reversed
	//		args.sync: Boolean - If args.unhide is true, all the pieces converge at the same time
	//							 (default is true)

	var node = args.node = dojo.byId(args.node);
	var s = node.style;

	args.distance = args.distance || 1;
	args.duration = args.duration || 1000;
	args.random = args.random || 0;
	if(typeof(args.fade) == "undefined"){
		args.fade = true;
	}
	if(typeof(args.sync) == "undefined"){
		args.sync = true;
	}
	args.random = Math.abs(args.random);

	// Returns the animation object for each piece
	args.pieceAnimation = function(piece, pieceCoords, coords, number, numPieces){
		var pieceHeight = pieceCoords.h;
		var pieceWidth = pieceCoords.w;
		var distance = args.distance * 2;
		var duration = args.duration;
		var startTop = parseFloat(piece.style.top);
		var startLeft = parseFloat(piece.style.left);
		var delay = 0;
		var randomX = 0;
		var randomY = 0;
		if(args.random){
			var seed = (Math.random() * args.random) + Math.max(1 - args.random, 0);
			distance *= seed;
			duration *= seed;
			// To syncronize, give each piece an appropriate delay so they end together
			delay = ((args.unhide && args.sync) || (!args.unhide && !args.sync)) ? (args.duration - duration) : 0;

			// Slightly randomize the direction of each piece
			randomX = Math.random() - 0.5;
			randomY = Math.random() - 0.5;
		}
		var distanceY = ((coords.h - pieceHeight) / 2 - (pieceCoords.y - coords.y));
		var distanceX = ((coords.w - pieceWidth) / 2 - (pieceCoords.x - coords.x));
		var distanceXY = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
		var endTop = startTop - distanceY * distance + distanceXY * randomY;
		var endLeft = startLeft - distanceX * distance + distanceXY * randomX;

		// Create the animation objects for the piece
		// These are separate anim objects so they can have different curves
		var pieceSlide = dojo.animateProperty({
			node: piece,
			duration: duration,
			delay: delay,
			easing: (args.easing || (args.unhide ? dojo.fx.easing.sinOut : dojo.fx.easing.circOut)),
			beforeBegin: (args.unhide ? function(){
					if(args.fade){
						//piece.style.opacity = 0;
						dojo.style(piece,"opacity", 0);
					}
					piece.style.position = args.crop ? "relative" : "absolute";
					piece.style.top = endTop + "px";
					piece.style.left = endLeft + "px";
				} : function(){piece.style.position = args.crop ? "relative" : "absolute";}),
			properties: {
				top: (args.unhide ? { start: endTop, end: startTop } : { start: startTop, end: endTop }),
				left: (args.unhide ? { start: endLeft, end: startLeft } : { start: startLeft, end: endLeft })
			}
		});

		if(args.fade){
			var pieceFade = dojo.animateProperty({
				node: piece,
				duration: duration,
				delay: delay,
				easing: (args.fadeEasing || dojo.fx.easing.quadOut),
				properties: {
					opacity: (args.unhide ? {start: 0, end: 1} : {end: 0})
				}
			});

			// return both animations as an array
			return (args.unhide ? [pieceFade, pieceSlide] : [pieceSlide, pieceFade]);
		}else{
			// Otherwise return only the slide animation
			return pieceSlide;
		}
	};

	var anim = dojox.fx.text._split(args);
	return anim; // dojo._Animation
};

dojox.fx.text.converge = function(/*Object*/ args){
	args.unhide = true;
	return dojox.fx.text.explode(args);
};

dojox.fx.text.disintegrate = function(/*Object*/ args){
	// summary: Split a block of text into words or letters and let them fall
	//
	// description:
	//		Returns an animation that will split the text into spans of words
	//		or characters that drop.
	//
	//	args:
	//		args.crop: Boolean - If true, pieces will be positioned relatively rather than absolutely
	//		args.words: Boolean - If true, text will be split into words rather than characters
	//		args.interval: Float - The number of milliseconds between each piece's animation
	//		args.distance: Float - The number of the node's heights to drop (default is 1.5)
	//		args.fade: Boolean - If true, pieces fade out while in motion (default is true)
	//		args.random: Float - If set, pieces fall in random order. The value defines how much
	//							   randomness is introduced
	//		args.reverseOrder: Boolean - If true, pieces animate in reversed order
	//		args.unhide: Boolean - If true, the peices fall from above and land in place

	var node = args.node = dojo.byId(args.node);
	var s = node.style;

	args.duration = args.duration || 1500;
	args.distance = args.distance || 1.5;
	args.random = args.random || 0;
	if(!args.fade){
		args.fade = true;
	}
	var random = Math.abs(args.random);

	// Returns the animation object for each piece
	args.pieceAnimation = function(piece, pieceCoords, coords, number, numPieces){
		var pieceHeight = pieceCoords.h;
		var pieceWidth = pieceCoords.w;

		var interval = args.interval || (args.duration / (1.5 * numPieces));
		var duration = (args.duration - numPieces * interval);

		var randomDelay = Math.random() * numPieces * interval;
		// If distance is negative, start from the top right instead of bottom left
		var uniformDelay = (args.reverseOrder || args.distance < 0) ?
			(number * interval) : ((numPieces - number) * interval);
		var delay = randomDelay * random + Math.max(1 - random, 0) * uniformDelay;

		// Create the animation object for the piece
		var properties = {};
		if(args.unhide){
			properties.top = {
				start: (parseFloat(piece.style.top) - coords.h * args.distance),
				end: parseFloat(piece.style.top)
			};
			if(args.fade){
				properties.opacity = {start: 0, end: 1};
			}
		}else{
			properties.top = {end: (parseFloat(piece.style.top) + coords.h * args.distance)};
			if(args.fade){
				properties.opacity = {end: 0};
			}
		}
		var pieceAnimation = dojo.animateProperty({
			node: piece,
			duration: duration,
			delay: delay,
			easing: (args.easing || (args.unhide ? dojo.fx.easing.sinIn : dojo.fx.easing.circIn)),
			properties: properties,
			beforeBegin: (args.unhide ? function(){
				if(args.fade){
					// piece.style.opacity = 0;
					dojo.style(piece, "opacity", 0);
				}
				piece.style.position = args.crop ? "relative" : "absolute";
				piece.style.top = properties.top.start + "px";
			} : function(){ piece.style.position = args.crop ? "relative" : "absolute";})
		});

		return pieceAnimation;
	};

	var anim = dojox.fx.text._split(args);
	return anim; // dojo._Animation
};

dojox.fx.text.build = function(/*Object*/ args){
	args.unhide = true;
	return dojox.fx.text.disintegrate(args);
};

dojox.fx.text.blockFadeOut = function(/*Object*/ args){
	// summary: Split a block of text into words or letters and fade them
	//
	// description:
	//		Returns an animation that will split the text into spans of words
	//		or characters that fade in or out.
	//
	//	args:
	//		args.words: Boolean - If true, text will be split into words rather than characters
	//		args.interval: Float - The number of milliseconds between each piece's animation (default is 0)
	//		args.random: Float - If true, pieces have a random delay. The value defines how much
	//							   randomness is introduced
	//		args.reverseOrder: Boolean - If true, pieces animate in reversed order
	//		args.unhide: Boolean - If true, the animation is reversed

	var node = args.node = dojo.byId(args.node);;
	var s = node.style;

	args.duration = args.duration || 1000;
	args.random = args.random || 0;
	var random = Math.abs(args.random);

	// Returns the animation object for each piece
	args.pieceAnimation = function(piece, pieceCoords, coords, number, numPieces){
		var interval = args.interval || (args.duration / (1.5 * numPieces));
		var duration = (args.duration - numPieces * interval);

		var randomDelay = Math.random() * numPieces * interval;
		// If interval or random is negative, start from the bottom instead of top
		var uniformDelay = (args.reverseOrder) ?
			((numPieces - number) * interval) : (number * interval);
		var delay = randomDelay * random + Math.max(1 - random, 0) * uniformDelay;

		// Create the animation object for the piece
		var pieceAnimation = dojo.animateProperty({
			node: piece,
			duration: duration,
			delay: delay,
			easing: (args.easing || dojo.fx.easing.sinInOut),
			properties: {
				opacity: (args.unhide ? {start: 0, end: 1} : {end:0})
			},
			beforeBegin: (args.unhide ? function(){ dojo.style(piece,"opacity",0); } : undefined)
		});

		return pieceAnimation;
	};

	var anim = dojox.fx.text._split(args);
	return anim; // dojo._Animation
};

dojox.fx.text.blockFadeIn = function(/*Object*/ args){
	args.unhide = true;
	return dojox.fx.text.blockFadeOut(args);
};

dojox.fx.text.backspace = function(/*Object*/ args){
	// summary: Split a block of text into words or letters and backspace them in sequence
	//
	// description:
	//		Returns an animation that will split the text into spans of words
	//		or characters that appear as if they were being backspaced (or typed) in real-time.
	//
	//	args:
	//		args.interval: Float - The number of milliseconds between each piece's animation
	//							   (default is determined by text length and args.duration);
	//		args.wordDelay: Integer - The number of milliseconds between each word
	//								  (only effective when args.unhide = true)
	//		args.fixed: Boolean - If true, only style.opacity changes; otherwise, style.display
	//							  changes between none and inline, adding realism (default = false)
	//		args.random: Float - If true, pieces have a random delay. The value defines how much
	//							   randomness is introduced (only effective when args.unhide = true)
	//		args.unhide: Boolean - If true, the animation is reversed

	var node = args.node = dojo.byId(args.node);
	var s = node.style;

	args.words = false;
	args.duration = args.duration || 2000;
	args.random = args.random || 0;
	var random = Math.abs(args.random);
	var delay = 10;

	// Returns the animation object for each piece
	args.pieceAnimation = function(piece, pieceCoords, coords, number, numPieces){
		var interval = args.interval || (args.duration / (1.5 * numPieces));
		var text = piece.textContent;
		var whitespace = text.match(/\s/g);

		if(typeof(args.wordDelay) == "undefined"){
			args.wordDelay = interval * 2;
		}

		if(!args.unhide){
			delay = (numPieces - number - 1) * interval;
		}

		var beforeBegin, onEnd;

		if(args.fixed){
			if(args.unhide){
				var beforeBegin = function(){ dojo.style(piece,"opacity",0); };
			}
		}else{
			if(args.unhide){
				var beforeBegin = function(){piece.style.display = "none";};
				var onEnd = function(){piece.style.display = "inline";};
			}else{
				var onEnd = function(){piece.style.display = "none";};
			}
		}

		// Create the animation object for the piece
		var pieceAnimation = dojo.animateProperty({
			node: piece,
			duration: 1,
			delay: delay,
			easing: (args.easing || dojo.fx.easing.sinInOut),
			properties: {
				opacity: (args.unhide ? {start: 0, end: 1} : {end:0})
			},
			beforeBegin: beforeBegin,
			onEnd: onEnd
		});

		if(args.unhide){
			var randomDelay = Math.random() * text.length * interval;
			var wordDelay = randomDelay * random / 2 + Math.max(1 - random / 2, 0) * args.wordDelay;

			delay += randomDelay * random + Math.max(1 - random, 0) * interval * text.length +
				(wordDelay * (whitespace && text.lastIndexOf(whitespace[whitespace.length-1]) == text.length - 1));
		}

		return pieceAnimation;
	};

	var anim = dojox.fx.text._split(args);
	return anim; // dojo._Animation
};

dojox.fx.text.type = function(/*Object*/ args){
	args.unhide = true;
	return dojox.fx.text.backspace(args);
};