define([
	'jsclass/class',
	'./WidgetService',
	'app/common/layout/manager',
	'app/common/layout/model',
	'app/common/datatable/component.datatable',
	'Shared/Base/ConfigureGrid',
	'Shared/Filter/filter',
	'Shared/Filter/PageFilterSortInfo',
	'Shared/Base/LoadMask',
	'Shared/Extensions/ScopedWorkspace',
	'Shared/Util/Widget/GenericHelpers',
	'./WidgetLoader',
	'./Util',
	'nunjucks',
	'Shared/Extensions/WindowTitleForDate',
	'async'
], function (
	Class, WidgetService, Manager, Model, Datatable, ConfigureGrid, Filter, PageFilterSortInfo,
	LoadMask, ScopedWorkspace, GenericHelpers, WidgetLoader, Util, nunjucks, WindowTitleForDate,
	Async
	) {
	'use strict';
	var DIR = 'Shared/WidgetFoundation/',
		$CacheMemory = {};
	return new Class(
		{
		extend: {
			EXPORT_TYPE: {
				DETACH: 'DETACH'
			},
			loader: function(settings) {
				return new WidgetLoader(this, settings);
			}
		},
		settings: {
			// default: { features: {} } - workspace and subworkspace, all tabs, main and detach
			//
			// workspace: { default: { default: { features: {} } } } - workspace, all tabs, main and detach
			// workspace: { default: { main: { features: {} } } } - workspace, all tabs, main
			// workspace: { reports: { default: { features: {} } } } - workspace, reports tab, main and detach
			// workspace: { reports: { main: { features: {} } } } - workspace, reports tab, main
			// workspace: { reports: { detach: { features: {} } } } - workspace, reports tab, detach
			//
			// subworkspace: { default: { features: {} } } - subworkspace, main and detach
			// subworkspace: { main: { features: {} } } - subworkspace, main
			// subworkspace: { detach: { features: {} } } - subworkspace, detach
			default: {
				title: 'Abstract Widget Title',
				features: {
					export: true,
					exportType: ['DETACH'],
					maximize: true,
					mininize: true,
					close: true,
					resize: true,
					drag: true
				},
				size: {
					width: 500,
					height: 400
				},
				listeners: {
					// format: eventName: function() {}, or eventName: [function() {}]
					// widgetInitialized: [],
					// widgetPassive: function() { console.info('just become passive'); }
					// available event types:
					// eventName - widget foundation event (see this.eventsMap)
					// widget:eventName (see this.frameworkEventsMap)
					// service:serviceName
				},
				// Methods, which will be passed to this[methodKey]
				methods: {},
				plugins: {
					// PluginPath: { parameters: 'here' }
				},
				internal: {
					workspaceChangeWorkaroundTimeout: 50,
					paths: {
						service: 'Service',
						view: ':text:' + DIR + 'View/AbstractWidget.html' // :text:, :require:, :service: or direct url (without html)
					},
					services: {
						getState: 'AUM/GetWidgetState',
						saveState: 'AUM/SetWidgetState',
						getStateNoId: 'User/GetWidgetStateWithoutId',
						saveStateNoId: 'User/SetWidgetStateWithoutId'
					}
				}
			}
		},
		// Fetched settings
		actualSettings: {},
		// State
		originalState: {},
		state: {},
		service: null,
		eventListeners: null,
		memory: null,
		plugins: null,
		// Widget
		initialized: false,
		id: null, // unique id
		widgetFrameworkPath: null, // framework internal, ie AssetsUnderManagement/AssetsUnderManagement:Assets Under Management
		widgetPath: null, // ie AssetsUnderManagement/AssetsUnderManagement
		widgetWorkspaceTab: null,
		widgetObject: null,
		widgetBodyContainerId: null,
		isWidgetDetached: null,
		widgetArgs: [],
		workspaceContext: null,
		spinner: null,
		// Mapping
		featuresMap: {},
		// WidgetFoundation events
		eventsMap: {
			settingsFetched: 'settingsFetched',
			widgetInitialized: 'widgetInitialized',
			viewInitialized: 'viewInitialized',
			stateLoaded: 'stateLoaded',
			statePersisting: 'statePersisting',
			exportDataToDetached: 'exportDataToDetached',
			detachedReceiveData: 'detachedReceiveData'
		},
		// Framework events
		frameworkEventsMap: {
			// Features
			onExportClicked: Model.WIDGET_EXPORT,
			onSettingsClicked: 'settings',
			onVisualizationClicked: Model.WIDGET_VISUALIZATION,
			onFilterClicked: 'filter',
			onRefreshClicked: 'refresh',
			onResized: Model.AFTER_RESIZE,
			onMaximized: Model.AFTER_MAXIMIZE,
			onCascaded: Model.AFTER_CASCADE,
			onDragged: Model.WIDGET_DRAG_COMPLETED,
			onClose: Model.WIDGET_CLOSE,
			// Detach
			onDetachedInitializing: Model.EXPORTED_VIEW_READY,
			onDetachedDataReceived: Model.EXPORTED_VIEW_DATA_EVENT,
			// Strange one-instance widget strategy events
			onWidgetPassivate: Model.VIEW_IN_PASSIVE_MODE,
			onWidgetActivate: Model.VIEW_IN_ACTIVE_MODE,
			onScopeChanged: ScopedWorkspace.SCOPE_CHANGE_EVENT,
			onUserPreferenceChanged: 'USER_PREFERENCES_SAVED'
		},
		// Work like localization for export items
		exportTypesMap: {
			DETACH: 'Detach'
		},
		workspacesIdMap: {
			"Home": 1,
			"Clients": 2,
			"Worklist": 3,
			"Markets": 4,
			"Resources": 5,
			"Trading": 6,
			"Services": 8,
			"Reports": 9,
			"Fixed Income": 10,
			"Back Office": 19,
			"Admin": 20,
//			"Home": 21,
//			"Home": 22,
			"Accounts": 23,
			"Documents": 24,
//			"Markets": 25,
			"Bill Pay": 26,
			"Credit Card Activity": 27
//			"Trading": 28,
		},
		initialize: function (settings, whom, isDetached, args, widgetModulePath) {
			var self = this;
			// Merge inherited values in the right way
			Util.forEach(['settings', 'featuresMap', 'eventsMap', 'exportTypesMap', 'frameworkEventsMap'], function(i, key) {
				var merged = {};
				Util.forEach(self.__eigen__().lookup(key), function(i, value) {
					merged = Util.Object.merge(merged, value);
				});
				self[key] = merged;
			});
			// Merge settings
			this.settings = Util.Object.merge(this.settings, settings);
			this.widgetFrameworkPath = widgetModulePath;
			this.widgetPath = widgetModulePath.split(':')[0];
			this.isWidgetDetached = !!isDetached;
			this.widgetArgs = args || [];
			(function (initSettings, nextStep) {
				// Define some settings manually
				if (!self.isWidgetDetached) {
					self.updateWorkspaceContext();
					setTimeout(function() {
						// don't work for clients tab
						// self.widgetWorkspaceTab = Manager.getWorkspaces()[Manager.getWorkspace()].name;
						self.widgetWorkspaceTab = $('#workspaceTabsHolder').find('.workspace.active').text();
						initSettings(nextStep);
					}, self.settings.default.internal.workspaceChangeWorkaroundTimeout);
				}
				// Receive settings from original widget
				else {
					var dummyWindow = new Model.WindowProperties(whom, self.widgetFrameworkPath);
					var dummyWidget = Manager.createWidget(dummyWindow);
					Manager.renderWidget(dummyWidget, function(event, postMessage) {
						if (self.frameworkEventsMap.onDetachedDataReceived == event) {
							if (postMessage.state) {
								self.originalState = Util.Object.clone(postMessage.state);
								self.state = Util.Object.clone(postMessage.state);
							}
							self.widgetWorkspaceTab = postMessage.widgetWorkspaceTab || 'Unknown';
							self.workspaceContext = postMessage.workspaceContext || {};
							(function(next) {
								if (!self.initialized) initSettings(next);
								else next();
							})(function() {
								nextStep(function() {
									self.fireEvent(self.eventsMap.detachedReceiveData, {
										postMessage: postMessage
									});
									if (postMessage.state) {
										self.fireEvent(self.eventsMap.stateLoaded, postMessage.state);
									}
								});
							});
						}
					}, '');
					// Prepare html to make it wonderful
					$('#' + dummyWidget.getId()).hide();
					$('body #centerpane').prepend($('
' +
							'' +
								'Exporting...' +
							'' +
						'
')
					);
					Manager.subscribeToEvent(self.frameworkEventsMap.onDetachedDataReceived, self.widgetFrameworkPath);
					Manager.sendToParentWindow(self.widgetFrameworkPath, self.frameworkEventsMap.onDetachedInitializing);
				}
			})(function(callback) {
				// Fetch settings from given hash
				self.actualSettings = self._fetchActualSettings(self.settings);
				// Store methods to use it as native
				Util.forEach(self.actualSettings.methods, function(methodKey, method) {
					if (self[methodKey]) throw new Error('Cannot override "' + methodKey + '"! Method must have unique name!');
					self[methodKey] = method.bind(self);
				});
				self.fireEvent(self.eventsMap.settingsFetched);
				// Initialize plugins
				if (!Util.Object.isEmpty(self.actualSettings.plugins)) {
					self.plugins = [];
					Async.each(Util.Object.getKeys(self.actualSettings.plugins), function(plugin, cb) {
						require([plugin], function(Plugin) {
							self.plugins[plugin] = new Plugin(self, self.actualSettings.plugins[plugin], cb);
						});
					}, callback);
				}
				else callback();
			}, function (listenersInitialized) {
				// Go next
				var windowProperties = new Model.WindowProperties(whom, self.widgetFrameworkPath);
				// Title
				if (self.isWorkspace()) windowProperties.setTitle(self.actualSettings.title);
				else windowProperties.setTitle(self.actualSettings.title + ' for ' + self.workspaceContext.display);
				// Features
				var features = [];
				for (var feature in self.featuresMap) {
					if (self.featuresMap.hasOwnProperty(feature)) {
						if (self.actualSettings.features[feature]) features.push(self.featuresMap[feature]);
					}
				}
				if (features.length) windowProperties.showDefaultMenuOptions(features);
				windowProperties.showDetachOption(self.actualSettings.features.export);
				// If not detached, then we customize some window properties
				if (!self.isWidgetDetached) {
					windowProperties.isMaximizable(self.actualSettings.features.maximize);
					windowProperties.isMinimizable(self.actualSettings.features.minimize);
					windowProperties.isClosable(self.actualSettings.features.close);
					windowProperties.isResizable(self.actualSettings.features.resize);
					windowProperties.isDraggable(self.actualSettings.features.drag);
				}
				// Size
				if (!self.isWidgetDetached) {
					windowProperties.setWidth(self.actualSettings.size.width);
					windowProperties.setHeight(self.actualSettings.size.height);
					windowProperties.setMinSize(self.actualSettings.size.width, self.actualSettings.size.height);
				}
				else {
					var viewPortDims = viewport();
					windowProperties.setWidth(viewPortDims.width);
					windowProperties.setHeight(viewPortDims.height);
				}
				self.widgetObject = Manager.createWidget(windowProperties);
				// And here we go
				self.stackInitServices(function() {
					self.stackInitEventListeners(function() {
						listenersInitialized && listenersInitialized();
						self.stackInitView(function() {
							if (self.isWidgetDetached) self.getNode().hide();
							// Subworkspace title
							if (self.isSubworkspace()) {
								self.appendToTitle('for ' + self.workspaceContext.display);
							}
							self.showSpinner();
							if (self.isWidgetDetached) {
								$('#loading').remove();
								self.getNode().show();
							}
							self.stackInitState(function() {
								self.hideSpinner();
								self.initialized = true;
								self.fireEvent(self.eventsMap.widgetInitialized);
							});
						});
					});
				});
			});
		},
		/**
		 * Fetch settings for current workspace/subworkspace from all settings (smart merge)
		 *
		 * @param allSettings
		 * @returns {{}}
		 * @private
		 */
		_fetchActualSettings: function(allSettings) {
			var settings = Util.Object.clone(allSettings),
				paths = ['default'];
			if (this.isWorkspace()) {
				// Defaults
				paths.push('workspace.default.default');
				// With tab
				var currentTab = this.getCurrentTabName().toLowerCase();
				paths.push('workspace.' + currentTab + '.default');
				// With widget state (main or detach)
				paths.push('workspace.default.' + (!this.isDetached() ? 'main' : 'detach'));
				paths.push('workspace.' + currentTab + '.' + (!this.isDetached() ? 'main' : 'detach'));
			}
			else {
				// Defaults
				paths.push('subworkspace.default');
				// With widget state (main or detach)
				paths.push('subworkspace.' + (!this.isDetached() ? 'main' : 'detach'));
			}
			var fetchedSettings = {};
			for (var i = 0; i < paths.length; i++) {
				fetchedSettings = Util.Object.merge(fetchedSettings, Util.Object.getPath(settings, paths[i], {}));
			}
			return fetchedSettings;
		},
		// --- Stack
		/**
		 * Initialize widget services
		 * @private
		 */
		stackInitServices: function(next) {
			var self = this;
			require([this._determineServicesPath()], function (Service) {
				self.service = (new Service).initializeServices(self.getWidgetPath());
				// Listen services as events
				Util.forEach(self.service.servicesConfig, function(serviceName) {
					self.service.listenService(serviceName, function(data) {
						if (!self.active) return;
						self.fireEvent('service:' + serviceName, data);
					});
				});
				next();
			});
		},
		/**
		 * Subscribe to events
		 * @private
		 */
		stackInitEventListeners: function(next) {
			var self = this;
			Util.forEach(this.actualSettings.listeners, function(eventName) {
				var events = self.actualSettings.listeners[eventName];
				if ('function' == typeof events) events = [events];
				for (var i = 0; i < events.length; i++) {
					self.listenEvent(eventName, events[i]);
				}
			});
			next();
		},
		stackInitState: function(next) {
			if (!this.isWidgetDetached) this.loadState(next);
			else next();
		},
		// --- Generic
		getNodeId: function () {
			return this.widgetObject.getId();
		},
		getNode: function () {
			return $('#' + this.getNodeId());
		},
		getWidgetObject: function() {
			return this.widgetObject;
		},
		getNodeBodyContainerId: function() {
			return this.widgetBodyContainerId;
		},
		getNodeBodyContainer: function() {
			return $('#' + this.widgetBodyContainerId);
		},
		getWidgetFrameworkPath: function() {
			return this.widgetFrameworkPath;
		},
		getWidgetPath: function () {
			return this.widgetPath;
		},
		/**
		 * For require.js usage
		 * @param {Boolean} [trailingSlash=true]
		 * @returns {String}
		 */
		getWidgetDir: function(trailingSlash) {
			if (undefined === trailingSlash) trailingSlash = true;
			return this.widgetPath.replace(/\/[^/]+$/, '') + (trailingSlash ? '/' : '');
		},
		getSettings: function () {
			return this.actualSettings;
		},
		getAllSettings: function() {
			return this.settings;
		},
		isInitialized: function() {
			return this.initialized;
		},
		// --- Environment
		isDetached: function() {
			return this.isWidgetDetached;
		},
		getArgs: function() {
			return this.widgetArgs;
		},
		getWorkspaceContext: function() {
			return this.workspaceContext;
		},
		updateWorkspaceContext: function() {
			if (this.isWidgetDetached) throw new Error('Cannot update workspace context for detached widget');
			this.workspaceContext = ScopedWorkspace.getScopeOfCurrentWorkspace();
			return this;
		},
		isWorkspace: function() {
			return this.workspaceContext === ScopedWorkspace.SCOPE_UNDEFINED
		},
		isSubworkspace: function() {
			return !this.isWorkspace();
		},
		getCurrentTabName: function() {
			return this.widgetWorkspaceTab;
		},
		// --- Events
		listenEvent: function (event, handler) {
			if (!this.eventListeners) this.eventListeners = {};
			var self = this;
			var eventModificators = {
				once: function(event, handler) {
					var extraHandler = function() {
						// Remove event
						if (~self.eventListeners[event].indexOf(extraHandler)) {
							delete self.eventListeners[event][self.eventListeners[event].indexOf(extraHandler)];
						}
						// Call original handler
						handler.apply(this, arguments);
					};
					return extraHandler;
				}
			};
			var extraEventMatch = event.match(/:([^:]+)$/);
			if (extraEventMatch && eventModificators[extraEventMatch[1]]) {
//				if (!eventModificators[extraEventMatch[1]]) throw new Error('Modificator ' + extraEventMatch[1] + ' is unknown');
				// Remove modificator from event
				event = event.replace(/:([^:]+)$/, '');
				handler = eventModificators[extraEventMatch[1]](event, handler);
			}
			if (!this.eventListeners[event]) this.eventListeners[event] = [];
			this.eventListeners[event].push(handler);
			return this;
		},
		fireEvent: function(event, args) {
			if (!this.eventListeners) this.eventListeners = {};
			// Currently used for widget final instance handlers (settings.listeners)
			if (this.eventListeners[event]) {
				for (var i = 0; i < this.eventListeners[event].length; i++) {
					this.eventListeners[event][i].call(this, args);
				}
			}
			// Internal event methods
			var methodEventHandler = event.replace(/^[^:]+:/, '') + 'EventHandler';
			if (this[methodEventHandler] && 'function' == typeof this[methodEventHandler]) this[methodEventHandler].call(this, args);
			return this;
		},
		// --- Event handlers
		onCloseEventHandler: function() {
			this.saveStateIfChanged();
		},
		onExportClickedEventHandler: function() {
			var self = this;
			require(["Shared/Extensions/Export"], function (_export) {
				var items = [];
				Util.forEach(self.actualSettings.features.exportType, function(i, type) {
					if (self.exportTypesMap[type]) {
						items.push({
							name: self.exportTypesMap[type],
							callback: self.onExportingEventHandler.bind(self),
							id: Util.getUniqueId(),
							type: type
						});
					}
				});
				_export.createExportmenu(self.getNodeId(), self.getNodeBodyContainerId(), items);
			});
		},
		onExportingEventHandler: function(item) {
			var self = this;
			switch (item.type) {
				case 'DETACH':
					Manager.detachWindow(this.widgetFrameworkPath, function(event) {
						// Export general values
						if (self.frameworkEventsMap.onDetachedInitializing == event) {
							var postMessage = {
								state: self.state,
								widgetWorkspaceTab: self.widgetWorkspaceTab,
								workspaceContext: self.workspaceContext
							};
							self.fireEvent(self.eventsMap.exportDataToDetached, {
								postMessage: postMessage,
								sendMethod: this.sendMessage.bind(this)
							});
							this.sendMessage(postMessage);
						}
//						console.info('export layoutEventHandler: ', arguments, this);
					});
					break;
				default:
					console.error('Unknown export type: ' + item.type);
			}
		},
		onScopeChangedEventHandler: function() {
			this.updateWorkspaceContext().appendToTitle('for ' + this.workspaceContext.display);
		},
		// --- Services
		getService: function() {
			return this.service;
		},
		getServices: function() {
			return WidgetService;
		},
		callOwnService: function(service, parameters, callback) {
			this.service.call(this.service.getRealServiceName(service), parameters, callback);
			return this;
		},
		_determineServicesPath: function () {
			return this.getWidgetDir() + this.actualSettings.internal.paths.service;
		},
		// --- State
		getFromState: function (path, def) {
			return Util.Object.getPath(this.state, path, def);
		},
		setToState: function (path, value) {
			Util.Object.setPath(this.state, path, value);
			return this;
		},
		loadState: function (callback) {
			var requestCallback = function (data) {
				var state = data || {};
				this.state = state;
				this.originalState = Util.Object.clone(state);
				this.fireEvent(this.eventsMap.stateLoaded, state);
				callback(state);
			}.bind(this);
			if (this.actualSettings.stateId) {
				console.warn('stateId is deprecated, but used in "' + this.widgetPath + '" - ' + this.actualSettings.stateId);
				WidgetService.directRequest(this.actualSettings.internal.services.getState,
					{ id: this.actualSettings.stateId }, 'GET', 'json', function(data) {
						requestCallback(data && data.Preference ? data.Preference : {});
					});
			}
			else {
				WidgetService.directRequest(this.actualSettings.internal.services.getStateNoId,
					{
						widgetPath: this.widgetFrameworkPath,
						workspaceId: Util.Object.getPath(this.workspacesIdMap, this.getCurrentTabName(), this.workspacesIdMap.Home),
						isSubworkspace: this.isSubworkspace()
					}, 'GET', 'json', function(data) {
						if (data && data.json) data = data.json;
						requestCallback(data);
					}, true);
			}
			return this;
		},
		isStateChanged: function () {
			// skip whitespace from comparison
			return JSON.stringify(this.originalState).replace(/\s+/g, '') != JSON.stringify(this.state).replace(/\s+/g, '');
		},
		saveState: function (callback) {
			var self = this;
			this.fireEvent(this.eventsMap.statePersisting, this.state);
			var requestCallback = function () {
				self.originalState = Util.Object.clone(self.state);
				if (callback) callback();
			}.bind(this);
			if (this.actualSettings.stateId) {
				console.warn('stateId is deprecated, but used in "' + this.widgetPath + '" - ' + this.actualSettings.stateId);
				WidgetService.directRequest(this.actualSettings.internal.services.saveState,
					{ id: this.actualSettings.stateId, Preference: this.state }, 'POST', 'json', requestCallback);
			}
			else {
				WidgetService.directRequest(this.actualSettings.internal.services.saveStateNoId,
					{
						widgetPath: this.widgetFrameworkPath,
						workspaceId: Util.Object.getPath(this.workspacesIdMap, this.getCurrentTabName(), this.workspacesIdMap.Home),
						isSubworkspace: this.isSubworkspace(),
						json: this.state
					}, 'POST', 'json', function(data) {
						if (!data.Response) alert('Error: state wasn`t persisted! { Response: false }');
						requestCallback();
					});
			}
			return this;
		},
		saveStateIfChanged: function(callback) {
			if (!this.isStateChanged()) { if (callback) callback(); }
			else this.saveState(callback);
			return this;
		},
		// --- View
		loadView: function (callback) {
			this.loadHTML(this.actualSettings.internal.paths.view, callback);
			return this;
		},
		loadHTML: function(loadUrl, callback) {
			loadUrl = loadUrl.replace(/{WidgetFoundation}\/?/, DIR);
			// Service loading
			if (-1 != loadUrl.indexOf(':service:')) {
				WidgetService.call(loadUrl.replace(':service:'), callback);
			}
			// Require
			else if (-1 != loadUrl.indexOf(':require:')) {
				require([loadUrl.replace(':require:')], callback);
			}
			// Require-text
			else if (-1 != loadUrl.indexOf(':require-text:')) {
				require(['Lib/Vendor/RequireJS/text!' + loadUrl.replace(':require-text:', '')], callback);
			}
			// Text
			else if (-1 != loadUrl.indexOf(':text:')) {
				nunjucks.render(loadUrl.replace(':text:', '')
					// Relative to widget path
					.replace(new RegExp('^\./'), this.widgetPath.replace(/[^/]+$/, '') + '/'),
					function (err, html) {
						if (err) {
							console.error(err);
						}
						else setTimeout(function() { callback(html); }, 0);
					});
			}
			// URL request
			else {
				WidgetService.directRequest(loadUrl, {}, 'GET', 'html', function (template) {
					callback(template);
				}.bind(this));
			}
			return this;
		},
		// --- Spinner
		getSpinner: function() {
			if (!this.spinner) {
				if (this.widgetBodyContainerId) {
					this.spinner = new LoadMask.LoadMask(this.getNodeBodyContainer().parent().prop('id'));
				}
				else {
					console.warn('Cannot construct spinner for ' + this.widgetPath + '! Node id is unknown yet!');
				}
			}
			return this.spinner;
		},
		showSpinner: function() {
			if (!this.spinnerCallCount) this.spinnerCallCount = 0;
			// Count how much it called to show
			this.spinnerCallCount++;
			if (1 == this.spinnerCallCount) {
				var spinner = this.getSpinner();
				if (spinner) spinner.start();
			}
			return this;
		},
		hideSpinner: function() {
			if (!this.spinnerCallCount) this.spinnerCallCount = 0;
			// Prevent from going under zero number
			if (this.spinnerCallCount > 0) this.spinnerCallCount--;
			if (0 == this.spinnerCallCount) {
				var spinner = this.getSpinner();
				if (spinner) spinner.stop();
			}
			return this;
		},
		// --- Memory cache
		getFromGlobalCacheOrExecute: function(handler, key, callback) {
			if ('function' == typeof key) {
				callback = key;
				key = undefined;
			}
			if (undefined === key) key = Util.md5(handler.toString());
			(function(handle) {
				// Already cached
				if ($CacheMemory[key]) handle($CacheMemory[key]);
				// Need to be cached
				else handler(function(result) { callback($CacheMemory[key] = result); });
			})(callback);
		},
		getFromMemory: function(path, def) {
			if (!this.memory) this.memory = {};
			return Util.Object.getPath(this.memory, path, def);
		},
		setToMemory: function(path, value) {
			if (!this.memory) this.memory = {};
			Util.Object.setPath(this.memory, path, value);
			return this;
		},
		// --- Title & header
		/**
		 * Set widget title
		 * @param {String} title
		 */
		setTitle: function(title) {
			title = (title + '').trim();
			if (title) {
				Manager.updateWidgetTitle(this.widgetFrameworkPath, title);
				this.lastWidgetTitle = title;
			}
			return this;
		},
		getTitle: function() {
			return this.lastWidgetTitle;
		},
		/**
		 * Append something to widget title. If empty value passed (or no value), then we restore original title
		 * @param {String} [text='']
		 * @param {Boolean} [spaceBefore=true]
		 * @returns this
		 */
		appendToTitle: function(text, spaceBefore) {
			if (!spaceBefore) spaceBefore = true;
			this.setTitle(this.actualSettings.title + (!text ? '' : (!spaceBefore ? text : ' ' + text)));
			return this;
		},
		setAsOfDate: function(asOfDate) {
			if (asOfDate) WindowTitleForDate.addDateToTitle(this.getNodeId(), asOfDate, true);
			else WindowTitleForDate.removeDateFromTitle(this.getNodeId());
			this.lastAsOfDate = asOfDate;
			return this;
		},
		getAsOfDate: function() {
			return this.lastAsOfDate || '';
		}
	});
});