/*
 * Resizer - a JavaScript module for dynamically animating the resizing
 * of an HTML element over time, so that it appears to grow or shrink
 *
 * Author:	Benjamin Black <benjamin.black@gmail.com>
 * Revision:	1.0
 * Link:	http://www.benjaminblack.net/js/Resize.js
 * License:	GPL (http://www.gnu.org/copyleft/gpl.html)
 * 
 * Copyright (C) 2006  Benjamin Black
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

var	__Resize_stateBuffer	= new Array();

var	__Resize_timeStep	= 25;

var	__Debug			= false;

function dump(obj)
{
	var s = "";
	for (i in obj)
	{
		s += i + ": " + obj[i] + "\n";
	}
	alert(s);
}


function ResizeOptions(width, height, duration, rate, animateBorder, endBorder, callback)
{
	// check the arguments for validity
	this.checkArgs(arguments);
	
	this.isWidthResized = (width != null);
	this.isHeightResized = (height != null);

	// Internet Explorer prior to version 7 does not support element dimensions of zero;
	// this code checks if the user is running IE 6 or lower and caps the width and height
	// at a minimum of one
	if (width == 0 || height == 0) {
		with (navigator.appVersion) {
			if (indexOf("MSIE") >= 0) {
				var version = parseFloat(substr(indexOf("MSIE") + 5, 3));
				 if (version <= 6.0) {	
				 	if (width == 0)
				 		width = 1;
				 	if (height == 0)
				 		height = 1;
				 }
			}
		}
	}
	
	this.endWidth = width;
	this.endHeight = height;
	this.duration = duration;
	this.rate = rate;
	this.animateBorder = animateBorder;
	this.endBorder = endBorder;
	this.callback = callback;
}



ResizeOptions.prototype.checkArgs = function(args)
{
	var width, height, duration, rate, animateBorder, endBorder, callback;
	width = args[0]; 
	height = args[1]; 
	duration = args[2]; 
	rate = args[3]; 
	animateBorder = args[4]; 
	endBorder = args[5];
	callback = args[6];

	try {
		if (width != null) {
			if (width < 0)
				throw new Error("ResizeOptions: 'width' must be a greater than or equal to zero.");
			if (width % 1 != 0)
				throw new Error("ResizeOptions: 'width' must be a whole number.");
		}
		
		if (height != null) {
			if (height < 0)
				throw new Error("ResizeOptions: 'height' must be greater than or equal to zero.");
			if (height % 1 != 0)
				throw new Error("ResizeOptions: 'height' must be a whole number.");
		}
		
		if (duration == null && rate == null)
			throw new Error("ResizeOptions: either 'duration' or 'rate' must be specified (cannot both be null).");
		
		if (duration != null) {
			if (typeof duration != "number")
				throw new Error("ResizeOptions: 'duration' must be a number.");
			else {
				if (duration < __Resize_timeStep)
					throw new Error("ResizeOptions: 'duration' must be at least " + __Resize_timeStep + ".");
				if (duration % 1 != 0)
					throw new Error("ResizeOptions: 'duration' must be a whole number.");
			}
		}
			
		if (rate != null) {
			if (typeof rate != "number")
				throw new Error("ResizeOptions: 'rate' must be a number.");
			else {
				if (rate <= 0)
					throw new Error("ResizeOptions: 'rate' must be greater than zero.");
				if (rate % 1 != 0)
					throw new Error("ResizeOptions: 'rate' must be a whole number.");
			}
		}
		
		if (animateBorder != null && typeof animateBorder != "string")
			throw new Error("ResizeOptions: 'animateBorder' must be a string representing a valid CSS border.");
		
		if (endBorder != null && typeof endBorder != "string")
			throw new Error("ResizeOptions: 'endBorder' must be a string representing a valid CSS border.");
			
		if (callback != null && typeof callback != "function")
			throw new Error("ResizeOptions: 'callback' must be a valid function.");
	}
	catch (e) {
		alert(e);
		throw e;
	}

}





function __Resize_state(id, element, options, 
		currentWidth, widthStep, widthComp,
		currentHeight, heightStep, heightComp)
{
	this.id = id;
	this.element = element;
	this.options = options;
	this.currentWidth = currentWidth;
	this.widthStep = widthStep;
	this.widthComp = widthComp;
	this.currentHeight = currentHeight;
	this.heightStep = heightStep;
	this.heightComp = heightComp;
	this.timeoutID = null;
	
	this.toString = function()
	{
		return	"id: " + id + "\n" +
			"element: " + element + "\n" +
			"isWidthResized: " + options.isWidthResized + "\n" +
			"isHeightResized: " + options.isHeightResized + "\n" +
			"width: " + options.width + "\n" +
			"height: " + options.height + "\n" +
			"duration: " + options.duration + "\n" +
			"animateBorder: " + options.animateBorder + "\n" +
			"endBorder: " + options.endBorder + "\n" +
			"callback: " + options.callback + "\n" + 
			"currentWidth: " + currentWidth + "\n" + 
			"widthStep: " + widthStep + "\n" + 
			"widthComp: " + widthComp + "\n" +
			"currentHeight: " + currentHeight + "\n" +
			"heightStep: " + heightStep + "\n" + 
			"heightComp: " + heightComp + "\n" +
			"timeoutID: " + timeoutID;
	}
}



function resize(element, id, options)
{	
	if (element == null || element.style == null)
		throw new Error("resize: 'element' must be a valid object with a 'style' attribute");

	if (id == null)
		id = "__Resize_global";
	
	if (typeof id == "object")
		id = id.toString();
		
	if (typeof id != "string" && typeof id != "number")
		throw new Error("resize: 'id' must be a valid object, string, or number");
		
	if (!(options instanceof ResizeOptions))
		throw new Error("resize: 'options' must be an instance of ResizeOptions");
		

	var state = __Resize_stateBuffer[id];
	if (state != null) {
		if (state.timeoutID != null)
			clearTimeout(state.timeoutID);
		state = __Resize_stateBuffer[id] = null;
	}

	var currentWidth, widthComp, widthStep;
	var currentHeight, heightComp, heightStep;

	var isUsingDuration = (options.duration != null);
	
	if (!options.isWidthResized) {
		currentWidth = widthComp = widthStep = null;
	}
	else {
		currentWidth = element.clientWidth;
		widthComp = (currentWidth < options.endWidth) ? Math.min : Math.max;
		if (isUsingDuration) {
			var percentChange = __Resize_timeStep / options.duration;
			widthStep = ((widthStep = (options.endWidth - currentWidth) * percentChange) > 0) ? 
					Math.max(1, Math.round(widthStep)) :
					Math.min(-1, Math.round(widthStep));
		}
		else {
			widthStep = options.rate * __Resize_timeStep / 1000;
		}
	}
	
	if (!options.isHeightResized) {
		currentHeight = heightComp = heightStep = null;
	}
	else {
		currentHeight = element.clientHeight;
		heightComp = (currentHeight < options.endHeight) ? Math.min : Math.max;
		if (isUsingDuration) {		
			var percentChange = __Resize_timeStep / options.duration;
			heightStep = ((heightStep = (options.endHeight - currentHeight) * percentChange) > 0) ?
					Math.max(1, Math.round(heightStep)) :
					Math.min(-1, Math.round(heightStep));
		}
		else {
			heightStep = options.rate * __Resize_timeStep / 1000;
			if (options.endHeight - currentHeight >= 0)
				heightStep = Math.abs(heightStep);
			else
				heightStep = -Math.abs(heightStep);
				
		}
	}
	
	var isWidthResized = (widthStep != null);
	var isHeightResized = (heightStep != null);
	
	state = new __Resize_state(id, element, options, 
					currentWidth, widthStep, widthComp,
					currentHeight, heightStep, heightComp);
	if (__Debug)
		dump(state);
	__Resize_stateBuffer[id] = state;
	if (options.animateBorder != null)
		element.style.border = options.animateBorder;
	__resize(id);
}



function __resize(id)
{
	var state = __Resize_stateBuffer[id];
	state.timeoutID = null;
	
	with (state)
	{
		if (options.isWidthResized) {
			currentWidth = widthComp(currentWidth + widthStep, options.endWidth);
			element.style.width = currentWidth + "px";
		}
		if (options.isHeightResized) {
			currentHeight = heightComp(currentHeight + heightStep, options.endHeight);
			element.style.height = currentHeight + "px";
		}
		
		if (currentWidth != options.endWidth || currentHeight != options.endHeight) {
			timeoutID = setTimeout("__resize(\""+id+"\");", __Resize_timeStep);
		}
		else {
			__Resize_stateBuffer[id] = null;
			if (options.endBorder != null)
				element.style.border = options.endBorder;
			if (options.callback != null)
				options.callback(state);
		}
	}
}
