(function($){
	var intCSS = function(el, prop){
		return parseInt($(el).css(prop))||0;
	};
	
	//	extend array object
	$.extend(Array.prototype, {
		min: function(){
			return Math.min.apply({},this);
		},
		
		max: function(){
			return Math.max.apply({},this);
		},
		
		sum: function(){
			for(var i = 0, sum = 0; i < this.length; sum += this[i++]);
			return sum;
		}
	});
	
	//	extend number object
	$.extend(Number.prototype, {
		roundTo: function(to){
			return Math.round(this/to)*to;
		}
	});
	
	$.each(['Width', 'Height'], function(i, name){

		// объявим eHeight and eWidth
		$.fn['e' + name] = function(options, size){
			if (!this[0]) return;
			
			//	объединим опции по умолчанию с опциями, пришедшими в качестве аргумента
			var	options = $.extend({padding: true}, options),
				
				//	axis - массив с вспомогательными строками для рассчета CSS-свойств
				axis = name == 'Width' ? ['Left', 'Right'] : ['Top', 'Bottom'],
				
				//	рассчет CSS-свойств
				padding = options.padding ? (intCSS(this, 'padding' + axis[0]) + intCSS(this, 'padding' + axis[1]))*options.padding : 0,
				border = options.border ? (intCSS(this, 'border' + axis[0] + 'Width') + intCSS(this, 'border' + axis[1] + 'Width'))*options.border : 0,
				margin = options.margin ? intCSS(this, 'margin' + axis[0]) + intCSS(this, 'margin' + axis[1]) : 0;
			
			//	если надо было вернуть размер, возвращаем размер
			//	если надо было присвоить размер, присваиваем его и возвращаем this для подддержки jQuery chainable
			
			
			/* баг сафари неправильно рассчитывает margin-right, если тот не задан явно скриптом или атрибутом */
			// на самом деле вебкит возвращает расстояние отправого края до левого внутреннего края родителя
				//так что маргин обнуляем
				/*if($.browser.safari && name == 'Width' && options.margin && (this.css('margin-right') != this[0].style.marginRight)){
					var parent = this.parent(),
						pLeft = parent.offset().left,
						pWidth = parent[0].offsetWidth,
						left = this.offset().left,
						width = this[0].offsetWidth;
					
					margin -=  (pLeft + pWidth - left - width);
					//alert(pLeft + pWidth - left - width)
				}*/
			
			return size != undefined ?  
				this[name.toLowerCase()](Math.max(0, size - padding - border - margin)) :
				this[name.toLowerCase()]() + padding + border + margin;
		};
		
		/*****************************************************
			делаем блок функций width0 width1 width2 width3 (height -//-)
			только внутренний размер, с паддингом, паддинг + бордер, паддинг + бордер + марджин
		******************************************************/
		$.each([{padding: 0, border: 0, margin: 0}, // опции для width0/height0
				{padding: 1, border: 0, margin: 0},	// опции для width1/height1
				{padding: 1, border: 1, margin: 0},	// опции для width2/height2
				{padding: 1, border: 1, margin: 1}	// опции для width3/height3
			], function(j, style){
					$.fn[name.toLowerCase()+j] = function(size){
						
						//	если наш браузер - IE, то не стоит учитывать padding и border,
						//	нужно отнять его
						var	p = style.padding,
							b = style.border;
						if(size && $.browser.msie){ p--; b--; }
									
								
						if(size != undefined)
							return this.each(function(){
								var
									$this = $(this),
									thisPadding = p,
									thisBorder = b;
								
								// везде кроме IE эти элементы включают в размер padding и border при присваивании
								if(size != undefined && !$.browser.msie){
									var apostates = [ 'input', 'button', 'textarea', 'select' ];
									
									for(var i = 0; i < apostates.length; i++)
										if($this.is(apostates[i])){
											thisPadding = thisBorder = 0;
											break;
										}
								}
						
								//	воспользуемся нашими методами eWidth/eHeight, передав им нужные опции
								$this['e' + name]({ padding: thisPadding, border: thisBorder, margin: style.margin }, size);				
							});
						else return this['e' + name]({ padding: p, border: b, margin: style.margin }, size);
					}
				});
	});


	$.fn.isAncestorOf = function(descendant){
		var checkParent = function(elem, ancestor){
			var parent = elem.parent();
			if(parent.is('body') || parent.is('html')) return false;
			if(parent[0] == ancestor[0]) return true;
			
			return checkParent(parent, ancestor);
		};
		
		return checkParent(descendant, this);
	};
	
	$.extend($.fn, {
		// drag and drop method
		dd: function(opt){
			return this.each(function(){

				//	åñëè äëÿ ýòîãî ýëåìåíòà DD	óæå åñòü
				if($(this).data('dd')){
					var dd = $(this).data('dd');
					
					//	åñëè ýòî êîìàíäà (îïöèè - ñòðîêà)
					if(typeof opt == 'string')
						switch(opt){
							case 'disable': dd.disable(); break;
							case 'enable': dd.enable(); break;
							case 'destroy': dd.destroy(); break;
						}
					else $.extend(dd.options, opt);
					
				// ñîçäàäèì DD îáúåêò è äàäèì ýëåìåíòó ññûëêó íà íåãî
				}else $(this).data('dd', new DD(this, opt));
			});
		}
	});



	DD = function(elem, options){
		this.elem = $(elem);
		this.enabled = true;
		
		//	ïðîèíèöèàëèçèðóåì óñòàíîâêè îáúåêòà (äåôîëòíûå + îïöèè)
		this.settings = $.extend(true, {
			delay: 0,
			proxy: null,
			region: null,
			position: 'relative',
			scroll: {
				sensitivity: 40,
				distance: 100,
				time: 300
			},
			origin: {x: 0, y: 0}
		}, options);
		
		//	íàéäåì ðåãèîí âíóòðè êîòîðîãî ìîæíî òàñêàòü
		//	jq-îáúåêò ëèáî ôóíêöèÿ, êîòîðàÿ âîçâðàùàåò jq-îáúåêò
		this.region = this.settings.region ?
			$.isFunction(this.settings.region) ? this.settings.region.call(this) : $(this.settings.region) :
			undefined;
		
		this.elem.bind('mousedown.ondragstart', this.ondragstart);	
		
		return this;
	};


	$.extend(DD.prototype, {
		ondragstart: function(e){
			//	íå äàäèì ñîáûòèþ ðàñïðîñòðàíÿòüñÿ íà ïðåäêîâ
			e.stopPropagation();
			var	$this = $(this),
				dd = $this.data('dd');
			//console.dir('2');
			if(!dd) return false;
			
			//	ñîõðàíèì åâåíò â îáúåêòå
			dd.e = e;
			
			// çàäàäèì ñîáûòèÿ ÷åðåç çàìûêàíèÿ, ÷òîáû ïåðåäàòü åâåíòû è dd-îáúåêò
			dd.delayTimer = setTimeout( (function(dd){
				return function(){
					//	çàäèñàáîåííûé ää íå ðàáîòàåò
					if(!dd.enabled) return false;
					
					if (dd.delayTimer) {
						clearTimeout(dd.delayTimer);
						dd.delayTimer = null;
					}
				
					/*	ïîêà íå ñêðîëëèëè, â ýòèõ ïåðåìåííûõ ñîõðàíÿåì
						îòñêðîëåííûå ðàññòîÿíèÿ ó áëèæàéøåãî ïðåäêà, êîòîðûé ñêðîëèòñÿ
					*/
					dd.scrollLeft = dd.scrollTop = 0;
				
					//	ïðîèíèöèàëèçèðóåì ïðîêñè
					if(!dd.proxy)
						dd.proxy = dd.settings.proxy ?
						$.isFunction(dd.settings.proxy) ? dd.settings.proxy.call(dd) : $(dd.settings.proxy) :
						dd.elem;
						
					//	åñëè ó ïðîêñè íå îïðåäåëåíî CSS-ñìåùåíèå, çàáüåì íóëÿìè
					dd.proxy.css({left: intCSS(dd.proxy, 'left') + 'px', top: intCSS(dd.proxy, 'top') + 'px'});
					
					if($.focus) $.focus.blur();
					$.focus = dd.proxy.focus();
			
					//	çàïîìíèì ñâîéñòâî position äëÿ ïðîêñè
					if(!dd.proxyPos){
						dd.proxyPos = dd.proxy.css('position');			//	íàéäåì CSS-ñâîéñòâî position ýëåìåíòà
						if(!dd.proxy.data('dd-native-position'))		//	ñîõðàíèì åãî â data('dd-native-position')
							dd.proxy.data('dd-native-position', dd.proxy.css('position'));
						dd.proxy.css('position', dd.settings.position);	//	è óñòàíîâèì position èç óñòàíîâîê
					}
					dd.elem.trigger('dd-start');
			
					//	ðàññ÷èòàåì ïîçèöèè ðåãèîíà, àâòîñêðîëëà è ïðîêñè
					dd.dimensions = dd.__dimensions();
					
					$(document).bind('mousemove.ondrag', function(event){
						dd.ondrag(event);
					});
				}
			})(dd), dd.settings.delay);

			$(document).bind('mouseup.ondragdrop', (function(dd){return function(event){
				dd.ondragdrop(event)
			}})(dd));
			
			//	àíòèñåëåêò
			if($.browser.opera || $.browser.msie){
				dd.unselect = $(':not([unselectable])').andSelf();
				dd.unselect.attr('unselectable', 'on');
			}
			else return false;
		},
		
		ondrag: function(e){
			this.e = e;
			this.elem.trigger('dd-drag-before');

			this.__updateCoords();

			this.__autoScroll();

			$.extend(true, this.dimensions, this.__dimensions({proxy: { size: false, mouseOffset: false }, region: false, autoScroll: false}));

			this.elem.trigger('dd-drag-after');
		},
		
		ondragdrop: function(e){
			
			if(this.settings.autoScroll)
				if(this.settings.autoScroll.data('scrollTimer'))
					clearTimeout(this.settings.autoScroll.data('scrollTimer'));

			$(document).unbind('mousemove.ondrag');
			$(document).unbind('mouseup.ondragdrop');
			
			if (this.delayTimer) {
				clearTimeout(this.delayTimer);
				this.delayTimer = null;
			}else this.elem.trigger('dd-drop');
			
			//	åñëè ïðîêñè áûë çàäàí ôóíêöèåé, óäàëèì ýëåìåíò ïðîêñè ÷òîáû ïðè ñëåäóþùåé èòåðàöèè 
			//	ïðîêñè ÷èòàëîñü èç ôóíêöèè
			if($.isFunction(this.settings.proxy) && this.proxy){
				this.proxy.remove();
				delete this.proxy;
				delete this.proxyPos;
			}
			
			//	çàêðûâàåì àíòèñåëåêò
			if(this.unselect)
				this.unselect.removeAttr('unselectable');
		},
		
		enable: function(){
			if(!this.enabled){
				this.enabled = true;
				this.elem.trigger('dd-enable');
			}else return false;
		},
		
		disable: function(){
			if(this.enabled){
				this.enabled = false;
				this.elem.trigger('dd-disable');
			}else return false;
		},
		
		destroy: function(){
			this.elem.trigger('dd-destroy');
			this.elem.removeData('dd');
			this.elem
				.unbind('mousedown.ondragstart', this.ondragstart)
				.unbind('dd-start')
				.unbind('dd-drag-after')
				.unbind('dd.drag-before')
				.unbind('dd-drop');
			var proxy = this.proxy ? this.proxy : this.elem;
			proxy.css('position', proxy.data('dd-nativePosition'));
			//delete this;
		},
		
		//****************************************************
		//	__dimensions()
		//	íàéäåì îòñòóïû îòíîñèòåëüíî íóëÿ è ðàçìåðû ó ãðóïïû ïðîêñè
		//	left, right, top, bottom, width, height
		//	òàêæå äåëüòó ìåæäó îòñòóïîì ãðóïïû ïðîêñè ìûøüþ
		//	dx, dy
		//	òàêæå âñþ èíôó ïî ðåãèîíó
		//	default settings: { proxy: { offset: true, size: true, mouseOffset: true }, region: true, autoScroll: true }
		__dimensions: function(options){
			var settings = { proxy: { offset: true, size: true, mouseOffset: true }, region: true, autoScroll: true },
				dd = this;
			$.extend(true, settings, options);
		
			//	áóäåì âîçâðàùàòü ðåçóëüòàò
			var result = { };
			
			//	ðàññ÷èòàåì ñìåùåíèå ïðîêñè
			if(settings.proxy.offset){
				//	proxies = [{left, top, width, height}, {left, top, width, height} ... ]
				var	proxies = $.map(this.proxy, function(n){
						if(dd.proxy.length > 1 || !(dd.dimensions && dd.dimensions.proxy))
							var width = $(n).eWidth(), height = $(n).eHeight();
							else var width = dd.dimensions.proxy.width, height = dd.dimensions.proxy.height;
						return $.extend($(n).offset(), {width: width, height: height});
					});
				result.proxy = {
					left: $.map(proxies, function(n){return n.left;}).min(),
					top: $.map(proxies, function(n){return n.top;}).min(),
					right: $.map(proxies, function(n){return n.left + n.width;}).max(),
					bottom: $.map(proxies, function(n){return n.top + n.height;}).max()
				};
			}
			
			//	ðàññ÷èòàåì ðàçìåð ïðîêñè
			if(settings.proxy.size)
				$.extend(result.proxy, {
					width: result.proxy.right - result.proxy.left,
					height: result.proxy.bottom - result.proxy.top
				});
			
			//	ðàññ÷èòàåì äåëüòû ïðîêñè è ìûøè
			if(settings.proxy.mouseOffset)
				$.extend(result.proxy, {
					dx: this.e.pageX - result.proxy.left,
					dy: this.e.pageY - result.proxy.top,
					cssDx: this.e.pageX - intCSS(this.proxy, 'left'),
					cssDy: this.e.pageY - intCSS(this.proxy, 'top')
				});
			
			//	ðàññ÷èòàåì ïîçèöèè è ðàçìåðû ðåãèîíà
			if(settings.region && this.region){
				result.region = {};
				$.extend(result.region, this.region[0] == document ?
					{
						width: this.region.width(),
						height: this.region.height(),
						offset: { left: 0, top: 0 }
					} :	{
						offset: this.region.offset(),
						width: this.region.eWidth(),
						height: this.region.eHeight()
					}
				);
				
				result.region.offset.left += intCSS(this.region, 'border-left-width');
				result.region.offset.top += intCSS(this.region, 'border-top-width');
				
				$.extend(result.region, {
					right: result.region.offset.left + result.region.width,
					bottom: result.region.offset.top + result.region.height
				});
			}
			
			//	ðàññ÷èòàåì ïîçèöèè àâòîñêðîëëà
			if(settings.autoScroll && this.settings.autoScroll){
				result.autoScroll = [];	
				this.settings.autoScroll.each(function(){
					var	$this = $(this);
						
					if(this != document && ( 
						!/auto|scroll/.test($this.css('overflow')) ||
						!/auto|scroll/.test($this.css('overflow-x')) ||
						!/auto|scroll/.test($this.css('overflow-y'))
					)) return;
					var res = { elem: $this };	//	çäåñü áóäåò ðåçóëüòàò äëÿ êîíêðåòíî îäíîãî ýëåìåíòà àâòîñêðîëëà
					
					if(this == document){
						$.extend(res, {
							offset: {left: 0, top: 0},
							width: $this.width(),
							height: $this.height()
						});
						$.extend(res, {
							right: res.width,
							bottom: res.height
						});
					}else {
						$.extend(res, {
							offset: $this.offset(),
							width: $this.width2() - this.offsetWidth + this.clientWidth,
							height: $this.height2() - this.offsetHeight + this.clientHeight
						});
						$.extend(res, {
							right: res.offset.left + intCSS(this, 'borderLeftWidth') + res.width,
							bottom: res.offset.top + intCSS(this, 'borderTopWidth') + res.height
						});
					}
					
					//	çàïèøåì âñå èçìåíåíèÿ ïî äàííîìó àâòîñêðîëó
					result.autoScroll.push(res);
				});
			}
			
			return result;
		},
		
		__updateCoords: function(){
			var e = this.e,
			//	íàéäåì ïðåäïîëàãàåìîå ñìåùåíèå ïðîêñè
			//	ïîñëå äâèæåíèÿ ìûøüþ
				cx = e.pageX - this.dimensions.proxy.dx,
				cy = e.pageY - this.dimensions.proxy.dy;
			
			//	ðàññ÷åò ãîðèçîíòàëüíîé êîîðäèíàòû
			if(!this.settings.axis || this.settings.axis == 'x'){
				if(this.region && cx + this.scrollLeft < this.dimensions.region.offset.left){
					//	ïðîòÿíóëè ëåâåå ðåãèîíà
					var x = intCSS(this.proxy, 'left') - (this.dimensions.proxy.left - this.dimensions.region.offset.left);
				}else if(this.region && cx > this.dimensions.region.right - this.dimensions.proxy.width){
					//	ïðîòÿíóëè ïðàâåå ðåãèîíà
					var x = intCSS(this.proxy, 'left') + this.dimensions.region.right - this.dimensions.proxy.right;
				}else var x = e.pageX - this.dimensions.proxy.cssDx + this.scrollLeft;
			}else var x = intCSS(this.proxy, 'left');
			
			
			//	ðàññ÷åò âåðòèêàëüíîé êîîðäèíàòû
			if(!this.settings.axis || this.settings.axis == 'y'){
				if(this.region && cy + this.scrollTop < this.dimensions.region.offset.top){
					//	ïðîòÿíóëè âûøå ðåãèîíà
					var y = intCSS(this.proxy, 'top') - (this.dimensions.proxy.top - this.dimensions.region.offset.top);
				}else if(this.region && cy > this.dimensions.region.bottom - this.dimensions.proxy.height){
					//	ïðîòÿíóëè íèæå ðåãèîíà
					var y = intCSS(this.proxy, 'top') + this.dimensions.region.bottom - this.dimensions.proxy.bottom;
				}else var y = e.pageY - this.dimensions.proxy.cssDy + this.scrollTop;
			}else var y = intCSS(this.proxy, 'top');
			
			//	îêðóãëèì çíà÷åíèÿ ïî ãðèäó
			if(this.settings.grid){
				x = x.roundTo(this.settings.grid.x) + this.settings.origin.x - Math.floor(this.settings.origin.x / this.settings.grid.x)*this.settings.grid.x;
				y = y.roundTo(this.settings.grid.y) + this.settings.origin.y - Math.floor(this.settings.origin.y / this.settings.grid.y)*this.settings.grid.y;
			}
			
			this.proxy.css({
				left:	x + 'px',
				top:	y + 'px'
			});
		},
		
		
		/*===================================================
			ïðîâåðÿåì ïîçèöèþ ìûøè
			åñëè áëèçêî ê êðàþ ñêðîëëèðóåìîé îáëàñòè, çàïóñêàåì ÷åðåç setTimeout ñêðîëëèðîâàíèå
			çàïîìèíàåì, êóäà ñêðîëëèì (scrollDirection), åñëè ïîçèöèÿ èçìåíèëàñü, òîæå óáèâàåì òàéìåð
			åñëè îòâåëè îò êðàÿ, óáèâàåì scrollTimer
		=====================================================*/
		__autoScroll: function(){
			
			
			//	çàïîìíèì dd-îáúåêò, ÷òîá ïîëüçîâàòüñÿ â çàìûêàíèè
			var dd = this,
				proxy = dd.dimensions.proxy;
				
			if(this.dimensions.autoScroll)
				$.each(this.dimensions.autoScroll, function(i, s){
					//	îñòàíîâèì âñþ òåêóùóþ àíèìàöèþ ñêðîëëèíãà è ïðîêñè
					if(s.elem.data('scrollTimer')){
						clearTimeout(s.elem.data('scrollTimer'));
/*!!!*/				//dd.proxy.stop();
					}
					
					//	åñëè áëèçêî ê êðàþ
					if((s.bottom - dd.settings.scroll.sensitivity < dd.e.pageY) || (s.right - dd.settings.scroll.sensitivity < dd.e.pageX) || (s.offset.left + dd.settings.scroll.sensitivity > dd.e.pageX) || (s.offset.top + dd.settings.scroll.sensitivity > dd.e.pageY)){
						s.elem.data('scrollTimer', setInterval(function(){

							var scroll = { left: 0, top: 0 },
								proxyInScroll = s.elem.isAncestorOf(dd.proxy);
								
							//	âîçëå íèæíåé ãðàíèöû
							if(s.bottom - dd.settings.scroll.sensitivity < dd.e.pageY && (proxyInScroll ? true: (dd.e.pageY < s.bottom && (dd.e.pageX > s.offset.left && dd.e.pageX < s.right))))
								scroll.top += Math.min(s.elem[0].scrollHeight - s.height - s.elem[0].scrollTop, dd.settings.scroll.distance);
							
							//	ñïðàâà
							if(s.right - dd.settings.scroll.sensitivity < dd.e.pageX && (proxyInScroll ? true: (dd.e.pageX < s.right && (dd.e.pageY > s.offset.top && dd.e.pageY < s.bottom))))
								scroll.left += Math.min(s.elem[0].scrollWidth - s.width - s.elem[0].scrollLeft, dd.settings.scroll.distance);
							
							//	ñëåâà
							if(s.offset.left + dd.settings.scroll.sensitivity > dd.e.pageX && (proxyInScroll ? true: (dd.e.pageX > s.offset.left && (dd.e.pageY > s.offset.top && dd.e.pageY < s.bottom))))
								scroll.left += Math.max(-s.elem[0].scrollLeft, -dd.settings.scroll.distance);
							
							//	ñâåðõó
							if(s.offset.top + dd.settings.scroll.sensitivity > dd.e.pageY && (proxyInScroll ? true: (dd.e.pageY > s.offset.top && (dd.e.pageX > s.offset.left && dd.e.pageX < s.right))))
								scroll.top += Math.max(-s.elem[0].scrollTop, -dd.settings.scroll.distance);
							
													
							dd.proxy.each(function(){
								//	åñëè ñêðîëëèðóåìàÿ îáëàñòü - ïðåäîê êàêîãî-ëèáî ïðîêñè
								if($(this).parents().index(s.elem[0]) != -1){
									//	òî ñìåùàåì ýòîò ïðîêñè íà îòñêðîëåííîå ðàññòîÿíèå (åñëè íå âûëàçèò çà ðåãèîí)
									if(dd.region){
										if(scroll.top >= 0)
											var top = Math.max(0, Math.min(scroll.top, dd.dimensions.region.bottom - dd.dimensions.proxy.bottom));
										else
											var top = Math.min(0, Math.max(scroll.top, dd.dimensions.region.offset.top - dd.dimensions.proxy.top));

										if(scroll.left >= 0)
											var left = Math.max(0, Math.min(scroll.left, dd.dimensions.region.right - dd.dimensions.proxy.right));
										else
											var left = Math.min(0, Math.max(scroll.left, dd.dimensions.region.offset.left - dd.dimensions.proxy.left));

									}else var top = scroll.top, left = scroll.left;
									
									$(this).animate({
										'left': parseInt(this.style.left) + left + 'px',
										'top': parseInt(this.style.top) + top + 'px'
									}, dd.settings.scroll.time);

								}
							}); 
							s.elem.animate({
								'scrollLeft': s.elem[0].scrollLeft + scroll.left,
								'scrollTop': s.elem[0].scrollTop + scroll.top
							}, dd.settings.scroll.time, null, function(){dd.elem.trigger('dd-drag-after')});
							
							//	ïåðåñ÷èòàåì offset ðåãèîíà, åñëè îí ðåáåíîê îòñêðîëëèâøåãîñÿ scrollable
							if(dd.region && dd.region.parents().index(s.elem[0]) != -1){
								var reg = dd.dimensions.region;
								reg.offset.left -= scroll.left;
								reg.offset.top -= scroll.top;
								reg.right -= scroll.left;
								reg.bottom -= scroll.top;
								
							}
							
							//	if proxy is descendant of the autoScroll area
							if(proxyInScroll){
								dd.scrollLeft += scroll.left;
								dd.scrollTop += scroll.top;
							}
						}, 500));
					}
				});
		}
	});
	
})(jQuery);

