var MyLib = {
	mod: function(m, n) {
		return ((m % n) + n) % n;
	},
	getParams: function(element, names, defaults) {
		var params = defaults ? defaults : {};
		var target = $(element);
		for (var n in names) {
			var name = names[n];
			var valueStr = target.attr('data-' + name);
			if (typeof valueStr != 'undefined') {
				var value = parseFloat(valueStr);
				if (isNaN(value)) {
					value = valueStr;
				}
				params[name] = value;
			}
		}
		return params;
	},
	shuffleArray: function(anArray, first, limit){
		if (!first) {
			first = 0;
		}
		else {
			if (first < 0) 
				first = anArray.length + first;
		}
		if (!limit || limit > anArray.length) {
			limit = anArray.length;
		}
		else {
			if (limit < 0) 
				limit = anArray.length + limit;
		}
		var tmp = [];
		for (var i = first; i < limit; i++) {
			tmp.push({
				obj: anArray[i],
				random: Math.random()
			});
		}
		tmp.sort(function(a, b){
			return a.random - b.random;
		});
		for (var i = first; i < limit; i++) {
			anArray[i] = tmp[i - first].obj;
		}
	}
};

(function($){	
	/**
	 * I'm using a command pattern.
	 *
	 * @param {Object} cmd command function to be executed
	 */
	function Exec(cmd){
		this.cmd = cmd;
	}
	Exec.prototype.apply = function(layer){
		layer.startExecution(this);
		this.cmd(layer);
		layer.endExecution();
	}
	
	function Repeat(cmd){
		this.cmd = cmd;
	}
	Repeat.prototype.apply = function(layer){
		layer.startExecution(this);
		this.cmd(layer);
		layer.endExecution();
	}
	
	function After(ms){
		this.ms = ms;
		this.timer = null;
	}
	After.prototype.apply = function(layer){
		layer.startExecution(this);
		this.timer = setTimeout(function(){
			layer.endExecution();
		}, this.ms);
	}
	After.prototype.abort = function(layer){
		clearTimeout(this.timer);
		layer.endExecution();
	}
	
	function FadeIn(ms, what){
		this.ms = ms;
		this.what = what;
	}
	FadeIn.prototype.apply = function(layer){
		layer.startExecution(this);
		var target = this.what ? this.what : layer.target;
		target.fadeIn(this.ms, 'linear', function(){
			layer.endExecution();
		});
	}
	
	function FadeOut(ms, what){
		this.ms = ms;
		this.what = what;
	}
	FadeOut.prototype.apply = function(layer){
		layer.startExecution(this);
		var target = this.what ? this.what : layer.target;
		target.fadeOut(this.ms, 'linear', function(){
			layer.endExecution();
		});
	}

	function FadeTo(ms, from, to){
		this.ms = ms;
		this.from = from;
		this.to = to ? to : from;
	}
	FadeTo.prototype.apply = function(layer){
		layer.startExecution(this);
		var from = this.from ? this.from : layer.target;
		from.fadeOut(this.ms);
		if (this.to) {
			this.to.fadeIn(this.ms, function(){
				layer.endExecution();
			});
		}
	}
	
	function Hide(what){
		this.what = what;
	}
	Hide.prototype.apply = function(layer){
		layer.startExecution(this);
		if (this.what) {
			this.what.hide();
		}
		else {
			layer.target.hide();
		}
		layer.endExecution();
	}
	
	function Show(what){
		this.what = what;
	}
	Show.prototype.apply = function(layer){
		layer.startExecution(this);
		if (this.what) {
			this.what.show();
		}
		else {
			layer.target.show();
		}
		layer.endExecution();
	}
	
	/**
	 * Storyboard object
	 *
	 * @param {Object} target
	 */
	function Storyboard(target){
		this.target = target;
		this.queue = [];
		this.runningCommands = [];
		this.running = false;
	}
	Storyboard.prototype.debugMessage = function(message){
		if (this.debug) {
			var queueDescr = null;
			for (var i in this.queue) {
				if (queueDescr == null) {
					queueDescr = '[' + this.queue[i].constructor.name;
				}
				else {
					queueDescr = queueDescr + ', ' + this.queue[i].constructor.name;
				}
			}
			if (queueDescr == null) {
				queueDescr = '[]';
			}
			else {
				queueDescr = queueDescr + ']';
			}
			if (console) {
				console.log(message + ' - queue ' + queueDescr);
			}
		}
	}
	Storyboard.prototype.exec = function(cmd){
		this.enqueue(new Exec(cmd));
		return this;
	}
	Storyboard.prototype.log = function(msg){
		return this.exec(function(layer){
			if (console) {
				console.log(msg);
			}
		});
	}
	Storyboard.prototype.start = function(){
		this.running = true;
		this.dequeue();
		return this;
	}
	Storyboard.prototype.stop = function(){
		this.running = false;
		return this;
	}
	Storyboard.prototype.clear = function(){
		this.queue = [];
		var cmd = this.runningCommands[this.runningCommands.length - 1];
		if (cmd && cmd.abort) {
			cmd.abort(this);
		}
		return this;
	}
	Storyboard.prototype.isRunning = function(){
		return this.running;
	}
	Storyboard.prototype.repeat = function(){
		if (this.runningCommands.length > 0) {
			this.debugMessage('repeat');
			this.enqueue(new Repeat(this.runningCommands[0].cmd));
		}
		else {
			this.debugMessage('repeat (nothing to do)');
		}
	}
	Storyboard.prototype.after = function(ms){
		this.debugMessage('after');
		this.enqueue(new After(ms));
		return this;
	}
	Storyboard.prototype.fadeIn = function(ms, what){
		this.debugMessage('fadeIn');
		this.enqueue(new FadeIn(ms, what));
		return this;
	}
	Storyboard.prototype.fadeOut = function(ms, what){
		this.debugMessage('fadeOut');
		this.enqueue(new FadeOut(ms, what));
		return this;
	}
	Storyboard.prototype.fadeTo = function(ms, from, to){
		this.debugMessage('fadeTo');
		this.enqueue(new FadeTo(ms, from, to));
		return this;
	}
	Storyboard.prototype.hide = function(what){
		this.debugMessage('hide');
		this.enqueue(new Hide(what));
		return this;
	}
	Storyboard.prototype.show = function(what){
		this.debugMessage('show');
		this.enqueue(new Show(what));
		return this;
	}
	Storyboard.prototype.transform = function(transform){
		this.debugMessage('transform');
		return this.exec(function(layer){
			layer.target.css({
				'transform': transform,
				'-ms-transform': transform,
				'-webkit-transform': transform,
				'-moz-transform': transform,
				'-o-transform': transform
			});
		});
	}
	Storyboard.prototype.animate = function(transition){
		this.debugMessage('animate');
		return this.exec(function(layer){
			layer.target.css({
				'transition': transition,
				'-ms-transition': transition,
				'-webkit-transition': transition,
				'-moz-transition': transition,
				'-o-transition': transition
			});
		});
	}
	Storyboard.prototype.css = function(map){
		this.debugMessage('css');
		return this.exec(function(layer){
			layer.target.css(map);
		});
	}
	Storyboard.prototype.clearAnimation = function(){
		this.debugMessage('clearAnimation');
		return this.animate('');
	}
	
	Storyboard.prototype.enqueue = function(cmd){
		this.queue.push(cmd);
		this.debugMessage('enqueue of "' + cmd.constructor.name + '"');
		this.dequeue();
	}
	Storyboard.prototype.startExecution = function(cmd){
		this.runningCommands.push(cmd);
	}
	Storyboard.prototype.endExecution = function(){
		this.runningCommands.pop();
		this.dequeue();
	}
	Storyboard.prototype.dequeue = function(){
		while (this.step()) {
		}
	}
	Storyboard.prototype.step = function(){
		if (this.runningCommands.length == 0 && this.queue.length > 0 && this.running) {
			var cmd = this.queue[0];
			this.queue = this.queue.slice(1);
			this.debugMessage('dequeue of "' + cmd.constructor.name);
			cmd.apply(this);
			return true;
		}
		return false;
	}
	$.fn.scene = function(){
		return new Storyboard(this);
	}
	Scene = function(selector){
		return $(selector).scene();
	}
}(jQuery));

