/*
 * Shell for user interface (/user).
 */
$$.shell = new function () {
	const debug = $$.debug("shell");

	const pushHistoryState = function (href) {
		if (!history.state || history.state.href != href) {
			debug("pushHistoryState: ", href, "; current: ", history.state);
			history.pushState({href: href}, null, href);
		}
	}

	/**
	 * Follow the link with preventing default handling.
	 */
	const followLink = function (href, event) {
		debug("followLink: ", href, event);
		contentLoad(href);
		if (event)
			event.preventDefault();
	}

	/**
	 * Creates div s in #content and #title nodes.
	 * @param {*} command id attribute of created div s.
	 * @param {*} closable
	 */
	const getCommandDiv = function (command, closable) {
		let $commandDiv = $("body > #content > div#" + command);

		if (closable) {
			$("body > #content > div[id!='" + command + "']").hide();
			$commandDiv.show();

			// процесс уже был открыт, если нет открытых с классом editorStopReload редакторов - то перезагрузка
			if (command.match(/process(\-*\d+)/) &&
				$commandDiv.find(".editorStopReload:visible").length == 0) {
				removeCommandDiv(command);
				$commandDiv = $();
			}
		}

		if ($commandDiv.length == 0) {
			$('body > #content').append(sprintf("<div id='%s'></div>", command));
			$commandDiv = $("body > #content > div#" + command);

			let divTemplate =
				"<div id='%s' class='status'>\
					<div class='wrap'>\
						<div class='left'>\
							<div class='title'>\
								<h1 class='title' title='Refresh'></h1>";
			if (closable) {
				divTemplate +=
					"<div class='icon-close btn-white-hover icon'><i class='ti-close'></i></div>";
			}

			divTemplate += "\
							</div>\
						</div>\
						<div class='center'>\
							<h1 class='state'></h1>\
						</div>\
					</div>\
				</div>";

			$('#title #empty').after(sprintf(divTemplate, command));

			$commandDiv.isNew = true;
		}

		debug("getCommandDiv", $commandDiv.isNew, $commandDiv);

		$("body > #content > div[id!='" + command + "']").hide();
		$commandDiv.show();

		$("#title > div.status[id!='" + command + "']").hide();
		$("#title > div#" + command).show();

		return $commandDiv;
	}

	const onCommandDivShow = function ($commandDiv) {
		// вызов onShow обработчика, если оснастка его повесила
		const onShow = $commandDiv.data('onShow');
		if (onShow) {
			debug("call onShow");
			onShow();
		}
	}

	/**
	 * Removes elements in #content, #title nodes and #objectBuffer.
	 * @param {*} command id of element.
	 */
	const removeCommandDiv = function (command) {
		$("body > #content > div#" + command).remove();
		$("#title > div#" + command).remove();
		$('#objectBuffer ul li[value=' + command + ']').remove();
		updateBufferCount();
	}

	const getBufferCount = function () {
		return $('#objectBuffer ul li:not([style*="display: none"])').length;
	}

	const updateBufferCount = function () {
		$("#objectBuffer .object-count").text(getBufferCount());
	}

	const menuItems = {
		titles: [],
		icons: [],
		add: function (item) {
			item.titlePath = item.title;
			if (this.titles.length) {
				const sep = " / ";
				item.titlePath = this.titles.join(sep) + sep + item.title;
			}

			if (this.icons.length)
				item.icons = this.icons.slice();

			// TODO: Place items in separated sub object to avoid collisions.
			menuItems[item.href] = item;

			debug("menuItems add()", item);
		}
	}

	// next contentLoad starts only after previous is done
	let contentLoadDfd;

	const contentLoad = function (href, options) {
		debug("contentLoad", href, contentLoadDfd);

		const contentLoadCurrentDfd = contentLoadDfd;
		const contentLoadNewDfd = $.Deferred();
		$.when(contentLoadCurrentDfd).done(() => {
			contentLoadAsync(href, contentLoadNewDfd, options);
		});
		contentLoadDfd = contentLoadNewDfd;

		return contentLoadDfd.promise();
	}

	const contentLoadAsync = function (href, contentLoadDfd, options) {
		debug("contentLoadAsync: ", href, contentLoadDfd);

		// remove proto, host, port, if exist
		// can be on restoring address line
		const pos = href.indexOf('//');
		let command = pos >= 0 ? href.substring(href.indexOf('/', pos + 2)) : href;

		// append /user/ at the beginning if missing
		if (!command.startsWith("/user/"))
			command = "/user/" + command;

		// try to open a menu tool
		const isMenu = loadMenuTool(href, command, contentLoadDfd, options);

		// open object, if wasn't a menu tool
		if (!isMenu) {
			let m = null;
			let url = null;

			let bgcolor = "";
			let objectId = 0;

			// open customer
			if ((m = href.match(/.*customer#(\d+)/)) != null) {
				url = "/user/customer.do?id=" + m[1];
				bgcolor = "#A1D0C9";
			}
			// open process
			else if ((m = href.match(/.*process#(\-*\d+)/)) != null) {
				url = "/user/process.do?id=" + (objectId = m[1]);
				if (objectId < 0)
					url += "&wizard=1";
				bgcolor = "#E6F7C0";
			}
			// open user profile
			else if ((m = href.match(/.*profile#(\d+)/)) != null) {
				url = "/user/profile.do?method=getUserProfile&userId=" + m[1];
				bgcolor = "#C3A8D5";
			}
			// plugin defined mappings
			else {
				const mapping = $$.shell.mapUrl(href)
				if (mapping) {
					url = mapping.url;
					bgcolor = mapping.bgcolor;
				}
			}

			const maxObjectsInBuffer = $$.pers['iface.buffer.maxObjects'] || 15;

			// 1 - последний добавляется сверху, нижние удаляются,
			// 2 - последний добавляется внизу либо на своё предшествующее место, первые удаляются
			const bufferBehavior = $$.pers['iface.buffer.behavior'] || 1;

			if (url) {
				// берём после префикса /user , для сохранения обратной совместимости
				const id = command.substring(6).replace("#", "-");

				$("#taskPanel div.btn-task-active").attr('scroll', $(window).scrollTop());
				$("#taskPanel div")
					.removeClass("btn-task-active btn-blue").addClass("btn-white btn-task");

				const $commandLi = $(sprintf("#objectBuffer ul>li[value='%s']", id));
				if ($commandLi.length) {
					if (bufferBehavior == 1)
						$commandLi.remove();
					else
						$commandLi.css("display", "none");
				}

				if (bufferBehavior == 1) {
					$('#objectBuffer ul>li:gt(' + maxObjectsInBuffer + ')').each(function () {
						removeCommandDiv($(this).attr("value"));
						$(this).remove();
					})
				} else {
					while (getBufferCount() > maxObjectsInBuffer) {
						const $li = $("#objectBuffer ul li:first");
						removeCommandDiv($li.attr("value"));
						$li.remove();
					}
				}

				const currentlyOpen = $("body > #content > div:visible").attr("id");

				const $commandDiv = getCommandDiv(id, true);

				// если это не повторное открытие того же объекта
				if ($commandDiv.attr("id") != currentlyOpen) {
					pushHistoryState(command);

					onCommandDivShow($commandDiv);

					// если открыт уже какой-то объект - перемещение его в буфер
					if (typeof $$.closeObject == 'function')
						$$.closeObject();

					updateBufferCount();

					// функция перемещения текущего объекта в буфер
					$$.closeObject = function () {
						const liCode = sprintf("<li style='border-left: 8px solid %s;' value='%s'>%s</li>", bgcolor, id,
							"<span class='icon-close ti-close'></span>" + $("#title #" + id + " h1.title").html());

						if (bufferBehavior == 1)
							$('#objectBuffer ul').prepend(liCode);
						else {
							const $li = $('#objectBuffer ul>li[value="' + id + '"]');
							if ($li.length)
								$li.replaceWith(liCode);
							else
								$('#objectBuffer ul').append(liCode);
						}

						const $commandLi = $('#objectBuffer ul>li[value="' + id + '"]');

						$commandLi.one("click", function (event) {
							contentLoad(href);
							$('#objectBuffer > ul').hide();
							return false;
						});

						$commandLi.find(".icon-close").one("click", function (event) {
							removeCommandDiv(id);
							$commandLi.remove();
							updateBufferCount();
							event.stopPropagation();
							return false;
						});

						updateBufferCount();

						$$.closeObject = null;
					}

					$(window).scrollTop(0);
				}

				if ($commandDiv.isNew) {
					$$.ajax.load(url, $commandDiv, {dfd: contentLoadDfd}).done(() => {
						$("#title > .status:visible > .wrap > .left > .title .icon-close")
							.one("click", function () {
								if (objectId < 0)
									alert("Объект не инициализирован до конца, его невозможно закрыть.");
								else {
									removeCommandDiv(id);

									$$.closeObject = null;
									window.history.back();
								}
							});

						// refresh link
						$("#title > .status:visible > .wrap > .left > .title h1.title")
							.click(function () {
								$$.ajax.load(url, $commandDiv);
							});
					});
				} else
					contentLoadDfd.resolve();
			} else
				contentLoadDfd.resolve();
		}
	}

	const loadMenuTool = function (href, command, contentLoadDfd, options) {
		options = options || {};

		const pos = command.indexOf('#');
		const commandBeforeHash = pos > 0 ? command.substring(0, pos) : command;

		const item = menuItems[commandBeforeHash];
		if (item) {
			const idParam = getIdParam(command, pos, item.action);

			// after prefix '/user', for backward compatibility
			const id = commandBeforeHash.substring(6).replace(/\//g, "-");

			if (!item.allowed)
				alert("The menu item is not allowed.");
			else {
				let $taskButton = $('#taskPanel > div#' + id);

				if ($taskButton.length === 0) {
					let taskButton = sprintf("<div class='btn-blue btn-task-active' id='%s' title='%s'>", id, item.titlePath);

					// progress button and removing it after load has done
					taskButton += "<span class='progress'><i class='progress-icon ti-reload'></i>&nbsp;</span>";
					contentLoadDfd.done(() => {
						$taskButton.find(".progress").remove();
					});

					if (item.icons) {
						item.icons.forEach((icon) => {
							taskButton += sprintf("<span class='%s'>&nbsp;</span>", icon);
						});
					}

					taskButton += sprintf("<span class='title'>%s</span>", item.title);

					if (!options.pinned)
						taskButton += "<span class='icon-close ti-close'></span>";

					taskButton += "</div>";

					$('#taskPanel').append(taskButton);
					$taskButton = $('#taskPanel > div#' + id);

					let $commandDiv;

					$taskButton.data("href", href);

					$taskButton.contextmenu(function (e) {
						if (e) {
							e.preventDefault();
						}

						if ($taskButton.hasClass('btn-task-active')) {
							const contextMenu = $('#activeContextMenu');
							contextMenu.data('activeid', id);
							$$.ui.menuInit($(e.target), contextMenu, 'right', true);
						}
					});

					$taskButton.click(function () {
						$commandDiv = getCommandDiv(id);

						if ($commandDiv.isNew) {
							// refresh link
							$("#title > #" + id + ".status > .wrap > .left > .title h1.title")
								.click(function () {
									removeCommandDiv(id);
									$taskButton.click();
									$$.ajax.load(item.action + idParam, $commandDiv);
								});
						}

						if (typeof $$.closeObject == 'function')
							$$.closeObject();

						$("#taskPanel div[id!='" + id + "']")
							.removeClass("btn-task-active btn-blue").addClass("btn-white btn-task");

						$taskButton
							.removeClass("btn-white btn-task").addClass("btn-task-active btn-blue");

						$(window).scrollTop($taskButton.attr('scroll'));

						pushHistoryState(href);

						onCommandDivShow($commandDiv);
					});

					$taskButton.click();

					$$.ajax.load(item.action + idParam, $commandDiv, {dfd: contentLoadDfd});

					$taskButton.find('.icon-close').click(function () {
						// закрытие активной оснастки
						if ($taskButton.hasClass("btn-task-active")) {
							// последняя неактивная кнопка становится активной
							const $inactiveButtons = $("#taskPanel > div.btn-task");
							if ($inactiveButtons.length > 0)
								$inactiveButtons[$inactiveButtons.length - 1].click()
							else
								$("#title > #empty").show();
						}
						$taskButton.remove();
						removeCommandDiv(id);
					});
				} else {
					$taskButton.click();
					contentLoadDfd.resolve();
				}
			}
			return true;
		}
	}

	/**
	 * Creates from #<ID> ending of URL id=<ID> HTTP param.
	 * @param {*} command command URL, shown in browser address field
	 * @param {*} pos pos of '#' in URL
	 * @param {*} action URL, actually called from the server side
	 * @returns
	 */
	const getIdParam = (command, pos, action) => {
		if (pos > 0)
			return (action.includes('?') > 0 ? "&" : "?") + "id=" + command.substring(pos + 1);
		return "";
	}

	const initBuffer = function () {
		window.addEventListener("popstate", function (e) {
			// при переходе по # ссылкам e.state=null
			if (e.state) {
				debug("popstate: ", e.state);
				contentLoad(e.state.href);
			} else {
				//В Chrome выдаёт ошибку.
				//alert( 'Открыта некорректная ссылка, сообщите место её нахождения разработчикам!' );
			}
		}, false);

		// заглушка с пустой функцией стопа таймера
		let popupObjectBuffer = {
			stopTimer: function () {}
		};

		if (($$.pers["iface.buffer.openOnLongPress"] || 0) === 1) {
			const debug = $$.debug("buffer");

			const $buffer = $("#objectBuffer");
			const $bufferDrop = $("#objectBuffer > ul.drop");

			popupObjectBuffer = {
				startTimer: function (event) {
					// buffer is shown
					if ($bufferDrop.is(":visible"))
						return;

					debug('Start timer', event);

					window.clearTimeout(this.timer);

					this.timer = window.setTimeout(function () {
						popupObjectBuffer.showObjectBuffer(event);
					}, 500);
				},

				stopTimer: function () {
					window.clearTimeout(this.timer);
					debug('Stop timer', popupObjectBuffer);
				},

				showObjectBuffer: function (e) {
					debug('Show buffer', popupObjectBuffer);

					// отображать только непустой буфер
					if (getBufferCount() > 0) {
						$buffer.css("position", "static")

						$bufferDrop
							.css("position", "absolute")
							.css("left", e.clientX)
							.css("top", e.clientY)
							.show();
					}

					const closeBuffer = function (e) {
						// неоднократно могут вызываться при очистке буфера
						if ($buffer.find(e.target).length <= 0) {
							debug('Hide buffer');

							$buffer.css("position", "relative");

							const position = $bufferDrop.position();
							const width = $bufferDrop.width();
							const height = $bufferDrop.height();

							$bufferDrop
								.css('display', 'none')
								.css('position', '')
								.css("z-index", '')
								.css("left", '')
								.css("top", '');

							// эти загадочные манипуляции исправляют артефакты отрисовки в Хроме при сокрытии буфера
							$('<div></div>')
								.css('position', 'absolute')
								.css('top', position.top)
								.css('left', position.left)
								.width(width)
								.height(height)
								.appendTo($('<body>'))
								.remove();
						} else {
							$(window).one("mousedown", function (e) {
								closeBuffer(e);
							});
						}
					};

					$(window).one("mousedown", function (e) {
						closeBuffer(e);
					});
				}
			}

			$(window).mousedown(function (e) {
				if (e.which == 1 && !$(e.target).prop("draggable")) {
					if (e.target.nodeName == 'A' ||
						e.target.nodeName == 'BUTTON' ||
						e.target.nodeName == 'INPUT' ||
						e.target.nodeName == 'TEXTAREA') {
						return;
					}
					popupObjectBuffer.startTimer(e);
				}
			});

			$(window).mouseup(function (e) {
				if (e.which == 1)
					popupObjectBuffer.stopTimer();
			});

			// препятствие открытию буфера при выделении текста
			$(window).mousemove(function (e) {
				// вместо вызова stopTimer, т.к. очень много отладки идёт
				window.clearTimeout(popupObjectBuffer.timer);
			});
		}
	}

	/**
	 * Set URL fragment #ID
	 * @param {*} id fragment's ID
	 */
	const stateFragment = (id) => {
		const state = history.state;
		if (id > 0 && state) {
			const pos = state.href.indexOf('#');
			state.href = (pos < 0 ? state.href : state.href.substring(0, pos)) + "#" + id;
			history.replaceState(state, null, state.href)
		}
	}

	/**
	 * @returns Title area selector.
	 */
	const $title = () => {
		return $('#title > .status:visible h1.title');
	}

	/**
	 * @returns State area selector.
	 */
	const $state = () => {
		return $('#title > .status:visible > .wrap > .center');
	}

	/**
	 * Content block for the UI element.
	 * @param {*} el
	 */
	const $content = function (el) {
		while (el) {
			const parent = el.parentElement;
			if (parent && parent.id === 'content')
				return $(el);
			el = parent;
		}

		return $('#content > div:visible');
	}

	/**
	 * Close other tabs except active one
	 */
	const closeOthers = function() {
		$('#taskPanel').find('.btn-task').find('.ti-close').click();
	}

	/**
	 * Refresh current window
	 */
	const refreshCurrent = function() {
		const id = $('#activeContextMenu').data('activeid');
		$("#title > #" + id + ".status > .wrap > .left > .title h1.title").click();
	}

	// public functions
	this.$title = $title;
	this.$state = $state;
	this.$content = $content;
	this.debug = debug;
	this.menuItems = menuItems;
	this.initBuffer = initBuffer;
	this.contentLoad = contentLoad;
	this.followLink = followLink;
	this.removeCommandDiv = removeCommandDiv;
	this.stateFragment = stateFragment;
	this.closeOthers = closeOthers;
	this.refreshCurrent = refreshCurrent;
}

