var DS =        eAu.DS;
var LMS =       eAu.LMS;
var Bookmarks = eAu.Bookmarks;
var Test =      eAu.Test;

var use_internal_navigation = false;

$(function () {
	$('#eAu-show-test-stats').hide();
});


// Логика работы с LMS
// TODO: переделать 
$window.bind('eAu:test:start eAu:test:finish eAu:item:viewed', function (event) {
	if (!LMS.ok()) { return; }
	var lesson = window.linkedNode;
	if (lesson == head) {
		lesson = _Xml.child(head, 'study-modules');
	}
	if (!lesson) { return; }
	var allTests = _.filter(_Xml.select(lesson, "./descendant-or-self::test"), function (test) {
		return _Xml.isVisible(test, true);
	});
	var allSPMSNodes = _.reduce(_Xml.select(lesson, "./descendant-or-self::studiedproblem"), function (memo, node) {
		if (!_Xml.isVisible(node, true)) {
			return memo;
		}
		if (!_Xml.isGroupped(node)) {
			memo = memo.concat(_Xml.childs(node, 'text object'));
		} else {
			memo.push(node)
		}
		return memo;
	}, []);
	var raw = 0;
	var ViewedItems = getViewedItems();
	
	var hasUnwatchedSPs = !!_.detect(allSPMSNodes, function (sp) {
		var id = _Xml.id(sp);
		return _.indexOf(ViewedItems, id) == -1;
	});
	var hasNotPassedTests = !!_.detect(allTests, function (test) {
		return eAu.Test.passed(test) !== true;
	});
	var hasNotAttemptedTests = !!_.detect(allTests, function (test) {
		return eAu.Test.attempted(test) !== true;
	});
	var raw = _.reduce(allTests, function (totalScore, test) {
		var testScore = eAu.Test.getOption(test, 'scorePercent');
		totalScore += testScore;
		return totalScore;
	}, 0);
	raw = allTests.length ? raw / allTests.length : 100;
	
	if (allTests.length) {
		LMS.setScoredLessonData(raw, 0, 100);
	}
	
	if (hasUnwatchedSPs || hasNotAttemptedTests) {
		LMS.setLessonStatus('incomplete');
	} else if (allTests.length == 0) {
		LMS.setLessonStatus('completed');
	} else if (hasNotPassedTests) {
		LMS.setLessonStatus('failed');
	} else if (!hasNotPassedTests) {
		LMS.setLessonStatus('passed');
	}
});

function drawMap () {
	swfobject.removeSWF('mapSwf');
	$('#map-holder').html('<div id="map-container" style="width: 100%; height: 100%;"></div>');
	var tests = _.filter(_Xml.select(window.linkedNode, ".//test"), function (test) {
		return _Xml.isVisible(test, true) && _Xml.closest(test, 'study-modules');
	});
	drawMap.hasTests = !!tests.length;
	var testArr = _.map(tests, function (test) {
		return {
			Id: _Xml.id(test),
			Threshold: eAu.Test.getOption(test, 'passedPC'),
			Score: eAu.Test.attempted(test) ? eAu.Test.getOption(test, 'scorePercent') : 0,
			Passed: !!eAu.Test.attempted(test) && !!eAu.Test.passed(test),
			Title: _Xml.title(test)
		};
	});
	var flashvars = {
		fv_color1: encodeURIComponent(window.fv_color1 || "#f78f15"),
		fv_color2: encodeURIComponent(window.fv_color2 || "#1945a4"),
		fv_data: encodeURIComponent("[]")
	};
	var params = {
		menu: "false",
		quality: "high",
		scale: "noscale",
		wmode: "transparent",
		allowfullscreen: "false",
		allowscriptaccess: "always",
		allownetworking: "all"
	};
	var attributes = {
		id: "mapSwf"
	};

	if (testArr.length) {
		try {
			flashvars.fv_data = encodeURIComponent(JSON.stringify(testArr));
			swfobject.embedSWF(
				"images/swf/mapmap.swf"
				, "map-container"
				, "100%"
				, "100%"
				, "9.0.115"
				, "scripts/lib/external/expressInstall.swf"
				, flashvars, params, attributes
			);
		} catch (error) {
			try { console.log(error); } catch (e) {}
		}
	}
}

function initMap () {
	drawMap();
}

$window.one('course:ready', function () {
	if (head) {
		initMap();
	}
});

$window.bind('eAu:test:finish', function () {
	try {
		drawMap();
	} catch (error) {}
});

$(window).one('course:ready', function() {
	if (head) {
		window.sequencingMode = /^(linear|free)$/.test(_Xml.attr(head, 'sequencing-mode'))
			? _Xml.attr(head, 'sequencing-mode')
			: 'free';
	}

	window.use_internal_navigation = !eAu.LMS.ok();

	$('#menu-content')
		.click(function(event) {
			if ($(event.target).is('.menu-link-studiedproblems')
			    || $(event.target).is('.menu-link-lesson')
			    || $(event.target).is('.menu-link-unit')
			    || $(event.target).is('.hitbox')) {
				event.preventDefault();
				event.stopPropagation();
				return !toggleMenuItem($(event.target).parents('.tree-folder:first'))
			}
		});

	$(document).bind('mousemove click keyup', checkLink);
	
	$('#some-tests').click(function (event) {
		if (!$(event.target).is('a') && !$(event.target).parents('a:first').length) {
			return;
		}
		$.fn.colorbox.close();
	});
	
	$('#menu-content').bind('click', function (event) {
		$target = $(event.target);
		var tests = [],
		    targetNode;
		if (!$target.is('.eAu-access-forbidden-inner-link') && !$target.is('.eAu-access-forbidden-link'))
			return;
		
		$target.css('backgroundColor', '#16173f');
		$target.effect('highlight', { color: '#ffffff', complete: function () {
			$(this).css('backgroundColor', 'transparent');
		} });
		
		_.defer(function () {
			new eAu.Tooltip('<h2 class="eAu">Раздел недоступен</h2><div>Вы ещё не прошли все тесты, предшествующие данному разделу</div>', { target: event.target, cls: 'eAu-menu-tooltip' });
		});
		
	});
	
	$('#cont').bind('click', function (event) {
		$target = $(event.target);
		var tests = [],
		    targetNode;
		if (!$target.is('.eAu-access-forbidden-inner-link') && !$target.is('.eAu-access-forbidden-link'))
			return;
		
		$target.effect('highlight', { color: 'red' });
		
		if (!$target.attr('__dbId'))
			return;
		
		targetNode = _Xml.$(head, $target.attr('__dbId'));
		
		if (!targetNode || !getRightsForNode(targetNode))
			return;
		
		_.each(getRightsForNode(targetNode).previousTestIds || [], function (test) {
			if (!Test.passed(test)) {
				tests.push(test);
			}
		});
		
		$('#some-tests').html('<p>Вам необходимо пройти следующие тесты, что-бы получить доступ к данному разделу:</p><ul>' + _.map(tests, function (test) {
			// TODO: Нормалёк ли это?
			var lesson = _Xml.parentOrSelf(test, 'lesson unit')
			return '<li><a href="index.htm" target="_top" db-id="'+ _Xml.id(test) +'">' + (lesson ? _Xml.title(lesson) + ' → ' : '') + _Xml.title(test) + '</a></li>';
		}).join('\n') + '</ul>');
		
		if (!tests.length) {
			return;
		}
		
		$.fn.colorbox({
			inline: true,
			href: '#some-tests',
			scrolling: true,
			title: 'Тесты',
			maxWidth: $window.width() - 100,
			maxHeight: $window.height() - 100
		});
	});

	var mode = getMode();
	var status = false;

	if (/^(main|appendix)$/.test(mode) && DS && head) {
		status = link_info(mode == 'appendix'
			? DS.get('appendix-id')
			: DS.get('id')
		);
	}
	if (!status) {
		// Либо нода не найдена, либо переход запрещён
		// Или Псевдо-модули -- поиск, глоссарий
		// Или DS отсутствует :(
		loadingProxy();
	}
});

// При загрузке цепляем необходимые события и показываем курс
$(function() {
	$('#course-title')
		.html('<span>Подождите, идёт загрузка курса</span>');
	showContentLoader();

	$(window).bind('resize', function(event, thread) {
		var AC = arguments.callee;
		var This = this;

		if (!thread) {
			if (AC.t) { clearTimeout(AC.t); }
			AC.t = setTimeout(function() {
				$(window).trigger('resize', [true]);
			}, 0);
			return;
		}
		Resizers.resize(event);
	});
	$(document).bind('mousedown', function(event) {
		var $this = $(this);
		var parent = $(event.target)
			.parents('.auto-hide')
			.filter(function() {
				return _Xml.attr(this, 'autohide') == "yes";
			});
		var parentButton = $(event.target).is('.button') ? $(event.target) : $(event.target).parents('.button');
		var autoHide = $('.auto-hide').filter(function() {
			return _Xml.attr(this, 'autohide') == "yes";
		});

		var buttonToBecomeCurrent = {length: 0};

		if (parentButton.length && parentButton.attr('id')) {
			autoHide = autoHide.filter(':not(#'+ parentButton.attr('id').replace(/button-/, '') +')');
			var container = $('#'+ parentButton.attr('id').replace(/button-/, ''));
			if (container.length && container.is(':visible')) {
				buttonToBecomeCurrent = parentButton;
			}
		}

		if (parent.length) {
			autoHide = autoHide.filter(':not(#'+ parent.attr('id') +')')
			buttonToBecomeCurrent = $('#button-'+ parent.attr('id'));
		}
		var ButtonsCurrent = $('.button-current');
		if (buttonToBecomeCurrent.length) {
			ButtonsCurrent = ButtonsCurrent.filter(':not(#'+ buttonToBecomeCurrent.attr('id') +')');
			buttonToBecomeCurrent.addClass('button-current');
		}
		ButtonsCurrent.removeClass('button-current');

		_.each(autoHide.get(), function(element) {
			if (isElementVisible($(element)) && $(element).attr('eAu-animation-direction') === 'show') {
				$('#button-' + element.id).trigger('mouseup', ['document']);
			}
		});
	});

	if (courseDataLoaded) {
		$(window).trigger('course:ready');
	} else {
		$(window).one('course:data-loaded', function(event) {
			$(window).trigger('course:ready');
		});
	}

	$(window).trigger('resize');
});

$(function () {
	var input = $('#search .form input');
	var searchB = $('#search .form a.submit');
	searchB.click(function () {
		if ((input.val() || '').length < 3) {
			input.addClass('eAu-error-input');
			input.effect('highlight', {color: '#ff5e5e'}, 800);
			return false;
		}
		DS.set('search', '');
		executeSearchQuery({ str: input.val() });
		return false;
	});
	input.bind('change keypress', function (event) {
		$(this).removeClass('eAu-error-input');
		if (event.type == 'keypress' && event.keyCode == 13) {
			searchB.trigger('click');
		}
	});
});

$window.bind('eAu:content:afterchange eAu:search:results:shown', function (event, data) {
	$('h1, h2', data.node).filter(function () {
		return !$(this).parent().is('.eAu-funky-header-wrapper');
	}).wrap('<div class="eAu-funky-header-wrapper"></div>');
});

$window.bind('eAu:content:afterchange eAu:search:results:shown', function (event, data) {
	$('a', data.node).trigger('mousemove');
});

$(function() {
	if (hideLoader) {
		hideLoader();
	}
});

$(window).bind('load', function(event) {
	$(window).trigger('resize');
});

/* BEGIN: Content */
function showContentLoader(element) {
	element = $(element || document.getElementById('cont'));

	text_transform('<div class="load-indicator"></div>', element, false)
}
function showModalLoadingOverlay () {
	if (!$('#modal-loading-overlay').length) {
		$('#content').append('<div id="modal-loading-overlay"/>');
	}
	$('#modal-loading-overlay').show();
}
function hideModalLoadingOverlay () {
	$('#modal-loading-overlay').hide();
}

// TODO: нужно грамотно фильтровать меню на основе атрибута ?id=
//       либо вообще прятать его если доступен только один элемент (studiedproblem)
function loadContent (element, selected) {
	var itemsXPath = [
		"glossary[not(@visibility = 'hidden')]",
		"appendix-item[not(@visibility = 'hidden')]",
		"modules[not(@visibility = 'hidden')]/lesson[not(@visibility = 'hidden')]",
		"modules[not(@visibility = 'hidden')]/unit[not(@visibility = 'hidden')]",
		"modules[not(@visibility = 'hidden')]/studiedproblem[not(@visibility = 'hidden')]",
		"modules[not(@visibility = 'hidden')]/studiedproblems[not(@visibility = 'hidden')]",
		"lesson[not(@visibility = 'hidden')]",
		"subject[not(@visibility = 'hidden')]",
		"references[not(@visibility = 'hidden')]",
		"unit[not(@visibility = 'hidden')]",
		"studiedproblem[not(@visibility = 'hidden')]",
		"studiedproblems[not(@visibility = 'hidden')]",
		"test[not(@visibility = 'hidden')]"
	].join(" | ");
	arguments.callee.atLeastOneLoaded = true;
	
	if (selected == null) {
		selected = element;
	}

	function item (node, options) {
		var already_have_subject = false;
		options || (options = {});
		if (!node) { return ''; }

		var menu = [];
		var items = _Xml.select(node, itemsXPath);
		
		if (options.isRoot && !items.length) {
			items.push(node);
		}
		options.isRoot = false;
		if (options.leafNodes == null) {
			options.leafNodes = 0;
		}
		if (options.totalNodes == null) {
			options.totalNodes = 0;
		}

		for (var i = 0; i < items.length; i++) {
			var cur = items[i];

			var link = '<a href="index.htm" class="menu-link-'+ _Xml.name(cur) +'" target="_top" db-id="'+ _Xml.id(cur) +'" id="EU-'+_Xml.id(cur)+'" title="'+_Xml.t(cur)+'">'+_Xml.t(cur)+'</a>';

			if (/^(references|subject)$/.test(_Xml.name(cur))) {
				if (!already_have_subject) {
					already_have_subject = true;
					menu.push( '<div class="tree-item tree-leaf tree-container-'+ _Xml.name(cur) +'">'+ link +'</div>' );
					options.leafNodes += 1;
				}
			} else if (/^(studiedproblem|test|glossary)$/.test(_Xml.name(cur))) {
				menu.push( '<div class="tree-item tree-'+ (_Xml.name(cur) == 'test' ? 'test' : 'leaf') +' tree-container-'+ _Xml.name(cur) +' '+ (_Xml.attr(cur, 'is-practice') == 'true' ? 'tree-leaf-practice tree-container-'+ _Xml.name(cur) +'-practice' : '') +'">'+ link +'</div>' );
				options.leafNodes += 1;
			} else {
				var itemContent = item(cur, options);
				var content = itemContent
					? '<div>'+ itemContent +'</div>'
					: '';

				link = (content == '')
					? link
					: '<span class="hitbox"></span>'+ link;
				var state = DS.has('|menu_state_'+ _Xml.id(cur))
					? DS.get('|menu_state_'+ _Xml.id(cur))
					: 'collapsed';
				menu.push( '<div ref_db_id="'+ _Xml.id(cur) +'" class="tree-item '+ (itemContent != '' ? ('tree-folder tree-folder-'+ state) : 'tree-leaf') +' tree-container-'+ _Xml.name(cur) +'">'+ link + content +'</div>' );
				if (itemContent == '') {
					options.leafNodes += 1;
				}
			}
			options.totalNodes += 1;
		}
		return menu.join("");
	}
	function renderMenu (RootElement, contentId) {
		var mc = document.createElement('div')
		  , iHtml
		  , options;
		mc.setAttribute('id', 'menu-'+ contentId);
		options = { isRoot: true };
		mc.innerHTML = item(RootElement, options);
		$(mc).data('contentOptions', options);
		return mc;
	}

	var AC = loadContent;

	var studyModules = !element || element == head
		? _Xml.child(head, 'study-modules')
		: _Xml.parentOrSelf(element, 'study-modules');

	var appendixItem
	element && (appendixItem = _Xml.parentOrSelf(element, 'appendixes'));

	if (element && !studyModules && !appendixItem) {
		studyModules = _Xml.child(
			_Xml.parentOrSelf(element, 'organization'),
			'study-modules'
		);
	}

	if (!studyModules && !appendixItem) {
		return;
	}
	// Загружено основное содержимое
	if (!appendixItem) {
		appendixItem = _Xml.select(head, '//appendixes')[0];
	}

	var RootElement = studyModules ? studyModules : appendixItem;
	var contentId = (studyModules ? _Xml.id(head) : _Xml.id(appendixItem)) + '-content';
	var isSM = _Xml.name(RootElement) === 'study-modules';
	
	studyModules && _.each(_Xml.collect(studyModules), function (n) {
		_Xml.attr(n, 'eAu-pinned', 'yes');
	});
	
	if (isSM && eAu.UOptions.globalContent !== true) {
		if (!$('#menu-select-root').length) {
			var selectRoot = _.compact( _.map(
				_Xml.select(studyModules, './/*[@eAu-pinned = "yes"]'),
				function (n) {
					if (_Xml.within(n, window.linkedNode)) {
						return '<option value="'+ _Xml.id(n) +'">'+ _Xml.title(n) +'</option>';
					} else {
						return null;
					}
				}
			) );
			if (selectRoot.length) {
				$('#menu-header')
					.after('<div id="menu-select-root" class="wrapper first-wrapper"></div>');
				$('#menu-select-root').html('<select><option value="'+ _Xml.id(window.linkedNode) +'">Все разделы</option>'+ selectRoot +'</select>');
				$('#menu-select-root').children('select')
					.css('width', 332)
					.change(function () {
						var pinned = _Xml.$(head, this.value)
						  , selected;
						if (DS.get('id') && (selected = _Xml.$(head, DS.get('id')))) {
							if (pinned == _Xml.parentOrSelf(selected, '*@eAu-pinned=yes')) {
								pinned = selected;
							}
						}
						loadContent(pinned, selected);
						$window.trigger('resize');
					});
			}
		}
		var pinnedRoot = _Xml.parentOrSelf(element, '*@eAu-pinned=yes');
		if (pinnedRoot) {
			if (!_Xml.within(pinnedRoot, window.linkedNode)) {
				pinnedRoot = window.linkedNode;
			}
			RootElement = pinnedRoot;
			contentId = _Xml.id(RootElement) + '-content';
			use_internal_navigation && $('#menu-select-root')
				.children('select')
				.val(_Xml.id(RootElement));
		} else {
			$('#menu-select-root')
				.children('select')
				.val(_Xml.id(window.linkedNode));
		}
		$('#menu-select-root').show();
	} else {
		$('#menu-select-root').hide();
		if (isSM && window.linkedNode != head) {
			RootElement = window.linkedNode;
			contentId = _Xml.id(RootElement) + '-content';
		}
	}

	if (_.isUndefined(AC[ contentId ])) {
		AC[ contentId ] = renderMenu(RootElement, contentId);
	}

	var menu = document.getElementById('menu-content');
	if (!menu.firstChild || menu.firstChild.id != 'menu-'+ contentId) {
		Sarissa.clearChildNodes(menu);
		menu.appendChild(AC[ contentId ]);
	}
	if (eAu.UOptions.unifiedContentMenu === true && isSM && appendixItem) {
		var contentIdAppendix = _Xml.id(appendixItem) + '-content';
		if (_.isUndefined(AC[ contentIdAppendix ])) {
			AC[ contentIdAppendix ] = renderMenu(appendixItem, contentIdAppendix);
		}
		if (!menu.childNodes.item(1) || menu.childNodes.item(1).id != 'menu-'+ contentIdAppendix) {
			menu.appendChild(AC[ contentIdAppendix ]);
		}
	}

	highlightCurrentNodePath(selected, document.getElementById('menu-'+ contentId));

	$('#switch-to-main')
			.parent()
			.find('> div')
			.removeClass('menu-content-tab-current');

	if (isSM) {
		$('#switch-to-main')
			.addClass('menu-content-tab-current');
	} else {
		$('#switch-to-appendixes')
			.addClass('menu-content-tab-current');
	}
}
/* END: Content */

/* BEGIN: обработка текста */
function transformToText(element, stylesheet, options) {
	options = _.extend({
		'parent-id': DS.get('parentId'),
		'appendix-id': DS.get('appendix-id'),
		'main-id': DS.get('id')
	}, options || {});
	var returnValue = transformToTextEx(element, stylesheet, options);
	if (_.isUndefined(returnValue)) {
		$window.trigger('eAu:xslt:transform-error', {element: element});
	}
	return (returnValue || '');
}
function text_transform(text, id, showEffect) {
	var element
	    , fxQueue;
	if (DS.has('search-highlight') && !/load-indicator/.test(text)) {
		var match = 0;
		var gg = new String(text);
		var ggg = text.toLowerCase();
		var pieces = [];
		var hl = Sarissa.escape(eAu.String.normalizeSpaces(DS.get('search-highlight').toLowerCase(), true)).split(' ');
		if (hl.length && (hl.length == 1 || DS.get('search-type') == 'any')) {
			for (var i = 0; i < hl.length; ++i) {
				gg = new String(text);
				ggg = text.toLowerCase();
				pieces = [];
				do {
					var index = ggg.indexOf(hl[i]);
					if (index == -1) { break; }
					var substr0 = gg.slice(index, index + hl[i].length);
					var leftchar = gg.charAt(index-1);
					var rightchar = gg.charAt(index + hl[i].length);
					if (
						((leftchar && leftchar.match(/[\s>:\[()\\\/#*$\-=+~`«‘’‚‛“”„#*]/)) || !leftchar)
						&& ((rightchar && rightchar.match(/[\s<%\]()\\\/.:?!*#$\-=+~`,»‘’‚‛“”„…]/)) || !rightchar)
					) {
						var replacement = '<span class="eAu-search-match">'+substr0+'</span>';
						gg = gg.replace(substr0, replacement);
						pieces.push(gg.slice(0, index + replacement.length))
						gg = gg.slice(index + replacement.length);
					} else {
						pieces.push(gg.slice(0, index + hl[0].length));
						gg = gg.slice(index + hl[0].length);
					}
					ggg = gg.toLowerCase();
				} while(true);
				pieces.push(gg)
				text = pieces.join('');
			}
		} else if (hl.length) {
			do {
				var index = ggg.indexOf(hl[0]);
				if (index == -1) { break; }
				var lastIndex = ggg.indexOf(hl[hl.length - 1], index+hl[0].length);
				if (lastIndex == -1) { break; }
				var substr0 = gg.slice(index, lastIndex + hl[hl.length - 1].length);
				var substr1 = eAu.String.normalizeSpaces(eAu.String.stripTags(substr0), 'trim spaces').toLowerCase();
				var leftchar = gg.charAt(index-1);
				var rightchar = gg.charAt(lastIndex + hl[hl.length - 1].length);
				var test = !!(
					((leftchar && leftchar.match(/[\s>:\[()\\\/#*$\-=+~`«‘’‚‛“”„#*]/)) || !leftchar)
					&& ((rightchar && rightchar.match(/[\s<%\]()\\\/.:?!*#$\-=+~`,»‘’‚‛“”„…]/)) || !rightchar)
				)
				if (substr1 == hl.join(' ') && test) {
					var replacement = '<span class="eAu-search-match eAu-search-match-'+match+'">'+substr0+'</span>';
					gg = gg.replace(substr0, replacement);
					pieces.push(gg.slice(0, index + replacement.length))
					gg = gg.slice(index + replacement.length);
					match++;
				} else {
					pieces.push(gg.slice(0, index + hl[0].length));
					gg = gg.slice(index + hl[0].length);
				}
				ggg = gg.toLowerCase();
			} while(true);
			pieces.push(gg)
			text = pieces.join('');
		}
		DS.unset('search-highlight');
	}

	if (!id) {
		return text;
	} else {
		if (_.isString(id)) {
			element = $('#' + id)
		} else {
			element = $(id);
		}
		if (element.attr('id') == 'cont') {
			fxQueue = $('#content').queue();
			$('#content').stop(true, true);
			if (fxQueue.length == 2) {
				fxQueue[1].call($('#content').get(0), function () {});
			}
			if (arguments.callee.delayedFunctionTimerId) {
				clearTimeout(arguments.callee.delayedFunctionTimerId);
				arguments.callee.delayedFunctionTimerId = false;
			}
		}
		if (element.attr('id') == 'cont' && showEffect !== false && (eAu.UOptions.fxDisable !== true)) {
			if (eAu.UOptions.animationType == 'fade') {
				$('#content')
					.fadeOut(300)
					.queue(function (next) {
						var data = {
							node: element,
							text: text
						};
						this.style.filter = '';
						this.style.opacity = '';
						$window.trigger('eAu:content:beforechange', data);
						$('#cont').html(data.text);
						$window.trigger('eAu:content:afterchange', data);

						next();
					})
					.fadeIn(300)
					.queue(function (next) {
						this.style.filter = '';
						this.style.opacity = '';
						$window.trigger('eAu:content:afterchange:animation-complete', { node: element });

						next();
					});
			} else {
				$('#content')
					.effect('slide', {
						mode: 'hide',
						direction: 'right',
						duration: 300
					})
					.queue(function (next) {
						var data = {
							node: element,
							text: text
						};
						$window.trigger('eAu:content:beforechange', data);
						$('#cont').html(data.text);
						$window.trigger('eAu:content:afterchange', data);

						next();
					})
					.effect('slide', { direction: 'right', duration: 300 })
					.queue(function (next) {
						$window.trigger('eAu:content:afterchange:animation-complete', { node: element });

						next();
					});
			}
		} else {
			var data = {
				node: element,
				text: text
			};
			$window.trigger('eAu:content:beforechange', data);
			element.html(data.text);
			$window.trigger('eAu:content:afterchange', data);
			arguments.callee.delayedFunctionTimerId = _.delay(function () {
				$window.trigger('eAu:content:afterchange:animation-complete', { node: element });
			}, 200);
		}
	}
}
/* END: обработка текста */

function checkRights(node) {
	if (sequencingMode != 'linear' || !use_internal_navigation || !node) {
		return true;
	}

	var c = getRightsForNode(node);

	if (!c) {
		return true;
	}

	for (var i = 0, length = c.previousTestIds.length; i < length; ++i) {
		if (!Test.passed(c.previousTestIds[i])) {
			return false;
			break;
		}
	}

	return true;
}
function getRightsForNode(node) {
	var AC = arguments.callee;
	var nodeId = _Xml.id(node);
	var alowedIfWithin = ['appendixes', 'itro'];
	if (!AC.rights) {
		AC.rights = {};
	}

	if (!_.isUndefined(AC.rights[nodeId])) {
		return AC.rights[nodeId];
	}

	// Проверяем: если нода находится внутри приложений
	// или во введении, то заносим в ХЭШ null
	// т.е. элемент всегда разрешён
	if (_Xml.parentOrSelf(node, alowedIfWithin) || !_Xml.isVisible(node, true)) {
		AC.rights[nodeId] = null;
		return AC.rights[nodeId];
	}

	// Порядок неизвестен!!!
	var previousTests = _Xml.select(node, "preceding::test");
	var previousTestIds = [];
	for (var i = 0, length = previousTests.length; i < length; ++i) {
		if (_Xml.isVisible(previousTests[i], true)) {
			previousTestIds.push(previousTests[i]);
		}
	}
	// Устанавливаем ссылку на предыдущий тест для текущего элемента
	AC.rights[nodeId] = {
		id: nodeId,
		previousTestIds: previousTestIds
	};

	return AC.rights[nodeId];
}

/* BEGIN: переход к очередному элементу */
function getTargetNode(nextNode) {
	if (!nextNode) {
		return null;
	}
	
	var originalNode = nextNode;

	var target = _Xml.name(nextNode);
	if (target == 'studiedproblems') {
		nextNode = _Xml.child(nextNode, 'studiedproblem');
		target = nextNode ? _Xml.name(nextNode) : '';
	}
	if (target == 'studiedproblem' && !_Xml.isGroupped(nextNode)) {
		nextNode = _Xml.child(nextNode, 'object text');
	} else if ((/^(comments|text|object|reference|answer|answers|modules|study-modules|intro)$/.test(target) || eAu.Test.isQuestionNode(target)) && !/^(studiedproblem)$/.test(_Xml.name(_Xml.parent(nextNode)))) {
		nextNode = _Xml.parent(nextNode, 'glossary references test appendix-item organization');
	}
	return nextNode;
}
function getCurrentNode() {
	var node = DS.has('parentId')
		? _Xml.$(head, DS.get('parentId'))
		: DS.has('id')
			? _Xml.$(head, DS.get('id'))
			: head;
	return node ? node : head;
}
// Если находимся на сервере и переходим на видимую ноду (в основном курсе)
// надо проверить, что-бы следующая нода была в пределах
// текущего рассматриваемого вопроса или теста
function canNavigateToNodeOnServer(nextNode) {
	if (!nextNode) {
		return false;
	}

	var hasHiddenParents = !!nextNode && !_Xml.isVisible(nextNode, true);
	var isAppendix = !!nextNode && !!_Xml.parentOrSelf(nextNode, 'appendixes');

	if (!use_internal_navigation && !hasHiddenParents && !isAppendix) {
		return _Xml.within(nextNode, window.linkedNode);
	}
	return true;
}
function getNextNode (id) {
	return getTargetNode(!id ? head : _Xml.$(head, id));
}
function link_info(id) {
	return !isEventCanceled($window.triggerHandlerCustom('eAu:goto:request', [id]));
}
$window.bind('eAu:goto:request', function (event, id) {
	_.defer(function () {
		if (!isEventCanceled(event)) {
			$window.trigger('eAu:goto', [id]);
		}
	});
});
$window.bind('eAu:goto:request', function (event, id) {
	var nextNode = getNextNode(id);
	if (isEventCanceled(event)) return;
	if (!nextNode || !checkRights(nextNode) || !canNavigateToNodeOnServer(nextNode)) {
		event.stopPropagation();
	}
});
$window.bind('eAu:goto:request', function (event, id) {
	var nextNode = getNextNode(id);
	if (isEventCanceled(event)) return;
	if (nextNode && _.indexOf(['organization', 'unit', 'lesson', 'glossary', 'studiedproblem', 'test', 'object', 'subject', 'references', 'text', 'appendix-item', 'appendixes'], _Xml.name(nextNode)) == -1) {
		alert('Невозможно произвести просмотр, выберите другой элемент');
		event.stopPropagation();
	}
});
$window.bind('eAu:goto', function (event, id) {
	var nextNode = getNextNode(id);
	var currentNode = getCurrentNode();
	var hasHiddenParents = !!nextNode && !_Xml.isVisible(nextNode, true);
	var isAppendix = !!nextNode && (!!_Xml.parentOrSelf(nextNode, 'appendix-item') || _Xml.name(nextNode) == 'appendixes');

	// В случае если DS.get('id') содержит скрытую ноду и parentId отсутсвтует, то правим DS -> ('id')
	// Это, наверное бывает если в index.htm, через GET, был передан id скрытой ноды. В таком случае
	// prentId не установлен
	if (!DS.has('parentId') && !_Xml.isVisible(currentNode, true)) {
		DS.set('id', _Xml.id(head));
	}

	var target = _Xml.name(nextNode);

	if (hasHiddenParents && !DS.has('parentId')) {
		DS.set('parentId', DS.get('id'));
	}
	if (!hasHiddenParents && !isAppendix && target != 'glossary') {
		DS.unset('parentId');
	}

	id = _Xml.id(nextNode);

	if (isAppendix) {
		DS.set('appendix-id', id);
	} else {
		DS.unset('appendix-id');
		currentNode = getTargetNode(_Xml.$(head, DS.get('id')));
		if (currentNode && _Xml.name(currentNode) == 'glossary') {
			id = _Xml.id(head);
		} else if (target == 'glossary') {
			id = _Xml.id(currentNode);
		}
		DS.set('id', id);
	}

	DS.unset('search');
	if (target == 'glossary') {
		DS.set('glossary', _Xml.id(nextNode));
	} else {
		DS.unset('glossary');
	}
	loadingProxy();
	return true;
});
/* END: переход к очередному элементу */

/* BEGIN: Переключение режимов */
function switchMode(mode) {
	var oldMode = window.currentMode;
	window.currentMode = mode;

	var classes2add = [];
	var classes2remove = [];

	var isAppendix = $(document.body).is('.appendix-mode');
	var isGlossary = $(document.body).is('.glossary-mode');
	var isSearch = $(document.body).is('.search-mode');

	if ( (mode == 'glossary' || mode == 'search' || mode == 'appendix')) {
		if (!isAppendix)
			classes2add.push('appendix-mode');
	} else {
		classes2remove.push('appendix-mode');
	}

	if (mode == 'glossary' && !isGlossary) {
		classes2add.push('glossary-mode');
		$('#print-button').hide();
	} else if (isGlossary) {
		classes2remove.push('glossary-mode');
	}

	if (mode == 'search' && !isSearch) {
		classes2add.push('search-mode');
		$('#print-button').hide();
	} else if (isSearch) {
		classes2remove.push('search-mode');
	}

	if (classes2remove.length) {
		$(document.body)
			.removeClass(classes2remove.join(' '));
	}
	if (classes2add.length) {
		$(document.body)
			.addClass(classes2add.join(' '));
	}

	if (oldMode != mode) {
		$(document).trigger('mode:changed');
	}
}
/* END: Переключение режимов */

/* BEGIN: Загрузка и управление переключалкой режимов */
function generateBookmarksList() {
	var ac = arguments.callee,
	    id = DS.has('appendix-id') ? DS.get('appendix-id') : DS.get('id');
	
	if (!Bookmarks.count()) {
		$('#bookmarks-list').html('');
		$(window).trigger('resize', [true]);
		return;
	}
	
	if (!ac.template) {
		ac.template = _.template([
			'<ul>',
			'<% _.each(bookmarks, function (bookmark) { %>',
			'<li db_id="<%= bookmark.id %>" id="EU-BOOKMARK-<%= bookmark.id %>" class="<% if (id == bookmark.id) { %>current<% } %>">',
			'<a href="index.htm" target="_top" data-eau-target="navigate" db-id="<%= bookmark.id %>"onclick="return false;"><span class="delete-bookmark" title="Удалить закладку"></span><%= bookmark.title %></a>',
			'</li>',
			'<% }); %>',
			'</ul>'
		].join(''));
	}
	
	var filteredBookmarks;
	filteredBookmarks = _.reduce(Bookmarks.list(), function (memo, bookmark) {
		var node = _Xml.$(head, bookmark.id);
		
		if (node &&  (_Xml.within(node, window.linkedNode) || _Xml.closest(node, 'appendixes') || !_Xml.isVisible(node, true))) {
			memo.push(bookmark);
		}
		return memo;
	}, []);
	
	$('#bookmarks-list').html(
		text_transform(ac.template({ bookmarks : filteredBookmarks, id: id }))
	);

	$(window).trigger('resize', [true]);
}
function loadBookmarks() {
	generateBookmarksList();

	$('#add-bookmark')
		.unbind('click')
		.click(function(event) {
			if (/^(glossary|search)$/.test(window.currentMode.toLowerCase())) {
				return;
			}

			var id = DS.has('appendix-id') ? DS.get('appendix-id') : DS.get('id');
			Bookmarks.add(id);
			generateBookmarksList();
		});

	arguments.callee.loaded = true;
}
function showBookmarks() {
	if (!loadBookmarks.loaded) {
		loadBookmarks();
		$window.bind('eAu:node:changed', function () {
			_.defer(function () {
				$('#bookmarks-list .current')
					.removeClass('current');
				if (/^(glossary|search)$/.test(window.currentMode.toLowerCase())) {
					$('#add-bookmark').css({visibility: 'hidden'});
					$window.trigger('resize');
					return;
				}
				var id = DS.has('appendix-id') ? DS.get('appendix-id') : DS.get('id');
				if (_Xml.$(head, id)) {
					$(document.getElementById('EU-BOOKMARK-' + id))
						.addClass('current');
					$('#add-bookmark').css({visibility: 'visible'});
					$window.trigger('resize');
				}
			});
		});
		
		$(document).bind('test:passed', function() {
			_.defer(generateBookmarksList);
		});

		$('#bookmarks-list')
			.click(function (event) {
				event.preventDefault();
				if (!event.target || !$(event.target).is('.delete-bookmark')) {
					return;
				}

				var id = $(event.target)
					.parents('li:first')
					.attr('db_id');

				Bookmarks.del(id);
				$('#add-bookmark').css({visibility: 'visible'});
				generateBookmarksList();
				return;
			});

		$window.trigger('eAu:node:changed');
	}
}
/* END: Загрузка и управление переключалкой режимов */
function scrollMenuIntoView(element) {
	if (!highlightCurrentNodePath.FirstHtmlElement) { return; }
	var FirstHtmlElement = $(highlightCurrentNodePath.FirstHtmlElement);

	var menuOverflow       = $('#menu-overflow');
	var menuContentWrapper = $('#menu-content').parent();
	if (!menuOverflow.length || !menuContentWrapper.length) { return; }

	arguments.callee.scrolled = true;

	var PrePadding = 14;
	var menuOverflowScrollTop = menuOverflow.scrollTop();
	var menuOverflowHeight = menuOverflow.height();
	var menuContentHeight = menuContentWrapper.height()
		+ parseInt(menuContentWrapper.css('padding-top'), 10)
		+ parseInt(menuContentWrapper.css('padding-bottom'), 10);
	
	var ScrollAmount = FirstHtmlElement.position().top - menuOverflow.position().top - PrePadding;

	if (ScrollAmount + menuOverflowScrollTop + menuOverflowHeight > menuContentHeight) {
		ScrollAmount = menuContentHeight - menuOverflowHeight - menuOverflowScrollTop + PrePadding;
	}

	if (ScrollAmount == 0) { return; }
	if (Math.abs(ScrollAmount) < 5) {
		menuOverflow.scrollTop(menuOverflowScrollTop + ScrollAmount);
	} else {
		menuOverflow.animate({
			scrollTop: '+=' + ScrollAmount + 'px'
		}, 1000, ScrollAmount > 0 ? 'easeOutBounce' : 'easeInOutBounce');
	}
}
function highlightCurrentNodePath(element, root) {
	if (DS.has('parentId')) {
		element = _Xml.$(head, DS.get('parentId'));
	}
	if (!element) { return; }
	var id,
	    cur = element,
	    CurHtmlElement;

	var currentHighlightedElements = $(root)
		.find('.current')
		.removeClass('current');

	do {
		id = _Xml.id(cur);
		var old = cur;
		cur = _Xml.parent(cur);

		if (!checkRights(old) || !canNavigateToNodeOnServer(old)) { continue; }

		CurHtmlElement = document.getElementById('EU-' + id);
		if (!CurHtmlElement) { continue; }
		if (!arguments.callee.FirstHtmlElement) {
			arguments.callee.FirstHtmlElement = CurHtmlElement;
			$(CurHtmlElement).addClass('current-first');
		}
		$(CurHtmlElement).addClass('current');
		expandMenuItem($(CurHtmlElement).parents('.tree-folder:first'));
	} while (cur);
}
function toggleMenuItem($node) {
	if (!$node.length) {
		return false;
	}

	var node = $node.get(0);
	
	var dbId = _Xml.attr(node, 'ref_db_id');
	if (/collapsed/.test(node.className || '')) {
		$node
			.removeClass('tree-folder-collapsed')
			.addClass('tree-folder-expanded');
		DS.set('|menu_state_' + dbId, 'expanded');
	} else {
		$node
			.removeClass('tree-folder-expanded')
			.addClass('tree-folder-collapsed');
		DS.unset('|menu_state_' + dbId);
	}
	$(window).trigger('resize', [true]);
}
function expandMenuItem($node) {
	if (!$node.length) {
		return false;
	}

	if (/collapsed/.test($node.get(0).className)) {
		$node
			.removeClass('tree-folder-collapsed')
			.addClass('tree-folder-expanded');
	}
}

function loadBreadedCrumbs(node) {
	if (!node) {
		//$('#breaded-crumbs').hide();
		return;
	}

	var isNonInteractiveNode = eAu.Test.isQuestionNode(node);
	var nodesPath = [];
	var Parent = node;
	var hasInvisibleNode = false;
	if (isNonInteractiveNode) {
		Parent = _Xml.parent(Parent);
	}
	while (Parent && _Xml.name(Parent) != 'study-modules' && _Xml.name(Parent) != 'appendixes') {
		if (/^(modules|question-group|intro|subject|references)$/.test(_Xml.name(Parent)) ) {
			Parent = _Xml.parent(Parent);
			continue;
		}
		nodesPath.push(Parent);
		if (!_Xml.isVisible(Parent)) {
			hasInvisibleNode = true;
			break;
		}
		Parent = _Xml.parent(Parent);
	}
	var nodesPathStr = [];
	nodesPath = nodesPath.reverse();
	var li;
	for (var i = 0; i < nodesPath.length; ++i) {
		function gTitle(node) {
			if (/^(studiedproblem)$/.test(_Xml.name(node)) || eAu.Test.isQuestionNode(node)) {
				return eAu.String.trim(_Xml.attr(node, 'title') || '');
			} else {
				return _Xml.title(node, true);
			}
		}
		if (_Xml.name(nodesPath[i]) == 'studiedproblem' && _Xml.attr(nodesPath[i], 'group-text-nodes') == 'false') {
			continue;
		}
		if (nodesPath.length == i+1 && !/^(glossary|search)$/.test(getMode())) {
			nodesPathStr.push('<span class="eAu-breadedcrumbs-item">' + gTitle(nodesPath[i]) + '</span>');
		} else {
			if (_Xml.name(nodesPath[i]) == 'studiedproblems' || isNonInteractiveNode || !canNavigateToNodeOnServer(nodesPath[i]) || !checkRights(nodesPath[i])) {
				nodesPathStr.push('<span class="eAu-breadedcrumbs-item">' + gTitle(nodesPath[i]) + '</span>');
			} else {
				nodesPathStr.push('<a href="index.htm" db-id="'+ _Xml.id(nodesPath[i]) +'" onclick="return false;" target="_top" class="eAu-breadedcrumbs-item">' + gTitle(nodesPath[i]) + '</a>');
			}
		}
	}
	if (DS.has('appendix-id')) {
		var appendixes = _Xml.parentOrSelf(node, 'appendixes')
		nodesPathStr.unshift('<span class="eAu-breadedcrumbs-item" id="eAu-material-type">Дополнительный материал</span>');
	} else {
		nodesPathStr.unshift('<span class="eAu-breadedcrumbs-item" id="eAu-material-type">Основной материал</span>');
	}
	if (nodesPathStr.length) {
		nodesPathStr[0] = nodesPathStr[0].replace(/ class="eAu/, ' class="eAu-breadedcrumbs-first-item eAu');
		$('#breaded-crumbs-content').html(nodesPathStr.join('<span class="eAu-breadedcrumbs-separator"> → </span>'));
	}
}

function clearSlider () {
	$('#slider').addClass('ui-helper-hidden')
		.children().remove();

	$('#slider-td').css('width', '50%');
}

function getSliderItemNode (node, current) {
	var cssClasses = ['slider-item']
	  , nodeName = 'a';
	if (node) {
		!checkRights(node) && (nodeName = 'span');
		cssClasses.push('slider-item-' + _Xml.name(node));
		current && cssClasses.push('current');
		isItemViewed(node) && cssClasses.push('passed') && cssClasses.push('viewed');
		if (_Xml.is(node, 'test')) {
			Test.attempted(node) && Test.passed(node) && cssClasses.push('test-passed');
			Test.attempted(node) && !Test.passed(node) && cssClasses.push('test-failed');
			!Test.attempted(node) && cssClasses.push('test-not-attempted');
			current && cssClasses.push('test-current');
		}
	}
	return $('<'+ nodeName +'/>')
		.append('<span/>')
		.addClass(cssClasses.join(' '))
		.attr('data-eau-target', 'navigate')
		.attr('href', 'index.htm')
		.attr('target', '_top')
		.attr('title',     (node ? _Xml.title(node, true) : '') || '')
		.attr('node-name', (node ? _Xml.name(node)        : '') || '')
		.attr('db-id',     (node ? _Xml.id(node)          : '') || '');
}

/*function getSliderItemDimensions () {
	var $sliderClone;
	if (!getSliderItemDimensions.sliderItem) {
		$sliderClone = $( $('#slider').get(0).cloneNode(false) )
			.removeClass('ui-helper-hidden')
			.css({
				position: 'absolute',
				top: -32000,
				left: -32000,
				display: 'block'
			})
			.append(getSliderItemNode());

		$('#slider').after($sliderClone);

		getSliderItemDimensions.sliderItem = {
			width: $sliderClone.children('.slider-item').outerWidth(true),
			spacing: 0
		}
		$sliderClone.remove();
	}
	return getSliderItemDimensions.sliderItem;
}*/

function loadSlider (node) {
	"use strict";
	var sliderList
	  , slider
	  , $slider;
	
	clearSlider();
	
	slider = eAu.navigation.slider(node, eAu.navigation.SLIDER_DEPTH_SP);
	sliderList = _.reduce(slider.list, function (memo, item, index) {
		if (canNavigateToNodeOnServer(item)) {
			memo.push({
				item: item,
				isCurrent: slider.current == index
			});
		}
		return memo;
	}, []);
	
	if (sliderList.length <= 1) { return; }
	
	$slider = _.reduce(sliderList, function (memo, item) {
		memo.append( getSliderItemNode(item.item, item.isCurrent) );
		return memo;
	}, $('#slider').clone(true, true).children().remove().end());
	
	$('#slider').replaceWith(
		$slider.removeClass('ui-helper-hidden')
			.css('width', '100%')
	);
}
function toggleDescription(what, context) {
	var node = document.getElementById(what);
	if (!node) { return; }
	var isVisible = $(node)
		.toggle()
		.is(':visible');

	if (isVisible) {
		$(context)
			.removeClass('button-expand')
			.addClass('button-collapse')
			.attr('title', 'Свернуть описание');
	} else {
		$(context)
			.removeClass('button-collapse')
			.addClass('button-expand')
			.attr('title', 'Развернуть описание');
	}
}
function setButton(buttonType, handler, title, options) {
	var supported = ['next', 'previous', 'next-question', 'previous-question', 'answer-question'];
	options || (options = {});

	if (!buttonType) {
		for (var i = 0, length = supported.length; i < length; ++i) {
			setButton(supported[i]);
		}
	}

	if (_.indexOf(supported, buttonType) == -1) { return; }

	// Хак :)
	var newButtonType = buttonType.replace('-question', '');
	var button = $('#button-'+ newButtonType);

	if (!handler) {
		button
			.unbind('click')
			.attr('title', '')
			.removeClass('jump')
			.addClass('button-hidden');
		return;
	}

	var prefix = buttonType == 'next' ? 'Далее: ' : buttonType == 'previous' ? 'Назад: ' : '';

	button
		.unbind('click')
		.click(function(event){
			handler(event);
			return false;
		})
		.attr('title', prefix + title)
		.addClass(options.jump ? 'jump' : '')
		.removeClass('button-hidden');
}

$window.one('course:ready', function(event) {
	var headerIsVisible = !((window.isELS || !use_internal_navigation) && head && _Xml.attr(head, 'els-hide-header') == 'true');
	if (!headerIsVisible) {
		return;
	}
	if (DS.get('|header_state') == 'collapsed') {
		$('#layout-header')
			.hide();
		$('#header-collapser')
			.attr('title', 'Показать заголовок')
			.addClass('collapsed');

		$window.trigger('resize');
	} else {
		$('#header-collapser')
			.attr('title', 'Скрыть заголовок');
	}

	$('#header-collapser')
		.click(function() {
			var shouldIResizeImmediatly = !$('#layout-header').is(':visible');

			$('#layout-header')
				.toggle('drop', {
					direction: 'right',
					distance: $window.width() * 4.0 / 5.0
				}, 400, function() {
					this.style.opacity = '';
					this.style.filter = '';

					if (!$(this).is(':visible')) {
						DS.set('|header_state', 'collapsed');
						$window.trigger('resize', [true]);

						$('#header-collapser')
							.addClass('collapsed')
							.attr('title', 'Показать заголовок');
					} else {
						DS.unset('|header_state');

						$('#header-collapser')
							.removeClass('collapsed')
							.attr('title', 'Скрыть заголовок');
					}
				});

				shouldIResizeImmediatly && $window.trigger('resize', [true]);
		})
		.show();
});

function showAnimatedMenu (id, duration_user, origin) {
	var element = $('#' + id),
	    elementButton = $('#button-' + id),
	    duration = _.isUndefined(duration_user) ? 300 : duration_user;
	if (element.is(':animated')) {
		element.stop(true);
	}
	if (element.attr('eAu-toggle-state') === '1') {
		element.attr('eAu-toggle-state', '0');
		element.attr('eAu-animation-direction', 'hide');
		elementButton.removeClass('cute-popup-is-showing');
		element.animate({ bottom: $('#layout-bottom').height() - element.outerHeight() }, {
			duration: duration,
			step: function () {
				elementButton.css('top', $('#layout-bottom').height() - element.outerHeight() - arguments[0] - 1);
			},
			complete: function () {
				elementButton.css('top', -1);
				hideElement(element);
			}
		});
	} else {
		element.attr('eAu-toggle-state', '1');
		element.attr('eAu-animation-direction', 'show');
		elementButton.addClass('cute-popup-is-showing');
		if (!isElementVisible(element)) {
			showElement(element);
			$window.trigger('resize');
			element.css('bottom', $('#layout-bottom').height() - element.outerHeight());
		}
		element.animate({ bottom: $('#layout-bottom').height() }, {
			duration: duration,
			step: function () {
				elementButton.css('top', $('#layout-bottom').height() - element.outerHeight() - arguments[0] - 1);
			},
			complete: function () {
				elementButton.css('top', -element.outerHeight() - 1);
			}
		});
	}
}

function loadNavInfo(node) {
	var currentMode = window.currentMode;
	var isServerMode = !use_internal_navigation;

	// Если курс запущен в eLS, то предупредим его
	if (isServerMode && !$(document.body).is('.server-mode')) {
		$(document.body).addClass('server-mode');
	}
	if (document.location.protocol.indexOf('http') == 0 && !$(document.body).is('.http-mode')) {
		$(document.body).addClass('http-mode');
	}

	var isMenuLayoutStatic = false;

	$window.trigger('eAu:node:changed');

	var headerIsVisible = !((window.isELS || !use_internal_navigation) && _Xml.attr(head, 'els-hide-header') == 'true');
	if (!headerIsVisible) {
		$('#layout-header').hide();
	}

	var isNonInteractiveNode = !!node && eAu.Test.isQuestionNode(node);

	// Установка заголовка курса
	if (headerIsVisible) {
		$('#course-title')
			.html('<span>'+_Xml.title(head)+'</span>');
		$('#course-logo, #course-title')
				.unbind('click')
				.attr('title', '')
				.css('cursor', 'default');

		var LogoUrl = _Xml.attr(head, 'src');
		if (LogoUrl && !arguments.callee.logoLoaded && $('#course-logo').length == 1) {
			var image = new Image();
			image.onload = function() {
				if (image.height > 128) {
					var d = image.height / 128;
					$('#course-logo').attr('height', 128);
					$('#course-logo').attr('width', parseInt(image.width / d));
				}
				$(window).trigger('resize');
				image.onload = null;
				image = null;
				$('#course-logo').attr('src', LogoUrl);
				loadNavInfo.logoLoaded = true;
			}
			image.src = LogoUrl;
		}

		if (use_internal_navigation && !isNonInteractiveNode) {
			$('#course-title, #course-logo')
				.click(function() {
					link_info(_Xml.id(head));
					return false;
				})
				.attr('title', 'К описанию курса')
				.css('cursor', 'pointer');
		}
	}

	// Управление кнопками
	if (!arguments.callee.Handlers || _.isEmpty(arguments.callee.Handlers)) {
		arguments.callee.Handlers = {
			menu: function(event, origin) {
				showAnimatedMenu('menu', window.undefined, origin);
			},
			print: function(event) {
				//PrintNode(node);
			},
			bookmarks: function(event) {
				showBookmarks();
			},
			help: function(event) {
				$.fn.colorbox({
					inline: true,
					href: '#hilfe-content',
					scrolling: true,
					maxWidth: $window.width() - 200,
					maxHeight: $window.height() - 100,
					title: 'Помощь',
					onOpen: function () {
						$('body').addClass('colorbox-visible');
					},
					onClosed: function () {
						$('body').removeClass('colorbox-visible');
					}
				});
			},
			search: function (event, origin) {
				if (DS.has('search-string')) {
					$('#search .form input').val(DS.get('search-string'));
				}
				showBookmarks();
				showAnimatedMenu('search', window.undefined, origin);
			},
			map: function (event, origin) {
				showAnimatedMenu('map', window.undefined, origin);
			},
			refresh: function (event) {
				delete playBgSound.url;
				if (DS.has('appendix-id')) {
					link_info(DS.get('appendix-id'));
				} else if (DS.has('id')) {
					link_info(DS.get('id'));
				}
			},
			sound: function (event) {}
		};
		// TODO: pass context
		arguments.callee.Handlers = _.reduce(eAu.UOptions.ButtonHandlers || {}, function (memo, value, index) {
			memo[index] = value && _.isFunction(value) ? value : memo[index];
			return memo;
		}, arguments.callee.Handlers, {
			isNonInteractiveNode: isNonInteractiveNode
		});
	}
	var Handlers = arguments.callee.Handlers;
	var Button = {
		menu: {
			isVisible: !isNonInteractiveNode,
			event: 'mouseup'
		},
		print: {
			isVisible: false,
			event: 'click'
		},
		bookmarks: {
			isVisible: false,
			event: 'click'
		},
		mode: {
			isVisible: false,
			event: 'click'
		},
		help: {
			isVisible: false,
			event: 'click'
		},
		search: {
			isVisible: !isNonInteractiveNode,
			event: 'mouseup'
		},
		map: {
			isVisible: !isNonInteractiveNode && drawMap.hasTests,
			event: 'mouseup'
		},
		refresh: {
			isVisible: !isNonInteractiveNode && currentMode != 'search' && currentMode != 'glossary',
			event: 'mouseup'
		},
		sound: {
			isVisible: !isNonInteractiveNode,
			event: 'mouseup'
		}
	};

	// TODO: pass context
	Button = _.reduce(eAu.UOptions.Button || {}, function (memo, value, index) {
		memo[index] = _.extend(memo[index] || {}, value || {});
		return memo;
	}, Button, {
		isNonInteractiveNode: isNonInteractiveNode
	});

	for (var item in Button) {
		var it = Button[item];
		it.event = it.event || 'click';
		var handler = _.isFunction(Handlers[item])
			? Handlers[item]
			: null;
		if (handler) {
			$('#button-'+ item).unbind(it.event, it.handler);
		}

		var isVis = _.isFunction(it.isVisible) ? it.isVisible.call({
			isNonInteractiveNode: isNonInteractiveNode
		}) : it.isVisible;

		if (isVis) {
			$('#button-'+ item).removeClass('button-hidden');
			$('#button-'+ item + '-backup').removeClass('button-hidden');
			if (handler) {
				$('#button-'+ item).bind(it.event, handler)
			}
		} else {
			$('#button-'+ item).addClass('button-hidden');
			$('#button-'+ item + '-backup').addClass('button-hidden');
		}
	}

	if ((currentMode == 'main' || currentMode == 'appendix') && isMenuLayoutStatic) {
		showElement($('#menu'));
	}

	// Загружаем строку быстрой навигации
	if (isNonInteractiveNode) {
		clearSlider();
	} else if (node && (currentMode == 'main' || currentMode == 'appendix')) {
		loadSlider(node);
	} else {
		clearSlider();
	}

	// Загружаем хлебные крошки
	var introo
	node && (introo = _Xml.parentOrSelf(node, 'intro'));
	if (node && node !== head && !_Xml.is(node, 'intro') && !introo) {
		loadBreadedCrumbs(node);
	} else {
		$('#breaded-crumbs-content').html('');
	}

	// Если в курсе нет ни одного приложения, то заменим
	// кнопку переключения режимов на кнопку поиска
	var hasModes = true;
	var appendixes = head.selectNodes("//appendix-item");
	var glossary = head.selectNodes("//glossary");
	if (appendixes.length + glossary.length != 0) {
		$('#button-mode')
			.attr('title', 'Режимы');
	} else {
		$(document.body)
			.addClass('search-only');
		if (currentMode == 'main') {
			$('#button-mode').attr('title', 'Поиск');
		} else {
			$('#button-mode').attr('title', 'Вернуться к режиму обучения');
		}
	}

	if (isNonInteractiveNode) {
		$(document).trigger('mousedown');
	}

	// Кнопки навигации по курсу
	var siblings = eAu.nav_temp.neighbours(node);

	setButton();

	if (currentMode == 'main' || currentMode == 'appendix') {
		if (siblings.next && checkRights(siblings.next) && canNavigateToNodeOnServer(siblings.next)) {
			setButton('next', function() { link_info(_Xml.id(siblings.next)); }, _Xml.title(siblings.next), {
				jump: _Xml.closest(siblings.next, 'lesson unit') != _Xml.closest(node, 'lesson unit')
			});
		}
		if (siblings.prev && checkRights(siblings.prev) && canNavigateToNodeOnServer(siblings.prev)) {
			setButton('previous', function() { link_info(_Xml.id(siblings.prev)); }, _Xml.title(siblings.prev), {
				jump: _Xml.closest(siblings.prev, 'lesson unit') != _Xml.closest(node, 'lesson unit')
			});
		}
	}
	if (node == head) {
		var studyModules = _Xml.child(head, 'study-modules'),
		    childUnits,
		    child;
		if (studyModules) {
			childUnits = eAu.nav_temp.neighbours(studyModules);
			if (childUnits.next && checkRights(childUnits.next) && canNavigateToNodeOnServer(childUnits.next)) {
				setButton('next', function() { link_info(_Xml.id(childUnits.next)); }, _Xml.title(childUnits.next), { jump: true });
			}
		}
	}

	window.currentNode = node; // для печати

	if (/main|appendix/.test(currentMode) && isMenuLayoutStatic) {
		if (!$(document.body).is('.menu-layout-static')) {
			$(document.body)
				.addClass('menu-layout-static');
		}
		$('#menu')
			.attr('autohide', 'no');
	} else {
		if ($(document.body).is('.menu-layout-static')) {
			$(document.body)
				.removeClass('menu-layout-static');
		}
		$('#menu')
			.attr('autohide', 'yes');
	}
	if (node && /^(object|text|question|test|studiedproblem|intro)$/.test(_Xml.name(node)) || currentMode == 'search') {
		$(document).trigger('mousedown');
	}

	// Если мы находимся в основном режиме обучения
	// разрешена внутренняя навигация курса, то
	// загружаем оглавление
	if (currentMode == 'main') {
		loadContent(node);
	}
	// Если-же режим обучения - приложения, то загружаем оглавление приложения
	if (currentMode == 'appendix') {
		loadContent(node);
	} else if (currentMode == 'glossary') {
		var glossaryId = DS.get('glossary');
		if (glossaryId) {
			node = _Xml.$(head, glossaryId);
			if (node && _Xml.name(node) != 'glossary') {
				node = null;
			}
		}
		if (!node) {
			node = _Xml.select(head, { single: true, selector: '//glossary[1]' });
		}
		loadContent(node);
	}
	if (currentMode == 'search' && !loadContent.atLeastOneLoaded) {
		loadContent(node);
	}
	if (currentMode == 'appendix' || currentMode == 'glossary') {
		$('#switch-to-appendixes')
			.unbind()
			.mouseup(function (event) {
				event.stopImmediatePropagation();
				if ($('#menu-overflow').is(':animated')) {
					return;
				}
				$(this)
					.parent()
					.find('> div')
					.removeClass('menu-content-tab-current');
				$(this)
					.addClass('menu-content-tab-current');
				loadContent(node);
				$window.trigger('resize');
			});
	}
	if (currentMode == 'main') {
		$('#switch-to-main')
			.unbind()
			.mouseup(function (event) {
				event.stopImmediatePropagation();
				if ($('#menu-overflow').is(':animated')) {
					return;
				}
				$(this)
					.parent()
					.find('> div')
					.removeClass('menu-content-tab-current');
				$(this)
					.addClass('menu-content-tab-current');
				loadContent(node);
				$window.trigger('resize');
			});
	}
	var hasAppendixes = !!_Xml.select(head, '//appendix-item | //glossary').length
	if (hasAppendixes) {
		$('#switch-to-appendixes')
			.mouseup(function (event) {
				event.stopImmediatePropagation();
				if ($('#menu-overflow').is(':animated')) {
					return;
				}
				$(this)
					.parent()
					.find('> div')
					.removeClass('menu-content-tab-current');
				$(this)
					.addClass('menu-content-tab-current');
				loadContent(
					_Xml.select(head, { single: true, selector: "//appendixes[1]" })
				);
				$window.trigger('resize');
			});
		$('#switch-to-main')
			.mouseup(function (event) {	
				event.stopImmediatePropagation();
				if ($('#menu-overflow').is(':animated')) {
					return;
				}
				$(this)
					.parent()
					.find('> div')
					.removeClass('menu-content-tab-current');
				$(this)
					.addClass('menu-content-tab-current');
				loadContent(head);
				$window.trigger('resize');
			});
		$('#menu').addClass('cute-popup-w-tabs');
	} else {
		$('#switch-to-main, #switch-to-appendixes')
			.hide();
	}

	$(window).trigger('resize', [true]);
	return siblings;
}

function PrintNode(node) {
	throw new Error('not implemented');
}

if (block_content_copy) {
	eAu.contentBlocker.enable();
}

function getMode() {
	if (!DS) { return; }
	return DS.has('search')
		? 'search'
		: DS.has('glossary')
			? 'glossary'
			: DS.has('appendix-id')
				? 'appendix'
				: 'main';
}

function showError(title, text, level) {
	$('#cont')
		.html('<div class="eU-error"><h2 class="eU-error-title">'+ title +'</h2><div class="eU-error-text">'+ text +'</div></div>');
	if (level == 'crytical') {
		$('#course-title')
			.html('<span><i>Ошибка при загрузке курса</i></span>')
		$('#layout-bottom')
			.hide();
		$(window).trigger('resize');
	}
}

function getViewedItems () {
	var tmp = eAu.String.trim(DS.get('|nodesViewed') || '');
	return tmp ? tmp.split(' ') : [];
}

function markItemAsViewed(node) {
	if (_.isString(node))
		node = _Xml.$(head, node);
	if (!node)
		return;

	var viewedItems;
	if (!isItemViewed(node)) {
		viewedItems = getViewedItems();
		viewedItems.push(_Xml.id(node));
		DS.set('|nodesViewed', viewedItems.join(' '));
	}
	$window.trigger('eAu:item:viewed', [_Xml.id(node)]);
}
function isItemViewed(node) {
	if (_.isString(node))
		node = _Xml.$(head, node);
	if (!node)
		return false;
	return _.indexOf(getViewedItems(), _Xml.id(node)) != -1;
}

function playBgSound (url) {
	url = url || '';

	var ac = arguments.callee;
	var flashvars = {
		fv_filename: encodeURIComponent(getLocalFilename(url)),
		fv_color: (window.fv_color ? window.fv_color : '#ff00ff')
	};
	var params = {
		menu: "false",
		quality: "medium",
		scale: "noscale",
		wmode: "transparent",
		allowfullscreen: "false",
		allowscriptaccess: "always",
		allownetworking: "all"
	};
	var attributes = {
		id: "buttonSoundSwf",
		onfocus: "this.blur(); return false;"
	};

	var isTheSameURL = !!url && ac.url == getLocalFilename(url);
	if (!isTheSameURL) {
		swfobject.removeSWF('buttonSoundSwf');
		$('#button-sound').html('<div id="button-sound-container" style="width: 100%; height: 100%;"></div>');
	}
	
	if (!isTheSameURL) {
		ac.url = getLocalFilename(url);
		try {
			swfobject.embedSWF(
				"images/swf/button-volume.swf"
				, "button-sound-container"
				, "100%"
				, "100%"
				, "9.0.115"
				, "scripts/lib/external/expressInstall.swf"
				, flashvars, params, attributes
			);
		} catch (error) {
			try { console.log(error); } catch (e) {}
		}
	}
}
function _preprocessExercises (node) {
	var exercises = _Xml.select(node, "descendant-or-self::object[@type = 'exercise']");
	_.each(exercises, function (item) {
		// TODO: var question = _Xml.parentOrSelf(item, 'question-exercise');
		var filename = _Xml.attr(item, 'filename');
		var SplittedSrc = _Xml.attr(item, 'base-src').split('/');
		//TODO: if (_Xml.attr(item, 'temp-preprocessed') || question) {
		if (_Xml.attr(item, 'temp-preprocessed')) {
			return;
		}
		_Xml.attr(item, 'temp-preprocessed', 'temp-preprocessed');
		//var exerciseFileName = /^index.htm$/.test(filename) ? "FlashExercise.swf" : (/\.htm(l)?$/i.test(filename) ? filename.split('.')[0] + ".swf" : filename);
		var exerciseFileName = /^index.htm$/.test(filename) ? "FlashExercise.swf" : filename;
		if (/\.swf/i.test(exerciseFileName)) {
			_Xml.attr(item, 'type', 'flash');
		}
		_Xml.attr(item, 'src', SplittedSrc.join('/') + '/' + exerciseFileName);
	});
}
// Загрузка элементов
function loadingProxy() {
	if (arguments.callee.t) {
		clearTimeout(arguments.callee.t);
	}

	$('#content')
		.scrollTop(0)
		.find('#cont');

	if (eAu.UOptions.showContentLoaderWhileFetching !== false) {
		showContentLoader();
	}

	if (!DS) {
		showError('Этот файл не может быть открыт напрямую', 'Что-бы приступить к изучению курса вы должны открыть файл «<code>index.htm</code>» в любом, установленном у вас браузере.', 'crytical');
		return;
	} else if (!head) {
		showError('Содержимое курса не найдено или повреждено', [
			'Неудалось загрузить файл курса.',
			'Возможная причина &ndash; файл отсутсвует или повреждён.',
			'Обратитесь в службу тех. поддержки для устранения неисправностей.'
		].join(' '), 'crytical');
		return;
	} else if (!window.commonXSL || !window.printXsl) {
		showError('Файлы курса не найдены или повреждены', 'Неудалось загрузить один или несколько файлов курса, необходимых для его корректной работы. Обратитесь в службу тех. поддержки для устранения неисправностей.', 'crytical');
		return;
	}

	var mode = getMode();

	var id = DS.has('appendix-id')
		? DS.get('appendix-id')
		: DS.get('id');

	var node = id ? _Xml.$(head, id) : null;

	switchMode(mode);

	var neighbours = loadNavInfo(node ? getTargetNode(node) : head);

	if (!node && id) {
		// TODO: проверить строки
		showError('Невозможно найти элемент', 'Неудалось найти указанный элемент в структуре курса «<code>'+ id +'</code>». Обратитесь к разработчикам курса или в службу тех. поддержки для устранения неисправностей.');
		return;
	}
	
	if (!getTargetNode(node)) {
		showError('Невозможно найти элемент для отображения', 'Неудалось найти указанный элемент в структуре курса «<code>'+ id +'</code>». Обратитесь к разработчикам курса или в службу тех. поддержки для устранения неисправностей.');
		return;
	}

	if (!checkRights(node)) {
		showError('Вы ещё не прошли все необходимые тесты', 'Вам необходимо пройти все тесты, предшествующие данному разделу.');
		return;
	} else if (!canNavigateToNodeOnServer(node)) {
		showError('Вы не можете просмотреть раздел', 'Воспользуйтесь оглавлением серевера.');
		return;
	}

	var functionName = 'load' + (mode == 'main' ? '' : '_' + mode) + (/^(appendix|main)$/.test(mode) ? '_' + _Xml.name(node) : '');
	var nodenode = node == head ? _Xml.child(node, 'intro') : node;
	var bgsoundNode;
	var oldBgSoundNode;

	if (window[functionName]) {
		if (!nodenode) {
			playBgSound('');
		} else {
			bgsoundNode = nodenode;
			while (bgsoundNode && ( _.indexOf(['intro', 'studiedproblem', 'studiedproblems', 'lesson', 'unit'], _Xml.name(bgsoundNode)) == -1 || !eAu.String.trim(_Xml.attr(bgsoundNode, 'src')) )) {
				oldBgSoundNode = bgsoundNode;
				bgsoundNode = _Xml.parentOrSelf(bgsoundNode, 'intro@type=bgsound studiedproblem@type=bgsound studiedproblems@type=bgsound lesson@type=bgsound unit@type=bgsound');
				if (bgsoundNode && bgsoundNode == oldBgSoundNode) {
					bgsoundNode = _Xml.parent(bgsoundNode);
				}
			}
			playBgSound(bgsoundNode ? _Xml.attr(bgsoundNode, 'src') : '')
		}
		if (/^(object|text)$/i.test(_Xml.name(node)) && _Xml.parent(node) && _Xml.name(_Xml.parent(node)) == 'studiedproblem') {
			markItemAsViewed(_Xml.parent(node));
		}
		markItemAsViewed(node);
		_preprocessExercises(node);
		arguments.callee.t = setTimeout(function() {
			window[functionName](node, neighbours);
		}, 0);
	} else {
		// TODO: проверить строки
		showError('Невозможно отобразить содержимое раздела', 'Неудалось загрузить содержимое раздела. Отсутствует функция обработки данного типа модулей «<code>'+ _Xml.name(node) +'</code>». Обратитесь к разработчикам курса или в службу тех. поддержки для устранения неисправностей.');
	}

	return;
}
// Загрузка главной страницы
function load(node) {
	var intro = _Xml.child(node, 'intro');

	_Xml.when(_Xml.fetch(intro, true)).always(function () {
		text_transform(transformToText(node, commonXSL), 'cont');
	});
}
function load_organization(node) {
	load(node);
}

function load_appendix_appendixes (node) {
	text_transform(transformToText(node, commonXSL), 'cont');
}

/* BEGIN: Загрузка приложений */
function load_appendix_intro(node) {
	window['load_appendix_appendix-item'](_Xml.parent(node));
}
window['load_appendix_appendix-item'] = function(node) {
	var intro = _Xml.child(node, 'intro');

	_Xml.when(_Xml.fetch(intro, true)).always(function () {
		text_transform(transformToText(node, commonXSL), 'cont');
	});
}
function load_appendix_studiedproblem(node) {
	_Xml.when(_Xml.fetch(node, true)).always(function () {
		text_transform(transformToText(node, commonXSL), 'cont');
	});
}
function load_appendix_lesson(node) {
	_Xml.when(_Xml.fetch(node, true)).always(function () {
		text_transform(transformToText(node, commonXSL), 'cont');
	});
}
/* END: Загрузка приложений */

/* BEGIN: загрузка страниц поиска и глоссария */
function load_search() {
	getSearchResults();
}
function load_glossary() {
	var node;
	if (DS.get('glossary')) {
		node = _Xml.$(head, DS.get('glossary'));
	}
	getGlossary(node ? _Xml.id(node) : null);
}
/* END: загрузка страниц поиска и глоссария */

/* BEGIN: Загрузка учебных модулей */
window.load_subject = window.load_references = function(node) {
	var subjects = _Xml.collect(_Xml.parent(node), 'references subject');
	_.each(subjects, function (subject) {
		return _Xml.fetch(subject, true);
	});
	_Xml.when(subjects).always(function () {
		var subjectTexts = [];
		for (var i = 0; i < subjects.length; ++i) {
			subjectTexts.push( transformToText(subjects[i], commonXSL) );
		}
		text_transform(subjectTexts.join(''), 'cont');
	});
}
function load_unit(node) {
	text_transform(transformToText(node, commonXSL), 'cont');
}
function load_lesson(node) {
	text_transform(transformToText(node, commonXSL), 'cont');
}
function load_studiedproblem(node) {
	_Xml.when(_Xml.fetch(node, true)).always(function () {
		text_transform(transformToText(node, commonXSL), 'cont');
	});
}
function load_text(node) {
	_Xml.when(_Xml.fetch(node, true)).always(function () {
		text_transform(transformToText(node, commonXSL, {'element-title': _Xml.title(node, false, ' <span class="page-n">(страница %s из %s)</span>')}), 'cont');
	});
}
function load_object(node) {
	text_transform(transformToText(node, commonXSL, {'element-title': _Xml.title(node, false, ' <span class="page-n">(страница %s из %s)</span>')}), 'cont');
}
function load_test(node, neighbours) {
	showContentLoader();
	
	eAu.TestUI.load(node);
}

/* END: Загрузка учебных модулей */

(function () {
"use strict";
// TODO: учесть subject, references и поведение прерывания тестов!!!
// TODO: как должна себя вести ссылка, если первый trigger был load,
//       и ссылка после его работы осталась на экране
// TODO: учитывать атрибут 'json'
// TODO: синхронизировать массивы в attrs! Атрибут trigger будет главным!

// Эти атрибуты можно использовать непосредственно на object
var xmlAttributesPolicy = setDefaults({
	'object': {
		'title':      {},
		'type':       { override: false },
		'src':        { override: false },
		'rule':       { unset:    true },
		'width':      {},
		'height':     {},
		'align':      { unset:    function (attrs) { return _.first(attrs)['display-mode'] == 'inline'; } }, // useless width display-mode == 'inline'
		'DB_ID':      { override: false },
		'visible':    {},
		'default':    {},
		'visibility': { override: false, set: 'visible' },
		'autoplay':   {},
		'show-title': {}
	},
	'text': {
		'title':      {},
		'rule':       { unset: true },
		'visibility': { override: false, set: 'visible' },
		'DB_ID':      { unset: false }
	}
}, { override: true, unset: false });

var policySignature = {
	unset:    Boolean,
	override: Boolean,
	set:      String
};

/*
 * @target: где разворачивать ссылку
 *   # blank (_default_) - в новом окне
 *   # self - встраивается на место ссылки
 *   # before - развернуть до контейнера родительской ноды (относительно *XML*)
 *   # after - развернуть после контейнера родительской ноды (относительно *XML*)
 *   # parent - в контейнер родительской ноды (относительно *XML*)
 *   # top - встраивается в ноду #cont
 *   # navigate - переход
 *
 * @trigger: когда запускать механизм обработки внутренней ссылки
 *   # load
 *   # click (_default_)
 * 
 * @leave-link (???): оставлять-ли текст ссылки. В случае, если target == self,
 *  то ссылка после обработки становится неактивной. Если target == parent, blank или top
 *  то этот атрибут не учитывается
 * 
 * @display-mode: режим отображения развёрнутого содержимого
 *   # block (_default_)
 *   # inline
 */ 
// Первое значение - дефолтное
var attributeSignatures = {
	'target': {
		values:   'navigate blank self before after parent top tooltip'.split(' '),
		type:     String,
		defaults: [{
			selector: 'glossary/**/text',
			value:    'tooltip'
		}, {
			selector: 'text or object',
			value:    'blank'
		}],
		processors: {
			'tooltip': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					var text = text_transform( transformToText(node, commonXSL) );
					show_in_tooltip(node, text, data.link);
					
					next();
				});
			},
			'blank': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					// TODO это будет заблокировано браузером
					//      если вызвано при load
					if (/^(text|object)$/.test(_Xml.name(node))) {
						object_object(node, data.link);
					} else {
						throw new Error('target blank is not implemented for anything except text/object');
					}
					
					next();
				});
			},
			'navigate': function (node, data, next) {
				// TODO: show overlay while fetching
				_Xml.when(node).always(function () {
					link_info(_Xml.id(node));
					
					next();
				});
			},
			'self': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					var $link = $(data.link);
					if ($link.closest('body').length) {
						$link.replaceWith($(
							text_transform( transformToText(node, commonXSL) )
						).addClass('eau-xlinked'));
					} else {
						//console.log('Ooops, this item was removed from DOM');
					}
					
					next();
				});
			},
			'top': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					// TODO: что делать если в контейнере несколько ссылок
					$('#cont').html(
						text_transform( transformToText(node, commonXSL) )
					);
					
					next();
				});
			},
			'before': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					var $container = $(data.link).closest('.eAu-paragraph-container');
					$container.length && $container.before(
						text_transform( transformToText(node, commonXSL) )
					);
					
					next();
				});
			},
			'after': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					var $container = $(data.link).closest('.eAu-paragraph-container');
					$container.length && $container.after(
						text_transform( transformToText(node, commonXSL) )
					);
					
					next();
				});
			},
			'parent': function (node, data, next) {
				// TODO: show loading indicator while fetching
				_Xml.fetch(node, true);
				_Xml.when(node).always(function () {
					var $container = $(data.link).closest('.eAu-paragraph-container');
					// TODO: что делать если в контейнере несколько ссылок
					$container.length && $container.replaceWith(
						text_transform( transformToText(node, commonXSL) )
					);
					
					next();
				});
			}
		}
	},
	'trigger': {
		values: 'click load'.split(' '),
		type:   String
	},
	'display-mode': {
		values: 'block inline'.split(' '),
		type:   String
	},
	'leave-link': {
		values: 'yes no'.split(' '),
		type:   Boolean
	}
};

function getNodePolicy (node) {
	return xmlAttributesPolicy[_Xml.name(node)];
}
function getPolicySignature (node) {
	return policySignature;
}

function calculatePolicyItem (policy, policySignature, args, node) {
	return _.reduce(policySignature, function (memo, policyType, policyName) {
		if (policyType == Boolean) {
			memo[policyName] = getBooleanPolicy(policy[policyName], args, node);
		} else if (policyType == String) {
			memo[policyName] = getStringPolicy(policy[policyName], args, node);
		}
		return memo;
	}, {});
}

// @policy   - значение конкретной (override, unset, set) политики для какого-либо атрибута 
// @args     - атрибуты поведения ссылки. Пердставляет из себя массив хэшей (см. collectLinkAttributes).
//             Минимальный размер массива - 1
function getBooleanPolicy (policy, args, node) {
	return _.isFunction(policy)
		? policy.call(node, args) !== false
		: !!policy;
}
// IMPORTANT: значение, возвращаемое данной функцией необходимо проверять
// если значение == null, то это означает отсутствие значения!
function getStringPolicy (policy, args, node) {
	return _.isFunction(policy)
		? policy.call(node, args)
		: _.isString(policy)
			? policy
			: null;
}

function setDefaults (policies, defalts) {
	return _.reduce(policies, function (memo, elementPolicy, tagName) {
		return (memo[tagName] = _.reduce(elementPolicy, function (memo, attributePolicy, attributeName) {
			return (memo[attributeName] = _.extend({}, defalts, attributePolicy || {}), memo);
		}, {}), memo);
	}, {});
}

function getTypedValue (value, values, type) {
	if (type === Boolean) {
		if (/^(yes|true|t|1|on)$/i.test(value)) {
			return true;
		} else if (/^(no|false|f|0|off)$/i.test(value)) {
			return false;
		}
	} else if ( !values || 0 == values.length || _(values).indexOf(value) != -1 ) {
		return type('' + value);
	}
	return null;
}

// TODO В будущем может добавить поддержку атрибутов в селекторе?
//function parseToken (token) {}

// node/node, node/**/node, node
function matchesSimpleSelector (node, selector) {
	var selectorTokens = selector.split('/')
	  , i
	  , token
	  , searchParents = false
	  , anyNode = false
	  , currentNode = node
	  , nodeType
	  , nodeName
	  , isEmptyToken;
	
	for (i = selectorTokens.length - 1; i >= 0; --i) {
		// Если добавятся селекторы по атрибутам нужно будет убрать toLowerCase
		token = selectorTokens[i].toLowerCase();
		isEmptyToken = (token == '');
		
		if ((isEmptyToken && i != 0) || ((searchParents || i == 0) && token == '**')) {
			throw new Error('Invalid token');
		} else if (isEmptyToken) {
			return !currentNode || (nodeType = currentNode.nodeType, nodeType == 9) || nodeType == 11;
		}
		if (!currentNode) {
			return false;
		}
		
		anyNode = (token == '*');
		if (!anyNode) {
			nodeName = currentNode.nodeName.toLowerCase();
		}
		
		// */** равносилен получению родителя
		// Вторая проверка нужна для исключения следующего селектора */**/node
		if (searchParents && !anyNode) {
			currentNode = _Xml.closest(currentNode, token);
			if (!currentNode) {
				return false;
			}
		} else {
			searchParents = (token == '**');
			if (!(searchParents || anyNode || token == nodeName)) {
				return false;
			}
			if (!searchParents) {
				currentNode = currentNode.parentNode;
			}
		}
	}
	
	return !!selector;
}
function matchesSimpleSelectors (node, selectors) {
	return _.any(selectors.split(' or '), function (selector) {
		return matchesSimpleSelector(node, selector);
	});
}

function getDefaultValue(signature, node) {
	var defaultv
	  , extendedDefault;
	
	if (node && signature.defaults) {
		extendedDefault = _.detect(signature.defaults, function (def) {
			return matchesSimpleSelectors(node, def.selector);
		});
		if (extendedDefault) {
			defaultv = extendedDefault.value;
		}
	}
	
	if (_.isFunction(defaultv)) {
		defaultv = defaultv.call(node);
	}
	if (defaultv == null) {
		defaultv = _.first(signature.values);
	}
	
	return getTypedValue(defaultv, !1, signature.type);
}

function normalizeAttributes (linkAttrs, node) {
	var result = {}
	  , lengths = []
	  , maxLength
	  , i, j
	  , name
	  , signature
	  , attrs = _(attributeSignatures).keys();
	
	for (i = attrs.length-1; i >= 0; --i) {
		name = attrs[i];
		signature = attributeSignatures[name];
		
		result[name] = _(linkAttrs[name] || []).map(function (val) {
			var v = getTypedValue(val, signature.values, signature.type);
			return v == null
				? getDefaultValue(signature, node)
				: v;
		});
		
		lengths.push(result[name].length);
	}
	
	maxLength = Math.max.apply(Math, lengths);
	maxLength || (maxLength = 1);
	
	for (i = attrs.length-1; i >= 0; --i) {
		name = attrs[i];
		signature = attributeSignatures[name];
		while (result[name].length < maxLength) {
			result[name].push(
				getDefaultValue(signature, node)
			);
		}
	}
	
	var res = []
	  , intermediate;
	for (i = 0; i < maxLength; ++i) {
		intermediate = {}
		for (j = attrs.length-1; j >= 0; --j) {
			name = attrs[j];
			intermediate[name] = result[name][i];
		}
		res.push(intermediate);
	}
	return res;
}

function collectLinkAttributes (linkAttributes, node) {
	var attrRegexp = /^data-eau-/
	  , linkAttribute
	  , attributeName
	  , attrs = {};
	
	for (var i = linkAttributes.length - 1; i >= 0; --i) {
		linkAttribute = linkAttributes[i];
		if (attrRegexp.test(''+linkAttribute.nodeName)) {
			attributeName = (''+linkAttribute.nodeName).replace(attrRegexp, '');
			if (attributeName.length != (''+linkAttribute.nodeName).length) {
				if ( !_.isUndefined(attributeSignatures[attributeName]) ) {
					attrs[attributeName] = eAu.String.trim(''+linkAttribute.nodeValue).split(/\s+/);
				}
			}
		}
	}
	
	return normalizeAttributes(attrs, node);
}

function collectLinkOverrides (linkAttributes, node, policies) {
	var attrOverrideRegexp =  /^data-eauattr-/
	  , linkAttribute
	  , attributeName
	  , overrides = {};
	
	for (var i = linkAttributes.length - 1; i >= 0; --i) {
		linkAttribute = linkAttributes[i];
		if (attrOverrideRegexp.test(''+linkAttribute.nodeName)) {
			attributeName = (''+linkAttribute.nodeName).replace(attrOverrideRegexp, '');
			if (attributeName.length != (''+linkAttribute.nodeName).length) {
				// TODO allow unknown attributes ???
				if ( !_.isUndefined(policies[attributeName]) ) {
					overrides[attributeName] = ''+linkAttribute.nodeValue;
				}
			}
		}
	}
	
	return overrides;
}

var domClone;

function trololo (link, event) {
	var node = _Xml.$(head, (_Xml.attr(link, 'db-id') || _Xml.attr(link, 'InnerLink') || '').replace(/^EUL:/i, ''))
	  , config = getConfig(link, node);
	if (node && config) {
		if (!domClone) {
			domClone = Sarissa.getDomDocument().appendChild(head.cloneNode(true));
		}
		node = _Xml.$(domClone, _Xml.id(node), '*', 'skip cache');
		// TODO: !!!
		//_preprocessExercises(node);
		// TODO оптимизировать, может оказаться что будем грузить один и
		//      тот-же файл дважды, что не очень хорошо. Надеюсь, правда
		//      что кэш исправит эту ситуацию )
		_Xml.fetch(node);
		if (config.overrides) {
			_.each(config.overrides, function (value, attrName) {
				if (value === null) {
					_Xml.removeAttr(node, attrName);
				} else {
					_Xml.attr(node, attrName, ''+value);
				}
			});
		}
		_.each(config.attributes, function (a) {
			// TODO: clone config to store some personal values?
			if ( (event && event.type == a.trigger) || (!event && a.trigger == 'load') )
				doAction(node, link, _({}).extend(a));
		});
	}
}

$(document).delegate('*[InnerLink], *[db-id]', 'click keyup mousedown mouseup', function (event) {
	if (head) {
		if ( $(this).is('a') )
			event.preventDefault();
		
		trololo(this, event);
	}
});

function doAction (node, link, config, event) {
	var processors;
	
	processors = attributeSignatures.target.processors[config.target];
	if (_.isFunction(processors)) {
		processors = [ processors ]
		attributeSignatures.target.processors[config.target] = processors;
	}
	
	if (_.isArray( processors )) {
		processors = [].concat(processors);
		showModalLoadingOverlay();
		processors.push(function (node) {
			// TODO: может быть каждый раз вызывать после отработки next?
			hideModalLoadingOverlay();
			$window.trigger('eAu:content:link:processed', { xmlnode: node });
		});
		(function (index, node, data) {
			function next () {
				var processor = processors[index++];
				processor && processor(node, data, next);
			}
			return next;
		})(0, node, {
			link:   link,
			config: config,
			event:  event
		})();
	} else {
		// invalid target
		throw new Error('no processor for specified target');
	}
}

/* { attributes: [], overrides: {} } */
function getConfig (link) {
	var node = _Xml.$(head, (_Xml.attr(link, 'db-id') || _Xml.attr(link, 'InnerLink') || '').replace(/^EUL:/i, ''))
	  , config = {}
	  , linkAttributes = _.toArray(link.attributes)
	  , policy;
	
	config.attributes = collectLinkAttributes(linkAttributes, node);
	
	if (!node || !getNodePolicy(node)) { return config; }
	
	config.overrides = _.reduce( getNodePolicy(node), function (memo, policyItem, attrName) {
		policyItem = calculatePolicyItem(policyItem, getPolicySignature(node), config.attributes, node);
		
		// TODO: what to do with unknown attributes???
		if (policyItem.unset) {
			memo[attrName] = null;
		} else {
			if (!policyItem.override) {
				delete memo[attrName];
			}
			if (policyItem.set !== null) {
				memo[attrName] = policyItem.set;
			}
		}
		
		return memo;
	}, collectLinkOverrides(linkAttributes, node, getNodePolicy(node)) );
	
	return config;
}

$window.bind('eAu:content:afterchange', function () {
	if (head) {
		// TODO стоит-ли onload обрабатывать элементы db-id
		//_.each($('*[InnerLink], *[db-id]').get(), function (link) { trololo(link); });
		_.each($('*[InnerLink]').get(), function (link) { trololo(link); });
	}
});

window.eAu.LinkManager = {
	defaults: function (attribute, update, reset) {
		var signature = attributeSignatures['' + attribute];
		
		if (!_.isUndefined(signature)) {
			if (reset) {
			} else {
				signature.defaults = _.reduce(update, function (memo, newItem) {
					var existing = _.detect(memo, function (item) {
						return item.selector == newItem.selector;
					});
					if (existing) {
						existing.value = newItem.value;
					} else {
						memo.push(newItem);
					}
					return memo;
				}, signature.defaults || []);
			}
		}
		return this;
	},
	// TODO пока полезно только для атрибута target
	processors: function (attribute, update, reset) {
		var signature = attributeSignatures['' + attribute]
		  , newProcessors = [];
		
		if (!_.isUndefined(signature)) {
			if (reset) {
			} else {
				signature.processors = _.reduce(update, function (memo, processor, processorIndex) {
					if (_.indexOf(signature.values, processorIndex) == -1) {
						newProcessors.push(processorIndex);
					}
					
					if (!memo[processorIndex]) {
						memo[processorIndex] = [];
					}
					if (_.indexOf(memo[processorIndex], processor) == -1) {
						memo[processorIndex].push(processor);
					}

					return memo;
				}, signature.processors || {});
			}
			signature.values = [].concat(signature.values, newProcessors);
		}
		
		return this;
	},
	policy: function (nodeName, update, reset) {
		if (!_.isUndefined(xmlAttributesPolicy[nodeName])) {
			if (reset) {
			} else {
				xmlAttributesPolicy[nodeName] = _.reduce(update, function (memo, obj, attr) {
					if (_.isUndefined(memo[attr])) {
						memo[attr] = obj;
					} else {
						_.extend(memo[attr], obj);
					}
					return memo;
				}, xmlAttributesPolicy[nodeName]);
				xmlAttributesPolicy = setDefaults(xmlAttributesPolicy, { override: true, unset: false });
			}
		}
		return this;
	}
}

})();

