/**
 * @author Sergey Chikuyonok (gonarch@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */

function Leveling(elem){
	this.root = $(elem);
	this.img_container = this.root.find('.big-image');
	this.img_ptr = this.img_container.find('img');
	this.items = this.root.find('.preview li');
	
	if(!this.items.filter('.selected').length){
		this.items.filter(':first').addClass('selected');
	}
	
	this.tools = this.root.find('.tools>*');
	this.current_tool = this.tools.filter('.selected');
	if(this.current_tool.length){
		this.current_tool.removeClass('selected');
		this.root.replaceClass('tool-\\S+', 'tool-'+this.current_tool[0].className);
		this.current_tool.addClass('selected');
	}

	this.canvas_width = this.img_container.width();
	this.canvas_height = this.img_container.height();

	this.zoom_step = 0.2;
	this.zoom_rect_threshold = 5;

	this._state = 0;
	this._show_rect = false;

	//create hidden zoom rectangle
	this._zoom_rect = this.img_container.append('<div>').find('div').addClass('zoom-rect').css('display', 'none');

	var img_src = this.img_ptr.attr('src');

	//for IE, replace image with layer
	if($.browser.msie){
		var img = $('<div>').addClass('img');
		this.img_ptr.parent().append(img[0]).end().remove();
		this.img_ptr = img;
	}

	this.attachEvents();
	this.loadImage(img_src);
};

Leveling.STATE_PAN = 1;
Leveling.STATE_ZOOMIN = 2;
Leveling.STATE_ZOOMOUT = 4;

Leveling.prototype = {
	/**
	 * Load image
	 * @param {String} src
	 */
	loadImage: function(src,target){
		var me = this;
		
		if(target){
			if(this.loader_icon){
				clearInterval(this.icon_timeout);
				this.loader_icon.removeClass("on");
				this.loader_icon = null;
				delete this.loader_icon;
			}
			
			this.loader_icon = $(target).find('i').eq(0);
			this.icon_timeout = setTimeout(function(){me.loader_icon.addClass("on");},500);
		}
		
		if(this._img){
			this._img = null;
			delete this._img;
		}
		this._img = new Image();
		this._img.onload = function(){
			me.onLoad(me._img);
			if(me.loader_icon){
				clearInterval(me.icon_timeout);
				me.loader_icon.removeClass("on");
			}
		};

		this._img.src = src;
	},

	attachEvents: function(){
		var me = this;
		var f = function(evt){
			return me.dispatchEvent(evt);
		};

		var f2 = function(){
			return !me.getState();
		};

		$(document).mousemove(f).mouseup(f).bind('dragstart', f2).bind('drag', f2).bind('select', f2);
		this.img_ptr.mousedown(f).mouseup(f);

		this.tools.click(function(){
			var obj = $(this);
			me.tools.removeClass('selected');
			me.root.replaceClass('tool-\\S+', 'tool-'+obj[0].className);
			me.current_tool = obj.addClass('selected');
		});

		this.items.find('a').click(function(evt){
			me.items.removeClass('selected');
			var obj = $(this);
			obj.parent().addClass('selected');
			me.loadImage(obj.attr('href'),obj);
			return false;
		});
	},

	/**
	 * Event dispatcher
	 * @param {Event} evt
	 */
	dispatchEvent: function(evt){
		switch(evt.type){
			case 'load':
				this.onLoad(evt);
				break;
			case 'mousedown':
				this.onMouseDown(evt);
				break;
			case 'mouseup':
				this.onMouseUp(evt);
				break;
			case 'mousemove':
				this.onMouseMove(evt);
				break;
		}
	},

	/**
	 * Event invoked when image was loaded
	 * @param {Image} img
	 */
	onLoad: function(img){
		this.image = {
			src: img.src,
			max_width: img.width,
			max_height: img.height,
			aspect_ratio: img.width / img.height
		};

		//find minimum image size
		if(img.width > img.height){
			this.image.min_width = this.canvas_width;
			this.image.min_height = Math.round(this.image.min_width / this.image.aspect_ratio);
		}
		else{
			this.image.min_height = this.canvas_height;
			this.image.min_width = Math.round(this.image.min_height * this.image.aspect_ratio);
		}

		//calculate image scale
		this.image.min_scale = this.image.min_width / this.image.max_width;
		this.image.max_scale = 1;

		//place image
		this.img_ptr.css('display', 'none');
		if($.browser.msie){
			this.img_ptr[0].style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+this.image.src+'",sizingMethod="scale")';
		}
		else{
			this.img_ptr.attr('src', this.image.src);
		}
		this.img_ptr.css(
			{
				width: this.image.min_width,
				height: this.image.min_height,
				left: (this.canvas_width - this.image.min_width) / 2,
				top: (this.canvas_height - this.image.min_height) / 2,
				display: 'block'
			}
		);

		//set current image state
		this.image.width = this.image.min_width;
		this.image.height = this.image.min_height;
		this.image.scale = this.image.min_scale;
	},

	/**
	 * Zoom in
	 * @param {Number} [cx] X-coordinate of center point
	 * @param {Object} [cy] Y-coordinate of center point
	 */
	zoomIn: function(cx, cy){
		this.zoomTo(this.image.scale + this.zoom_step, cx, cy);
	},

	/**
	 * Zoom out
	 * @param {Number} [cx] X-coordinate of center point
	 * @param {Object} [cy] Y-coordinate of center point
	 */
	zoomOut: function(cx, cy){
		this.zoomTo(this.image.scale - this.zoom_step, cx, cy);
	},

	/**
	 * Zoom image to specified level
	 * @param {Number} level Zoom percentage
	 * @param {Number} [cx] X-coordinate of center point
	 * @param {Number} [cy] Y-coordinate of center point
	 */
	zoomTo: function(level, cx, cy){
		//level must be in min/max range
		level = Math.min(this.image.max_scale, Math.max(this.image.min_scale, level));

		cx = cx || 0;
		cy = cy || 0;

		//calculate new image size
		var width = this.image.max_width * level;
		var height = this.image.max_height * level;

		var img = this.img_ptr[0];

		//find absolute coordinats of center point
		var x = cx /* - img.offsetLeft */;
		var y = cy /* - img.offsetTop */;

		//calculate offset of image to leave center point on its place
		var dx = x * (1 - width / this.image.width);
		var dy = y * (1 - height / this.image.height);

		this.img_ptr.css('visibility', 'hidden');
		this.resizeTo(width, height);
		this.moveBy(dx, dy);
		this.img_ptr.css('visibility', '');
		this.image.scale = level;
	},

	/**
	 * Resize image
	 * @param {Number} width
	 * @param {Number} height
	 */
	resizeTo: function(width, height){
		this.img_ptr.css({
			width: width,
			height: height
		});

		this.image.width = width;
		this.image.height = height;
	},

	/**
	 * Offset image by coordinates
	 * @param {Number} dx
	 * @param {Number} dy
	 */
	moveBy: function(dx, dy){
		this.moveTo(this.img_ptr[0].offsetLeft + dx, this.img_ptr[0].offsetTop + dy);
	},

	/**
	 * Move image to specified coordinates
	 * @param {Number} x
	 * @param {Number} y
	 */
	moveTo: function(x, y){
		//find min/max coordinates for image
		var min_x = this.canvas_width - this.image.width;
		if(min_x > 0){ min_x/=2; }
		var max_x = (min_x > 0) ? min_x : 0;

		var min_y = this.canvas_height - this.image.height;
		if(min_y > 0){ min_y/=2; }
		var max_y = (min_y > 0) ? min_y : 0;

		//ensure that passed coordinates are in bounds
		x = Math.min(max_x, Math.max(min_x, x));
		y = Math.min(max_y, Math.max(min_y, y));

//		this.img_ptr.css({left: x, top: y});
		var s = this.img_ptr[0].style;
		s.left = x+'px';
		s.top = y+'px';
	},

	setState: function(num){
		this._state = num;

		if(!num){
			this._zoom_rect.css('display', 'none');
			this._show_rect = false;
		}
	},

	getState: function(){
		return this._state;
	},

	onMouseDown: function(evt){
		//remember cursor position
		this._drag_start = {x: evt.pageX, y: evt.pageY};

		//get absolute image coordinates
		this._image_pos = this.img_ptr.offset();

		//remember image position
		var img = this.img_ptr[0];
		this._img_pos = {x: img.offsetLeft, y: img.offsetTop};

		if(this.current_tool){
			if(this.current_tool.is('.pan')){
				this.setState(Leveling.STATE_PAN);
			}
			else if(this.current_tool.is('.zoomin')){
				this.setState(Leveling.STATE_ZOOMIN);
			}
			else if(this.current_tool.is('.zoomout')){
				this.setState(Leveling.STATE_ZOOMOUT);
			}
		}


		evt.preventDefault();
		return false;
	},

	/**
	 * Method invoked when user moves mouse on document
	 * @param {Event} evt
	 */
	onMouseMove: function(evt){
		switch(this.getState()){
			case Leveling.STATE_PAN:
				this.onDrag(evt);
				break;
			case Leveling.STATE_ZOOMIN:
				if(!this._show_rect &&
					(
						Math.abs(this._drag_start.x - evt.pageX) >= this.zoom_rect_threshold ||
						Math.abs(this._drag_start.y - evt.pageY) >= this.zoom_rect_threshold
					)
				){
					this._show_rect = true;
					var o = this.img_container.offset();
					this._zoom_rect_pos = {x: this._drag_start.x - o.left, y: this._drag_start.y - o.top};
					this._zoom_rect.css({display: 'block'});
				}

				if(this._show_rect){
					this._drawRect(evt);
				}
		}
	},

	onDrag: function(evt){
		var dx = evt.pageX - this._drag_start.x;
		var dy = evt.pageY - this._drag_start.y;

		this.moveTo(this._img_pos.x + dx, this._img_pos.y + dy);
	},

	/**
	 * Draw zoom rectangle
	 * @param {Event} evt
	 */
	_drawRect: function(evt){
		var dx = evt.pageX - this._drag_start.x;
		var dy = evt.pageY - this._drag_start.y;

		var w = Math.abs(dx);
		var h = Math.abs(dy);
		var x = (dx > 0) ? this._zoom_rect_pos.x : this._zoom_rect_pos.x - w;
		var y = (dy > 0) ? this._zoom_rect_pos.y : this._zoom_rect_pos.y - h;

		this._zoom_rect.css({
			left: x,
			top: y,
			width: w,
			height: h
		});
	},

	/**
	 * Method invoked when user releases mouse button
	 * @param {Event} evt
	 */
	onMouseUp: function(evt){
		if(evt.target == this.img_ptr[0]){
			switch(this.getState()){
				case Leveling.STATE_ZOOMIN:
					this.zoomIn(evt.pageX - this._image_pos.left, evt.pageY - this._image_pos.top);
					break;
				case Leveling.STATE_ZOOMOUT:
					this.zoomOut(evt.pageX - this._image_pos.left, evt.pageY - this._image_pos.top);
					break;
			}
		}
		else if(this._show_rect && this.getState() == Leveling.STATE_ZOOMIN){
			this.zoomByRect();
		}
		this.setState(0);

	},

	zoomByRect: function(){
		var w = this._zoom_rect.width(), h = this._zoom_rect.height();
		var x = this._zoom_rect[0].offsetLeft, y = this._zoom_rect[0].offsetTop;
		var img_x = this.img_ptr[0].offsetLeft, img_y = this.img_ptr[0].offsetTop;

		var k = (w > h) ? w/this.canvas_width : h/this.canvas_height;
		this.zoomTo(this.image.scale/k, x + w/2 - img_x, y + h/2 - img_y);
	}
};

$(function(){
	$('.leveling').each(function(){
		new Leveling(this);
	});
});
