/** Toggles an element between hidden and shown */
function toggleElement(el, duration, collapseStyle, oncomplete)
{
	if (!el.style)
		return;
	if (el.style.display == 'none')
		showElement(el, duration, oncomplete);
	else
		hideElement(el, duration, collapseStyle, oncomplete);
}

/** Fade out, collapse and hide a block element */
/* collapse: 0 no-size, 1 height, 2 width, 3 both. -ve to not fade */
function hideElement(el, duration, collapseStyle, oncomplete)
{
	if (el.old_height == undefined)
		el.old_height = el.offsetHeight;
	if (el.old_width == undefined)
		el.old_width = el.offsetWidth;
	resizeElement(el, (Math.abs(collapseStyle) == 1 ? 0 : null), (Math.abs(collapseStyle) == 2 ? 0 : null), duration);
	if (collapseStyle >= 0)
		fadeElement(el, 0, duration, oncomplete);
}

/** Fade in, uncollapse and show a block element */
function showElement(el, duration, oncomplete)
{
	setElementOpacity(el, 0);
	resizeElement(el, el.old_height, el.old_width, duration);
	fadeElement(el, 100, duration, oncomplete);
}

/** Fade an element to the target opacity */
function fadeElement(el, target_opacity, duration, oncomplete)
{
	if (el == null)
		return;
	if (!target_opacity || target_opacity < 0)
		target_opacity = 0;
	if (target_opacity > 100)
		target_opacity = 100;
	if (!duration)
		duration = 500;
	if (duration < 50)
		duration = 50;
	
	if (!el.defaultDisplayStyle)
	{
		var defaultStyle = 'block';
		if (el.getAttribute('defaultDisplayStyle'))
			defaultStyle = el.getAttribute('defaultDisplayStyle');
		else if (el.nodeName.toLowerCase() == 'table')
			defaultStyle = 'table';
		else if (el.nodeName.toLowerCase() == 'span')
			defaultStyle = 'inline';
		el.defaultDisplayStyle = (el.style.display.length > 0 && el.style.display != 'none' ? el.style.display : defaultStyle);
	}
	
	// If at new target, run completion
	var curOpac = getElementOpacity(el);
	if (curOpac == target_opacity)
	{
		_fadeElement(el, el.fading, curOpac, 0, oncomplete);
		return;
	}
	
	// Show hidden element
	if (target_opacity > 0 && el.style.display != el.defaultDisplayStyle)
	{
		setElementOpacity(el, curOpac);
		el.style.display = el.defaultDisplayStyle;
	}
	
	// Action ID (will break if changes)
	el.fading = Math.random();
	
	var opac_step = (target_opacity - curOpac) / (duration / 50);
	setTimeout(function() { _fadeElement(el, el.fading, target_opacity, opac_step, oncomplete); }, 50);
}
function _fadeElement(el, id, target_opacity, opac_step, oncomplete)
{
	// Break if action replaced
	if (el.fading != id)
		return;
	
	// Check next opacity is valid and not target
	var opac = getElementOpacity(el) + opac_step;
	if (opac > 0 && opac < 100
		&& ((opac_step > 0 && opac < target_opacity)
		|| (opac_step < 0 && opac > target_opacity)))
	{
		setElementOpacity(el, opac);
		setTimeout(function() { _fadeElement(el, id, target_opacity, opac_step, oncomplete); }, 50);
	}
	else
	{
		// If opacity should be 0, invisible
		setElementOpacity(el, target_opacity);
		if (target_opacity == 0)
			el.style.display = 'none';
		else if (el.style.display.length == 0)
			el.style.display = el.defaultDisplayStyle;
		
		// Action complete
		el.fading = 0;
		if (oncomplete != undefined)
			oncomplete(el);
	}
}

/** Resize an element to the given dimensions */
// TODO: Resize around fixed point using "position"
function resizeElement(el, target_height, target_width, duration, position, oncomplete)
{
	if (el == null)
		return;
	if (target_height == undefined && target_width == undefined)
		return;
	if (!duration)
		duration = 500;
	if (duration < 50)
		duration = 50;
	
	if (target_height > 0 && target_width > 0 && el.style.display == 'none')
		el.style.display = 'block';
	
	el.last_height = el.offsetHeight;
	var height_step = (target_height == undefined ? 0 : (target_height - el.last_height) / (duration / 50));
	el.last_width = el.offsetWidth;
	var width_step = (target_width == undefined ? 0 : (target_width - el.last_width) / (duration / 50));
	if (height_step == 0 && width_step == 0)
		return;
	
	el.resizing = Math.random();
	setTimeout(function() { _resizeElement(el, el.resizing, target_height, target_width, height_step, width_step, position, oncomplete); }, 50);
}
function _resizeElement(el, id, target_height, target_width, height_step, width_step, position, oncomplete)
{
	if (el.resizing != id)
		return;
	var changed = false;
	el.last_height = el.last_height + height_step;
	if ((height_step > 0 && el.last_height < target_height)
		|| (height_step < 0 && el.last_height > target_height))
	{
		changed = true;
		el.style.height = Math.round(el.last_height) + 'px';
		//if (el.offsetHeight != Math.round(el.last_height))
		//	height_step = 0;
	}
	else if (height_step != 0)
	{
		el.style.height = target_height + 'px';
	}
	
	el.last_width = el.last_width + width_step;
	if ((width_step > 0 && el.last_width < target_width)
		|| (width_step < 0 && el.last_width > target_width))
	{
		changed = true;
		el.style.width = Math.round(el.last_width) + 'px';
		//if (el.offsetWidth != el.last_width)
		//	width_step = 0;
	}
	else if (width_step != 0)
	{
		el.style.width = target_width + 'px';
	}
	
	if (!changed)
	{
		el.resizing = 0;
		if (target_width == 0 || target_height == 0)
			el.style.display = 'none';
		if (oncomplete != undefined)
			oncomplete(el);
	}
	else
	{
		setTimeout(function() { _resizeElement(el, id, target_height, target_width, height_step, width_step, position, oncomplete); }, 50);
	}
}

function moveElement(el, targetX, targetY, duration, oncomplete)
{
	if (el == null)
		return;
	
	var step = 1;
	if (targetX != null && targetX != el.offsetLeft)
		step = Math.abs(targetX - el.offsetLeft) / (duration / 50);
	else if (targetY != null && targetY != el.offsetTop)
		step = Math.abs(targetY - el.offsetTop) / (duration / 50);
	if (step < 1)
		step = 1;
	
	el.moving = Math.random();
	_moveElement(el, el.moving, targetX, targetY, step, oncomplete);
}
function _moveElement(el, id, targetX, targetY, step, oncomplete)
{
	if (el.moving != id)
		return;
	var changed = false;
	
	var curX = el.offsetLeft;
	if (targetX != null && targetX != curX)
	{
		changed = true;
		if (targetX > curX && targetX > curX + step)
			el.style.left = (curX + step) + 'px';
		else if (targetX < curX && targetX < curX - step)
			el.style.left = (curX - step) + 'px';
		else
			el.style.left = targetX + 'px';
	}
	
	var curY = el.offsetTop;
	if (targetY != null && targetY != curY)
	{
		changed = true;
		if (targetY > curY && targetY > curY + step)
			el.style.top = (curY + step) + 'px';
		else if (targetY < curY && targetY < curY - step)
			el.style.top = (curY - step) + 'px';
		else
			el.style.top = targetY + 'px';
	}
	
	if (!changed)
	{
		el.moving = 0;
		if (oncomplete != undefined)
			oncomplete(el);
	}
	else
	{
		setTimeout(function() { _moveElement(el, id, targetX, targetY, step, oncomplete); }, 50);
	}
}
