
spackle; // (spackle.js is required.)

var INTERVAL_CLEARED = new Object;

var transitions = {
	TIMER_GRANULARITY: 20,

	OPACITY_INCREMENT: 1/255.
};

transitions.BasicTransition = function(current_value, target_value, increment,
                                       setter,
                                       duration,
                                       onfinish /* optional */) {
	if (typeof(current_value) != "number") current_value = parseFloat(current_value);
	if (typeof(target_value) != "number") target_value = parseFloat(target_value);
	if (typeof(increment) != "number") increment = parseFloat(increment);

	if (typeof(duration) != "number") duration = parseFloat(duration);

	if (current_value == target_value || !increment)
		return;

	if (target_value < current_value &&
	    increment > 0)
		increment = -increment;

	duration = Math.floor(duration);
	if (duration <= 0) {
		setter(target_value);
		return;
	}

	this.current_value = current_value;

	var n1 = Math.abs(target_value - this.current_value);

	var period = (duration + .0) / (n1 / Math.abs(increment));
	while (period < transitions.TIMER_GRANULARITY) {
		increment *= 2;
		period = (duration + .0) / (n1 / Math.abs(increment));
	}
	period = Math.floor(period);

	this.interval = 42;
	var that = this;
	var iteration = function() {
		that.current_value += increment;
		if (increment > 0 && (that.current_value > target_value) ||
		    increment < 0 && (that.current_value < target_value))
			that.current_value = target_value;

		setter(that.current_value);

		if (that.current_value == target_value) {
			clearInterval(that.interval);
			that.interval = 0;

			if (onfinish)
				onfinish();
		}
	};

	this.interval = setInterval(iteration, period);

	this.iteration = iteration, this.period = period;
}

transitions.BasicTransition.prototype.suspend = function() {
	if (this.interval) {
		clearInterval(this.interval);
		this.interval = 0;
	}
}
transitions.BasicTransition.prototype.resume = function() {
	if (!this.interval)
		this.interval = setInterval(this.iteration, this.period);
}

transitions.BasicTransition.prototype.cancel = function() {
	this.suspend();
}


// Inherits transitions.BasicTransition
transitions.FadeIn = function(element, target_opacity, duration,
                              onfinish /* optional */) {
	spackle(element);

	if (typeof(target_opacity) != "number")
		 return;
	if (target_opacity <= 0.0)
		return;
	if (target_opacity > 1.0)
		target_opacity = 1.0;

	var current_opacity = element.opacity();
	if (current_opacity >= target_opacity) {
		element.set_opacity(0.0);
		current_opacity = 0.0;
	}

	this.BasicTransition(
		current_opacity, target_opacity, transitions.OPACITY_INCREMENT,
		function(value) {
			element.set_opacity(value);
		},
		duration,
		onfinish);
}
transitions.FadeIn.prototype.BasicTransition = transitions.BasicTransition;
for (var m in transitions.BasicTransition.prototype)
	transitions.FadeIn.prototype[m] = transitions.BasicTransition.prototype[m];

// Inherits transitions.BasicTransition
transitions.FadeOut = function(element, target_opacity, duration,
                               onfinish /* optional */) {
	spackle(element);

	if (target_opacity >= 1) // Possibly duration passed as wrong argument.
		target_opacity = 0.0;

	var current_opacity = element.opacity();
	if (current_opacity <= target_opacity)
		return;

	this.BasicTransition(
		current_opacity, 0.0, -transitions.OPACITY_INCREMENT,
		function(value) {
			element.set_opacity(value);
		},
		duration,
		onfinish);
}
transitions.FadeOut.prototype.BasicTransition = transitions.BasicTransition;
for (var m in transitions.BasicTransition.prototype)
	transitions.FadeOut.prototype[m] = transitions.BasicTransition.prototype[m];


transitions.Dimmer = function(target_opacity) {
	if (target_opacity <= 0)
		target_opacity = 0.5;

	this.element = spackle(document.createElement("div"));
	this.element.style.position = "absolute";
	this.element.style.left = "0";
	this.element.style.top = "0";
	this.element.style.width = "100%";
	this.element.style.height = "100%";

	this.element.style.backgroundColor = "rgb(0, 0, 0)";

	this.element.set_opacity(0.0);

	this.target_opacity = target_opacity;

	document.documentElement.appendChild(this.element);

	this.current_transition = null;
}

transitions.Dimmer.prototype.dim = function(duration,
                                            onfinish /* optional */) {
	if (this.current_transition)
		this.current_transiton.cancel();

	this.current_transition =
		new transitions.FadeIn(this.element, this.target_opacity, duration, onfinish);
}
transitions.Dimmer.prototype.undim = function(duration,
                                              onfinish /* optional */) {
	if (this.current_transition)
		this.current_transition.cancel();

	this.current_transition =
		new transitions.FadeOut(this.element, 0.0, duration, onfinish);
}

transitions.Dimmer.prototype.finalize = function() {
	document.documentElement.removeChild(this.element);
	this.element = null;

	if (this.current_transition) {
		this.current_transition.cancel();
		this.current_transition = null;
	}
}


function max(a, b/*, ... */) {
	var result = Math.max(a, b);
	for (var i = 2; i < max.arguments.length; ++i) {
		var argument = max.arguments[i];
		if (argument > result)
			result = argument;
	}
	return result;
}

// Inherits transitions.BasicTransition
transitions.Zoom = function(element,
                            target_left, target_top, target_width, target_height,
                            duration,
                            onfinish /* optional */) {
	spackle(element);

	var bounds = element.bounds();
	var initial_left   = bounds.left,
	    initial_top    = bounds.top,
	    initial_width  = bounds.width,
	    initial_height = bounds.height;

	var nx = target_left   - initial_left,
	    ny = target_top    - initial_top,
		 nw = target_width  - initial_width,
		 nh = target_height - initial_height;
	var n = max(
		Math.abs(nx),
		Math.abs(ny),
		Math.abs(nw),
		Math.abs(nh));
	var dx = nx / n,
	    dy = ny / n,
	    dw = nw / n,
	    dh = nh / n;

	this.BasicTransition(0, n, 1,
		function(i) { // from 0 to n inclusive.
			var left   = initial_left   + i * dx,
			    top    = initial_top    + i * dy,
			    width  = initial_width  + i * dw,
			    height = initial_height + i * dh;
			if ((dx > 0) && (left > target_left) ||
			    (dx < 0) && (left < target_left))
				left = target_left;
			if ((dy > 0) && (top > target_top) ||
			    (dy < 0) && (top < target_top))
				top = target_top;
			if ((dw > 0) && (width > target_width) ||
			    (dw < 0) && (width < target_width))
				width = target_width;
			if ((dh > 0) && (height > target_height) ||
			    (dh < 0) && (height < target_height))
				height = target_height;

			element.move_to(left, top);
			element.resize_to(width, height);
		},
		duration,
		onfinish);
}
transitions.Zoom.prototype.BasicTransition = transitions.BasicTransition;
for (var m in transitions.BasicTransition.prototype)
	transitions.Zoom.prototype[m] = transitions.BasicTransition.prototype[m];

