function TemplateManager ( $settings ) {
	this.languagePrefix = '';
	this.pageId = 0;
	this.editUrl = '';
	this.browseUrl = '';
	this.saveLayoutUrl = '';
	this.saveContentUrl = '';
	this.editingMode = 0; // from {1 - browse, 2 - content, 3 - design}

	this.pageInfo = {editors: [], revisions: {}}; // information about page in "Content Mode"
	this.pageInfoUpdateTimer = null;

	this.revisionStatusMap = {
		1: 'cms-revision-published',
		2: 'cms-revision-pending',
		0: 'cms-revision-declined'
	};

	this._blocks = {};
	this._blockOrder = [];
	this.inDrag = false; // don't process mouse over/out events while in drag mode

	$.extend(this, $settings);

	var $me = this;

	$(document).ready(function() {
		$me.init();
	});
}

TemplateManager.prototype.init = function () {
	this.searchBlocks();

	if ( !this.editingMode ) {
		return;
	}

	// show special toolbar when in any of 3 browse modes
	var $template_manager = this,
		$head_frame = getFrame('head'),
		$extra_toolbar = $head_frame.$('div.front-extra-toolbar').clone(); // clone to keep original untouched

	$('a', $extra_toolbar).each(function () {
		// cut from end, because IE7 adds base_href to beginning of href
		var $editing_mode = $(this).attr('href');
		$editing_mode = $editing_mode.substr($editing_mode.length - 1, 1);

		$(this).attr('href', $template_manager.browseUrl.replace('#EDITING_MODE#', $editing_mode));

		if ( $editing_mode == $template_manager.editingMode ) {
			$(this).parents('td:first').addClass('button-active').prevAll('td:first').addClass('button-active');
		}
	});

	$head_frame.$('#extra_toolbar').html($extra_toolbar.html());

	var $hover_effect = [];

	if ( $template_manager.editingMode > 1 ) {
		// all modes except for "Browse Mode"
//		$hover_effect.push('div.cms-section-properties-btn:first');
	}

	if ( $template_manager.editingMode == 2 ) {
		// Content Mode
//		$hover_effect.push('div.cms-edit-btn');
//		$hover_effect.push('div.admin-edit-btn');

		// make all spans with phrases clickable
		$template_manager.setupEditTranslationButtons(document);

		// hide "Revision History" div on every body click (bubbled), but not a "toolbar button", that opens it
		$('body').click(function ($e) {
			var $target = $($e.target),
				$id = $target.attr('id');

			if ( $id && ($id == 'div_history' || $target.parent().attr('id') == 'div_history') ) {
				return;
			}

			$('#cms-revision-dropdown:visible').hide();
		});
	}

	if ( $template_manager.editingMode == 3 ) {
		// Design Mode
//		$hover_effect.push('div.cms-save-layout-btn:first, div.cms-cancel-layout-btn:first');

		$template_manager.renumberMovableElements();

		$('div.movable-area').sortable({
			placeholder: 'move-helper',
			handle: '.movable-header',
			items: 'div.movable-element',
			connectWith: ['div.movable-area'],
			tolerance: 'pointer',
			start: function (e, ui) {
				$template_manager.inDrag = true;
				ui.placeholder.height(ui.item.height());
			},
			stop: function (e, ui) {
				$template_manager.inDrag = false;

				// mouseout doesn't happen while in drag, so compensate it here
				var $header = $('.movable-header', ui.item);
				$('div.block-edit-block-btn-container', $header).mouseout();
			},
			change: function (e, ui) {
				$('div.cms-layout-btn-container').show();
			}
		});
	}

	// make requested elements fully visible on mouseover
	if ( $hover_effect.length ) {
		$($hover_effect.join(', '))
		.mouseover(function (e) {
			$(this).css('opacity', 1);
		})
		.mouseout(function (e) {
			$(this).css('opacity', 0.5);
		});
	}

	// related to content revision control toolbar
	if ( $template_manager.revisionToolbarEnabled() ) {
		$template_manager.initRevisionToolbar();
	}

	$('body')
	.bind('InlineEditor.Focus', function ($e, $editor_event) {
		$template_manager.inlineEditorFocus($editor_event);
	})
	.bind('InlineEditor.Blur', function ($e, $editor_event) {
		$template_manager.inlineEditorBlur($editor_event);
	});
};

TemplateManager.prototype.revisionToolbarEnabled = function () {
	return $('#cms-revision-toolbar-layer').length == 1;
};

TemplateManager.prototype.initRevisionToolbar = function () {
	var $template_manager = this;

	$('#cms-toggle-revision-toolbar').click(function ($e) {
		var $me = $(this);

		if ( $me.hasClass('opened') ) {
			var $height = $('#cms-revision-toolbar').height();

			$('#cms-revision-toolbar-layer').animate({top: (-1) * $height}, 'fast');
			$('#cms-editing-notice, #cms-revision-dropdown').hide();
			setCookie('toolbar_hidden', 1);
		}
		else {
			$('#cms-revision-toolbar-layer').animate({top: 0}, 'fast');
			setCookie('toolbar_hidden', 0);
		}

		$me.toggleClass('opened');

		$e.preventDefault();
	});

	$('#cms-close-toolbar').click(function ($e) {
		var $height = $('#cms-revision-toolbar').height();

		$('#cms-toggle-revision-toolbar').removeClass('opened');
		$('#cms-revision-toolbar-layer').css('top', (-1) * $height);

		$('#cms-editing-notice, #cms-revision-dropdown').hide();
		setCookie('toolbar_hidden', 1);

		$e.preventDefault();
	});

	$('#cms-close-editing-notice').click(function ($e) {
		$('#cms-editing-notice').hide();

		$e.preventDefault();
	});

	$('body').on('click', '#cms-revision-dropdown .top .item', function ($e) {
		window.location.assign(
			$('a:first', this).attr('href')
		);
	});

	setInterval(
		function () {
			$.getJSON(
				$('#kf_revisions_' + $template_manager.pageId).attr('action') + '&events[page-revision]=OnGetInfo',
				function ($data) {
					$template_manager.pageInfo = $data;
					$template_manager.processPageInfo();
				}
			);
		}, 20 * 1000 // 20 seconds
	);

	if ( !$.isEmptyObject($template_manager.pageInfo) ) {
		$template_manager.processPageInfo();
	}
};

TemplateManager.prototype.requirePageInfoUpdate = function ($now) {
	var $me = this;

	if ( $now === undefined || $now === false ) {
		clearTimeout(this.pageInfoUpdateTimer);
		this.pageInfoUpdateTimer = setInterval(function () { $me.requirePageInfoUpdate(true); }, 20 * 1000); // 20 seconds

		return;
	}

	$.getJSON(
		$('#kf_revisions_' + this.pageId).attr('action').replace('-d-', '-s-') + '&events[page-revision]=OnGetInfo',
		function ($data) {
			$me.pageInfo = $data;
			$me.processPageInfo();
		}
	);
};

TemplateManager.prototype.processPageInfo = function () {
	this.updateCurrentRevision();

	if ( $('#cms-toggle-revision-toolbar').hasClass('opened') ) {
		this.showEditingNotice();
	}

	this.updateRevisionHistory();
};

TemplateManager.prototype.updateCurrentRevision = function () {
	var	$me = this,
		$title = $('.revision-title', '#cms-current-revision-info');

	$title.html(this.pageInfo.current_revision.title);
	$('.draft-saved', '#cms-current-revision-info').html(this.pageInfo.current_revision.saved);

	$.each(this.revisionStatusMap, function ($status, $class_name) {
		$title.toggleClass($class_name, $status === $me.pageInfo.current_revision.status);
	});

	$.each(this.pageInfo.current_revision.toolbar_state, function ($button_name, $is_enabled) {
		a_toolbar.SetEnabled($button_name, $is_enabled);
	});
};

TemplateManager.prototype.showEditingNotice = function () {
	var $notice = $('#cms-editing-notice');

	if ( this.pageInfo.editors.length ) {
		var $notice_span = $('span:first', $notice);

		if ( $notice_span.data('prev_editors') != this.pageInfo.editors.join(',') ) {
			// show notice, only when editors change occurs
			$notice_span.html(this.pageInfo.editors_warning).data('prev_editors', this.pageInfo.editors.join(','));

			if ( $notice.is(':hidden') ) {
				$notice.fadeIn();
			}
		}
	}
	else if ( $notice.is(':visible') ) {
		$notice.fadeOut();
	}
};

TemplateManager.prototype.updateRevisionHistory = function () {
	var	$me = this,
		$revision_container = $('.top', '#cms-revision-dropdown'),
		$revision_mask = '	<div class="item">\
								<span class="{CLASS}"><a href="{LINK}">{TITLE}</a> ({STATUS_LABEL})</span>\
								<div class="cms-left">{DATETIME}</div>\
								<div class="cms-right">{AUTHOR}</div>\
								<div class="cms-clear"></div>\
							</div>';

	$revision_container.empty();

	if ( $.isArray(this.pageInfo.revisions) ) {
		return;
	}

	$.each(this.pageInfo.revisions, function ($revision_number, $revision_info) {
		var $html = $revision_mask;

		$.each($revision_info, function ($field, $value) {
			$html = $html.replace(new RegExp('{' + $field.toUpperCase() + '}', 'g'), $value);
		});

		$html = $html.replace(/{CLASS}/g, $me.revisionStatusMap[$revision_info.status]);

		if ( $revision_info['draft'] ) {
			$html = $html.replace(/{LINK}/g, $me.browseUrl.replace('#EDITING_MODE#', '2'));
		}
		else {
			$html = $html.replace(/{LINK}/g, $me.browseUrl.replace('#EDITING_MODE#', '2') + '&revision=' + $revision_number.substr(1));
		}

		$revision_container.append($html);
	});
};

TemplateManager.prototype.inlineEditorFocus = function ($editor_event) {
	var $container = $($editor_event.editor.element.$).parent('.cms-edit-btn-container');

	$container.removeClass('mode-inline-edit');
};

TemplateManager.prototype.inlineEditorBlur = function ($editor_event) {
	var	$me = this,
		$element = $($editor_event.editor.element.$),
		$container = $element.parent('.cms-edit-btn-container'),
		$url_params = {},
		$content_id = $container.data('content-id');

	if ( $element.data('no_save_on_blur') === true ) {
		$element.data('no_save_on_blur', null);
		return;
	}

	$element.css('position', 'static');
	$container.addClass('mode-inline-saving');

	$url_params["content[" + $content_id + "][" + this.languagePrefix + "Content]"] = $editor_event.editor.getData();

	$.post(
		this.saveContentUrl,
		$url_params,
		function ($data) {
			if ( $data != 'OK' ) {
				return;
			}

			if ( $me.revisionToolbarEnabled() ) {
				$me.requirePageInfoUpdate(true);
			}

			$element.css('position', 'relative');
			$container.removeClass('mode-inline-saving').addClass('mode-inline-edit');
		}
	);
};

TemplateManager.prototype.revisionToolbarClick = function ($button_name) {
//	console.log('button ', $button_name, ' clicked');

	var $button_event_map = {
		'select': 'OnSave',
		'delete': 'OnDiscard',
		'approve': 'OnPublish',
		'decline': 'OnDecline'
	};

	if ( $button_event_map[$button_name] !== undefined ) {
		$form_name = 'kf_revisions_' + this.pageId;
		submit_event('page-revision', $button_event_map[$button_name]);

		return;
	}

	switch ( $button_name ) {
		case 'preview':
			var $url = this.browseUrl.replace('#EDITING_MODE#', 0).replace(/&(admin|editing_mode)=[\d]/g, '');
			window.open($url + '&preview=1');
			break;

		case 'history':
			$('#cms-revision-dropdown').toggle();
			break;
	}
};

TemplateManager.prototype.setupEditTranslationButtons = function ($container) {
	$("span[name='cms-translate-phrase']", $container).each(function() {
		var $me = $(this),
			$parent_link = $me.parents('a:first');

		if ( $parent_link.length == 0 ) {
			// span in not inside "a" tag
			$me.prepend('<div class="cms-edit-btn"><div class="cms-btn-text">Edit</div></div>');
			$('div.cms-edit-btn:first', $me).click(TemplateManager.prototype.translatePhrase);

			$me.dblclick(function ($e) {
				$('div.cms-edit-btn:first', this).click();

				$e.preventDefault();
			});

			var $effect_element = $me;
		}
		else {
			// span is inside "a" tag
			var $clone = $me.clone();
			$clone.empty().attr('title', '');

			// in case if "a" tag is "display: block", then make "span" the same
			$clone.css('display', $parent_link.css('display'));

			$parent_link.html($me.html()).wrap($clone);
			$parent_link.before('<div class="cms-edit-btn" title="' + $me.attr('title') + '"><div class="cms-btn-text">Edit</div></div>');
			$parent_link.prev('div.cms-edit-btn:first').click(TemplateManager.prototype.translatePhrase);

			var $effect_element = $parent_link.parents("span[name='cms-translate-phrase']:first");
		}

		$effect_element
		.mouseover(function($e) {
			$('div.cms-edit-btn', this).css('display', 'inline');
		})
		.mouseout(function($e) {
			$('div.cms-edit-btn', this).hide();
		});
	});
};

TemplateManager.prototype.translatePhrase = function ($e) {
	var $translate_url = $(this).parents("span[name='cms-translate-phrase']:first").attr('href');

	if ( $translate_url.match(/javascript:(.*)/) ) {
		eval(RegExp.$1);
	}
	else {
		window.location.href = $translate_url;
	}

	$e.preventDefault();
};

TemplateManager.prototype.renumberMovableElements = function () {
	var $area_index = 0;

	// 1. dynamically assign IDs to all movable elements
	$('div.movable-area').each(function () {
		var $element_index = 0;

		$('div.movable-element', this).each(function () {
			$(this).attr('id', 'target_order_a' + $area_index + 'e' + $element_index);
			$element_index++;
		});

		$area_index++;
	});
};

TemplateManager.prototype.saveLayout = function () {
	// prepare order string
	var $sort_order = [];

	$('div.movable-area').each(function ($area_index) {
		var $order = $(this).sortable('serialize').replace(/target_order\[\]/g, 'target_order[' + $area_index + '][]');

		if ( $order ) {
			$sort_order.push($order);
		}
	});

	$sort_order = $sort_order.join('&');

	// save order string
	var $me = this;

	var $settings = {
		url: this.saveLayoutUrl + '&' + $sort_order + '&width=200&height=70&modal=true',
		caption: 'Layout Saving Result',
		onDataReceived: function ($data) {
			var $message = '';

			if ( $data == 'OK' ) {
				$message = 'New Layout Saved';
				$('div.cms-layout-btn-container').hide();
				$me.renumberMovableElements();
			}
			else {
				$message = 'Failed to Save New Layout';
			}

			$data = '<div style="text-align: center;">' + $message + '<br/><br/><input type="button" class="button" value="OK" onclick="TB.remove();"/></div>';

			return $data;
		}
	};

	TB.setWindowTitle(window.top.document.title.replace(main_title + ' :: ', ''));
	TB.show($settings);
};

TemplateManager.prototype.cancelLayout = function () {
	window.location.href = window.location.href;
};

TemplateManager.prototype.onBtnClick = function ($e, $element) {
	var $id = $element.id.replace(/_btn$/, '');
	var $block_info = this._blocks[$id];
	var $url = this.editUrl.replace('#BLOCK#', $block_info.block_name + ':' + $block_info.function_name).replace('#EVENT#', 'OnLoadBlock');

	direct_edit('theme-file', $url);

	$e.stopPropagation();
};

TemplateManager.prototype.onMouseOver = function ($e, $element) {
	if ( this.inDrag ) {
		return;
	}

	$element = $($element);

	if ( $element.hasClass('block-edit-design-btn-container') ) {
		$($element).addClass('block-edit-design-btn-container-over');

		var $button_group = $('div.cms-edit-design-btn-container:first', $element);

		if ( $button_group.length ) {
			$button_group.show();
		}
		else {
			$('.cms-edit-design-btn:first', $element).show();
		}
	}
	else {
		$($element).addClass('block-edit-block-btn-container-over');
		$('.cms-edit-block-btn:first', $element).show();
	}

	$e.stopPropagation();
};

TemplateManager.prototype.onMouseOut = function ($e, $element) {
	if ( this.inDrag ) {
		return;
	}

	$element = $($element);

	if ( $element.hasClass('block-edit-design-btn-container') ) {
		$($element).removeClass('block-edit-design-btn-container-over');

		var $button_group = $('div.cms-edit-design-btn-container:first', $element);

		if ( $button_group.length ) {
			$button_group.hide();
		}
		else {
			$('.cms-edit-design-btn:first', $element).hide();
		}
	}
	else {
		$($element).removeClass('block-edit-block-btn-container-over');
		$('.cms-edit-block-btn:first', $element).hide();
	}

	$e.stopPropagation();
};

TemplateManager.prototype.searchBlocks = function () {
	var $design_containers = $('div.block-edit-design-btn-container');
	var $block_containers = $('div.block-edit-block-btn-container');

	$design_containers.each(function () {
		var $block_container = $('div.block-edit-block-btn-container:first', this);

		if ( $block_container.length ) {
			$block_containers = $block_containers.not($block_container);

			// place "Edit Block" button near "Edit Design" button
			var $edit_design_btn = $('.cms-edit-design-btn:first', this);
			var $edit_block_btn = $('.cms-edit-block-btn:first', $block_container);

			$edit_design_btn
			.wrap('<div class="cms-edit-design-btn-container"></div>')
			.before($edit_block_btn.clone());

			$edit_block_btn.remove();

			// make "hint" from "Edit Block" button container main
			$(this).attr('title', $block_container.attr('title'));
			$block_container.attr('title', '');

			TemplateManager.prototype.registerBlock.call(aTemplateManager, $block_container.get(0), ['hover']);
		}

		TemplateManager.prototype.registerBlock.call(aTemplateManager, this, ['dblclick']);
	});

	$block_containers.each(function() {
		TemplateManager.prototype.registerBlock.call(aTemplateManager, this);
	});

	/*$('div').each (function () {
		*//*var $id = $(this).attr('id');

		if ( !$id || $id.match(/parser_block\[.*\].*_btn$/) || !$id.match(/parser_block\[.*\]/) ) {
			// skip other divs
			return true;
		}*//*


		TemplateManager.prototype.registerBlock.call(aTemplateManager, this);
	});*/
};

TemplateManager.prototype.registerBlock = function ($element, $skip_events) {
	var $params = $element.getAttribute('params').split(':');

	this._blocks[$element.id] = {
		block_name: $params[0],
		function_name: $params[1]
	};

	var $btn = document.getElementById($element.id + '_btn');

	$($btn).click(function (ev) {
		TemplateManager.prototype.onBtnClick.call(aTemplateManager, ev, this);
	});

	if ( $skip_events === undefined ) {
		$skip_events = [];
	}

	if ( !in_array('dblclick', $skip_events) ) {
		$($element).dblclick(function (ev) {
			TemplateManager.prototype.onBtnClick.call(aTemplateManager, ev, this);
		});
	}

	if ( !in_array('hover', $skip_events) ) {
		$($element)
		.mouseover(function (ev) {
			TemplateManager.prototype.onMouseOver.call(aTemplateManager, ev, this);
		})
		.mouseout(function (ev) {
			TemplateManager.prototype.onMouseOut.call(aTemplateManager, ev, this);
		});
	}

	this._blockOrder.push($element.id);
};
