var jsUtilsEffect = {
	Sequences: {
		linear: function(x) { return x },
		sinoidal: function(x) { return (-Math.cos(x*Math.PI)/2) + 0.5; },
		reverse: function(x) { return 1 - x; },
		beat: function(x, beats) { 
			beats = parseInt(beats ? beats : 5); 
			var amplitude = 2 * x * beats;
			var result = amplitude - Math.floor(amplitude);
			return (Math.round((x % (1/beats)) * beats) == 0 ? result : (1 - result));
		},
		none: function(x) {	return 0; },
		full: function(x) {	return 1; }
	}, 
	DefaultOptions: {
		duration: 1.0, 
		frames_per_second: 100, 
		delay: 0.0
	}, 
	Base: jsUtilsPhoto.ClassCreate(null, {
		position: null,
		start: function(options) {
			options = (options ? options : {});
			if (options['events'])
			{
				var res = {};
				for (var key in options['events'])
				{
					this[key] = options['events'][key];
				}
				for (var key in options)
				{
					if (key != 'events') { res[key] = options[key]; }
				}
				options = res;
			}
			
			this.options = jsUtilsPhoto.ObjectsMerge(jsUtilsEffect.DefaultOptions, options);
			if (!this.options.sequences) 
				this.options.sequences = jsUtilsEffect.Sequences.linear;
			this.currentFrame = 0;
			
			this.totalTime = this.options.duration * 1000; // время движения
			this.totalFrames = this.options.frames_per_second * this.options.duration; // количество кадров 
			
			this.state = 'stoped';
			this.startOn = this.options.delay * 1000; // относительное время начала движения
			this.finishOn = this.startOn + this.totalTime; // относительное время окончания движения
			this.controller = false;
			if (this.options['set_controller'] != false)
			{
				if (this.options['controller_name'])
					this.controller = (jsUtilsEffect.Controller[this.options['controller_name']] || new jsUtilsEffect.Controllers());
				else
					this.controller = jsUtilsEffect.Controller['default'];
				this.controller.add(this);
			}
		},
		render: function(pos){
			// pos - время сначала цикла в % 
			if (this.state == "stoped")
			{
				this.state = 'running';
				this.event('BeforeSetup');
				this.setup();
				this.event('AfterSetup');
			}
			if (this.state == 'running')
			{
				this.position = this.options.sequences(pos);
				this.event('BeforeUpdate');
				this.update(this.position);
				this.event('AfterUpdate');
			}
		}, 
		loop: function(timePos) 
		{
			if (timePos >= this.startOn) 
			{
				if (timePos >= this.finishOn) 
				{
					this.controller.remove(this);
					this.render(1.0);
					this.event('BeforeCancel');
					this.cancel();
					this.event('AfterCancel');
					this.event('BeforeFinish');
					this.finish(); 
					this.event('AfterFinish');
					return; 
				}
				var pos = (timePos - this.startOn) / this.totalTime, // сколько времени прошло с начала цикла (%)
				frame = Math.round(pos * this.totalFrames); // номер кадра
				if (frame != this.currentFrame) 
				{
					this.render(pos);
					this.currentFrame = frame;
				}
			}
		},
		cancel: function() 
		{
			this.state = 'finished';
		}, 
		finish: function() 
		{
			this.state = 'finished';
		}, 
		setup: function()
		{
		},
		update: function()
		{
		}, 
		event: function() {
			var EventName = arguments[0];
			if (this[EventName]) this[EventName](this, arguments);
			if (this.options[EventName]) this.options[EventName](this, arguments);
		}
	}), 
	
	Controllers: jsUtilsPhoto.ClassCreate(null, {
		init: function() 
		{
			this.effects = [];
			this.interval = null; 
		},
		add: function(effect) 
		{
			var timestamp = new Date().getTime();
			effect.startOn += timestamp; // начало движения
			effect.finishOn += timestamp; // конец движения
			
			this.effects.push(effect); // эффекты
			if (!this.interval) // запускаем событие цикла, которое будет выполняться каждые 10 милисекунд
			{
				var __method = this.check, object = this; 
				this.interval = setInterval(function(){__method.apply(object);}, 10);
			}
		},
		remove: function(effect) 
		{
			var result = [];
			for (var ii in this.effects)
			{
				if (this.effects[ii] != effect)
					result.push(this.effects[ii]);
			}
			this.effects = result;
			
			if (this.effects.length == 0) 
			{
				clearInterval(this.interval);
				this.interval = null;
			}
		},
		check: function() 
		{
			var timePos = new Date().getTime();
			for (var ii = 0, length = this.effects.length; ii < length; ii++)
			{
				this.effects[ii] && this.effects[ii].loop(timePos);
			}
		}
	})
};
jsUtilsEffect.Controller = {
	'default' : new jsUtilsEffect.Controllers()};
 
jsUtilsEffect.Scale = jsUtilsPhoto.ClassCreate(jsUtilsEffect.Base, {
	init: function(element, percent) 
	{
		this.className = 'Scale';
		if (!element) { return false; };
		this.element = element;
		var options = jsUtilsPhoto.ObjectsMerge({
			scaleX: true, 
			scaleXFrom: 1.0,
			scaleXTo: (percent ? percent : 1.0),
			scaleY: true, 
			scaleYFrom: 1.0,
			scaleYTo: (percent ? percent : 1.0)
		}, (arguments[2] || {}));
		this.start(options);
	},
	setup: function() 
	{
		this.originParams = {'top': 0, 'left' : 0, 'width' : 0, 'height' : 0, 'position' : 0};
		for (var key in this.originParams)
			this.originParams[key] = this.element.style[key];

		this.originParams['offset_top'] = this.element.offsetTop;
		this.originParams['offset_left'] = this.element.offsetLeft;
		
		this.scaleDelta = [(this.options.scaleXTo - this.options.scaleXFrom), (this.options.scaleYTo - this.options.scaleYFrom)]; 
		this.dims = [this.element.offsetWidth, this.element.offsetHeight];
		
	},
	update: function(position) // position - это % от конечного размера
	{
		var currentScale = [
			(this.options.scaleXFrom + this.scaleDelta[0] * position),
			(this.options.scaleYFrom + this.scaleDelta[1] * position)];
		this.setDimensions(this.dims[0] * currentScale[0], this.dims[1] * currentScale[1]);
	},
	setDimensions: function(width, height) 
	{
		var d = { };
		if (this.options.scaleX) d.width = Math.round(width) + 'px';
		if (this.options.scaleY) d.height = Math.round(height) + 'px';
		
		this.event('BeforeSetDimensions', d);
		for (var ii in d)
		{
			this.element.style[ii] = d[ii];
		}
		this.event('AfterSetDimensions');
	}
});

jsUtilsEffect.Transparency = jsUtilsPhoto.ClassCreate(jsUtilsEffect.Base, {
	init: function(element) 
	{
		this.element = element;
		if (!this.element) { return false; };
		var options = jsUtilsPhoto.ObjectsMerge({
			from: 0.0,
			to: 1.0 
		}, (arguments[1] || { }));
		
		this.start(options);
	},
	update: function(position) 
	{
		position = (position == 1 || position === '' ? '' : (position < 0.0001 ? 0 : position));
		this.element.style.opacity = position;
		return this.element;
	}, 
	BeforeSetup: function()
	{
		this.element.style.opacity = 0;
		if (this.element.style.visibility == 'hidden')
			this.element.style.visibility = 'visible';
		if (this.element.style.display == 'none')
			this.element.style.display = '';
	}
}
);

jsUtilsEffect.Untwist = jsUtilsPhoto.ClassCreate(jsUtilsEffect.Base, {
	init: function(element) 
	{
		this.className = 'Untwist';
		if (!element) { return false; };
		this.element = element;
		var options = jsUtilsPhoto.ObjectsMerge({
			scaleYFrom: 0.001,
			scaleYTo: 1.0
		}, (arguments[1] || {}));
		this.start(options);
	},
	setup: function() 
	{
		var params = (jsUtilsPhoto.GetElementParams(this.element) || {}); 
		this.dims = [params['width'], params['height']];
		this.originParams = {'bottom': 0, 'top' : 0, 'left' : 0, 'width' : 0, 'height' : 0, 'position' : 0, 'overflow' : 0};
		for (var key in this.originParams)
			this.originParams[key] = this.element.style[key];

		this.scaleDelta = this.options.scaleYTo - this.options.scaleYFrom;
	}, 
	update: function(position) // position - это % от конечного размера
	{
		this.setDimensions(this.dims[1] * (this.options.scaleYFrom + this.scaleDelta * position));
	},
	setDimensions: function(height) 
	{
		this.event('BeforeSetDimensions', height);
		this.element.style.height = Math.round(height) + 'px';;
		this.event('AfterSetDimensions');
	}, 
	finish: function(position) {
		for (var ii in this.originParams)
		{
			this.element.style[ii] = this.originParams[ii];
		}
	}, 
	AfterSetup: function(effect) 
	{
		var res = this.element.style.position;
		if (!res || res == 'static')
		{
			this.element.__style_position = res;
			this.element.style.position = 'relative';
		}
		var res = this.element.style.overflow;
		if (res != 'hidden')
		{
			this.element.__style_overflow = res;
			this.element.style.overflow = 'hidden';
		}
		this.element.style.top = '';
		this.element.style.height = '0px';
		this.element.style.bottom = '0px';
		this.element.style.display = '';
		this.element.style.visibility = 'visible';
	},
	AfterFinish: function(effect) 
	{
		if (this.element.__style_position)
		{
			this.element.style.position = this.element.style.top = this.element.style.left = this.element.style.bottom = this.element.style.right = '';
			this.element.__style_position = null;
		}
		if (this.element.__style_overflow)
		{
			this.element.style.overflow = this.element.__style_overflow;
			this.element.__style_overflow = null;
		}
	}
}
);
bPhotoEffectsLoad = true;