(function($){

var IE = $.browser.msie,
	DOC = window.document,
	$DOC = $(DOC),
	$window = $(window),
	HTML,
	VOILE, WINDOW, WINDOW_BODY,
	NS = 'jmw',
	ID_VOILE = NS+'-voile',
	ID_WINDOW = NS+'-window',
	CLS_ELM_PLASE = NS+'-elm_place',
	CLS_WINDOW_HIDDEN = NS+'-window-hidden',
	OPENED = false,
	ELM, ELM_PLASE,
	CUR_CONFIG, CONFIG = {
		esc: true,
		body: ID_WINDOW,
		voile: true,
		voileColor: '#36f',
		voileOpacity: .5,
		x: .5,
		y: .3,
		z: 9999,
		chase: 200
	};


function open(elm, config)
{
	if (OPENED) return;
	OPENED = true;

	ELM = elm;
	CUR_CONFIG = config;

	for (var i in CONFIG)
		if (CUR_CONFIG[i] === undefined)
			CUR_CONFIG[i] = CONFIG[i];

	if (CUR_CONFIG.voile) {
		VOILE.show();
		updateVoile();
	}

	$window.resize(onResize);
	CUR_CONFIG.chase && $window.scroll(onScroll);
	CUR_CONFIG.esc && $DOC.keypress(onKeyPress);
	CUR_CONFIG.cls && WINDOW.addClass(CUR_CONFIG.cls);

	ELM_PLASE = (!elm.parentNode || elm.parentNode.nodeName === '#document-fragment' || elm.parentNode === WINDOW_BODY[0])
		 ? null : $(createElm('span')).addClass(CLS_ELM_PLASE).insertAfter(elm);

	if (elm.parentNode !== WINDOW_BODY[0])
		WINDOW_BODY.append(elm);
	else
		$(elm).show();

	WINDOW.addClass(CLS_WINDOW_HIDDEN).show();

	updateWindowPosition();

	CONFIG.open && CONFIG.open.call(ELM, WINDOW, WINDOW_BODY, VOILE);
	CUR_CONFIG.open && (CUR_CONFIG.open !== CONFIG.open) && CUR_CONFIG.open.call(ELM, WINDOW, WINDOW_BODY, VOILE);

	CUR_CONFIG.closer && $(CUR_CONFIG.closer, WINDOW).click(close);

	WINDOW.removeClass(CLS_WINDOW_HIDDEN);
}

function close()
{

	if (!OPENED) return;

	if (CUR_CONFIG.close && false === CUR_CONFIG.close.call(ELM, WINDOW, WINDOW_BODY, VOILE)) return;

	OPENED = false;
	$window
		.unbind('resize', onResize)
		.unbind('scroll', onScroll);

	$DOC.unbind('keypress', onKeyPress);

	WINDOW.hide();
	CUR_CONFIG.cls && WINDOW.removeClass(CUR_CONFIG.cls);

	CUR_CONFIG.closer && $(CUR_CONFIG.closer, WINDOW).unbind('click', close);

	if (ELM_PLASE) {
		$(ELM).insertBefore(ELM_PLASE);
		ELM_PLASE.remove();
	}
	else if (CUR_CONFIG.notRemoveOnClose) $(ELM).hide();
	else $(ELM).remove();

	VOILE.hide();

	if (CUR_CONFIG.afterClose) CUR_CONFIG.afterClose.call(ELM, WINDOW, WINDOW_BODY, VOILE);
}

function onResize()
{
	updateVoile();
	updateWindowPosition();
}

function onKeyPress(e)
{
	if (e.keyCode === 27) close();
}

function onScroll()
{
	var p = getWindowPosition();
	(CUR_CONFIG.chase === true) ? WINDOW.css(p) : WINDOW.stop().animate(p, CUR_CONFIG.chase);
}

function getWindowPosition()
{
	var t = (CUR_CONFIG.y < 1) ? ($window.height() - WINDOW.outerHeight()) * CUR_CONFIG.y + $window.scrollTop() : CUR_CONFIG.y,
		l = (CUR_CONFIG.x < 1) ? ($window.width() - WINDOW.outerWidth()) * CUR_CONFIG.x + $window.scrollLeft() : CUR_CONFIG.x;

	if (t < 0) t = 0;
	if (l < 0) l = 0;

	return {left: l, top: t};
}

function updateWindowPosition()
{
	WINDOW.css(getWindowPosition());
}

function updateVoile()
{
	if (!CUR_CONFIG.voile) return;
	VOILE.css({
		width: HTML.clientWidth,
		height: HTML.clientHeight
	});
	IE ? setTimeout(syncVoile, 1) : syncVoile();
}

function syncVoile()
{
	var sw = HTML.scrollWidth,
		sh = HTML.scrollHeight,
		cw = HTML.clientWidth,
		ch = HTML.clientHeight;

	VOILE.css({
		width: sw > cw ? sw : cw,
		height: sh > ch ? sh : ch
	});
}

// initialize
////////////////////////////////////////////////////////////////////////////////
function initialize()
{
	initialize = function(){};

	// css
	var css = [
		'#',ID_VOILE,'{display:none;position:absolute;top:0;left:0;z-index:',CONFIG.z,'; background:',CONFIG.voileColor,'; opacity: ',CONFIG.voileOpacity,'}',
		'#',ID_WINDOW,'{display:none;position:absolute;z-index:',CONFIG.z,'; top: 0; left: 0}',
		'.',CLS_WINDOW_HIDDEN,'{visibility: hidden}',
		'span.',CLS_ELM_PLASE,'{display: none}'
	];

	if ($.ie) css.push('#',ID_VOILE,' iframe {filter: alpha(opacity=0);}');

	var style = createElm('style'), head = DOC.documentElement.firstChild;
	style.setAttribute('type', 'text/css');

	if (style.styleSheet) style.styleSheet.cssText = css.join('');
	else style.appendChild(DOC.createTextNode(css.join('')));

	head.insertBefore(style, head.firstChild);

	if ($.ie) $.ie.css(style.styleSheet);

	// elems
	HTML = DOC.body.parentNode;
	VOILE = $(createElm('div')).attr('id', ID_VOILE).appendTo(DOC.body);
	WINDOW = $(createElm('div')).attr('id', ID_WINDOW).appendTo(DOC.body);

	if ($.ie) VOILE.html('<iframe src="about:blank" width="100%" height="100%" />')

	if (CONFIG.window) WINDOW.append(CONFIG.window);

	WINDOW_BODY = CONFIG.body === ID_WINDOW ? WINDOW : $(CONFIG.body, WINDOW);

	CONFIG.init && CONFIG.init(WINDOW, WINDOW_BODY, VOILE);
}

// plugin
////////////////////////////////////////////////////////////////////////////////

$.modal = function()
{
	var a = arguments[0];
	if (typeof a === 'object') {
		for (var i in a) CONFIG[i] = a[i];
	}
	else {
		initialize();
		open(document.createTextNode(a), arguments[1] || {});
	}
};

$.modal.close = close;

$.fn.modal = function(config)
{
	initialize();
	this[0] && open(this[0], config || {});
	return this;
};


// utils
////////////////////////////////////////////////////////////////////////////////
function createElm(name)
{
	return DOC.createElement(name);
}


})(jQuery);