/**
 * The mask class is used to lock the interface for long asynchronous operations.
 * In order to put a mask on the container, you need to call the show method and pass the configuration object
 * in which the selector property specifies the selector on which the DOM element will be found.
 * If the selector is not specified, the mask will be applied to the Viewport.
 * For example, to display a mask in a container with id = "maskContainer", run the following code:
 *
 *  var maskId = Terrasoft.Mask.show({
 *   selector: "#maskContainer"
 *  });
 *
 * To hide the mask, call:
 *  Terrasoft.Mask.hide(maskId);
 *
 * The remaining parameters are optional and assume the default values.
 */
Ext.define("Terrasoft.controls.Mask", {
	alternateClassName: "Terrasoft.Mask",
	extend: "Terrasoft.BaseObject",
	singleton: true,

	/**
  * Mask delay time
  * @type {Number}
  * @private
  */
	defaultTimeout: 500,

	/**
  * Transparency of the mask in the range from 0 to 1
  * @type {Float}
  * @private
  */
	defaultOpacity: 0.4,

	/**
  * The background color of the mask
  * @type {String}
  * @private
  */
	defaultBackgroundColor: "#ffffff",

	/**
  * The name of the mask container class
  * @type {String}
  * @private
  */
	maskOpacityClass: "ts-mask-opacity",

	/**
  * Object for storing masks by identifier.
  * @type {Object}
  * @private
  */
	storage: {},

	/**
  * The string displayed at startup.
  * @type {String}
  * @private
  */
	defaultCaption: Terrasoft.Resources.Controls.Mask.Caption,

	/**
  * Adds the stylized display of the container where the spinner and header are located.
  * @type {Boolean}
  * @private
  */
	defaultFrameVisible: true,

	/**
  * @private
  */
	_defaultShowSpinner: true,

	/**
  * Template for the mask element.
  * Specifies the frame of the control, to which the content is  displayed afterwards.
  * @type {Array}
  * @private
  */
	tpl: [
	/*jshint white:false */
	"<div class=\"ts-mask-container\"", "<tpl if=\"position\">", "style=\"{position}\"", "</tpl> >", "<div class=\"{maskOpacityClass}\" style=\"{opacityStyle}\"></div>", "<tpl if=\"showSpinnerEl\">", "<div class=\"{maskSpinnerClass}\">", "<div class=\"ts-mask-frame\">", "<div class=\"ts-mask-spinner\">", "{progressSpinnerHtml}", "</div>", "<div class=\"ts-mask-spinner-caption\">", "<tpl>{caption}</tpl>", "</div>", "</div>", "</div>", "</tpl>", "</div>"
	/*jshint white:true */
	],

	/**
  * The method displays a mask and returns the mask identifier.
  * @param {Object} [config] Mask options.
  * @param {String} config.selector Selector to find the DOM element on which the mask will be applied.
  * If the selector is not specified, the mask is superimposed on the Viewport.
  * @param {Number} config.timeout Delay before displaying the mask.
  * @param {Number} config.showHidden Show a transparent mask before the timeout occurs.
  * @param {Number} config.opacity The degree of transparency of the mask in the range from 0 to 1.
  * @param {String} config.backgroundColor The background color of the mask fill.
  * @param {String} config.clearMasks Clear old masks if exist.
  * @param {String} config.showSpinner Show spinner.
  * @param {String} config.showSpinnerEl Show spinner element.
  * @return {String} The mask identifier.
  */
	show: function (config) {
		var self = this;
		config = config || {};
		var selector = config.selector;
		if (!selector) {
			selector = config.selector = "body";
		}
		if (config.clearMasks) {
			this.clearMasks(selector);
		} else if (this.isMaskExist(selector)) {
			return null;
		}
		var mask = this.createMask(config);
		mask.containerEl.set({ "maskState": "visible" });
		if (config.showHidden) {
			var showOpacity = mask.opacity;
			mask.opacity = 0;
			this.renderMask(mask.id);
			var maskOpacityEl = mask.el.child("div." + this.maskOpacityClass);
			mask.timeoutId = setTimeout(function () {
				maskOpacityEl.setOpacity(showOpacity);
			}, mask.timeout);
		} else {
			if (mask.timeout) {
				mask.timeoutId = setTimeout(function () {
					self.renderMask(mask.id);
				}, mask.timeout);
			} else {
				self.renderMask(mask.id);
			}
		}
		return mask.id;
	},

	/**
  * The method deletes the mask by its identifier.
  * @param {String} maskId
  */
	hide: function (maskId) {
		var mask = this.storage[maskId];
		if (mask == null) {
			return;
		}
		var containerEl = mask.containerEl;
		containerEl.set({ "maskState": "none" });
		if (!mask.rendered || mask.showHidden) {
			clearTimeout(mask.timeoutId);
		}
		if (mask.progressSpinner && !mask.progressSpinner.destroyed) {
			mask.progressSpinner.destroy();
		}
		if (mask.rendered) {
			mask.el.destroy();
			var parentStylePosition = mask.parentStylePosition;
			if (parentStylePosition !== "absolute" && parentStylePosition !== "relative" && parentStylePosition !== "fixed") {
				containerEl.setStyle("position", "");
			}
		}
		delete this.storage[maskId];
	},

	/**
  * Clear old masks if exist.
  * @param {String} selector Selector container element.
  */
	clearMasks: function (selector) {
		Terrasoft.each(this.storage, function (mask) {
			if (mask.containerEl.is(selector)) {
				this.hide(mask.id);
			}
		}, this);
	},

	/**
  * Clears all masks.
  */
	clearAllMasks: function () {
		Terrasoft.each(this.storage, function (mask) {
			this.hide(mask.id);
		}, this);
	},

	/**
  * The method creates a mask and returns its configuration.
  * @throws {Terrasoft.UnknownException}
  * Throws an exception if more than one container is found.
  * @throws {Terrasoft.NullOrEmptyException}
  * throws an exception if no containers are found.
  * @param {Object} config is passed from the show method {Terrasoft.controls.Mask.show}
  * @return {Object} the configuration of the created mask.
  * @private
  */
	createMask: function (config) {
		var defaultMaskConfig = {
			timeout: this.defaultTimeout,
			opacity: this.defaultOpacity,
			backgroundColor: this.defaultBackgroundColor,
			caption: this.defaultCaption,
			frameVisible: this.defaultFrameVisible,
			showSpinner: this._defaultShowSpinner
		};
		var maskConfig = Ext.applyIf(config, defaultMaskConfig);
		var maskId = Ext.id();
		var elements = Ext.select(maskConfig.selector);
		if (elements.getCount() > 1) {
			throw new Terrasoft.UnknownException({
				message: Terrasoft.Resources.Controls.Mask.DuplicateContainerException
			});
		}
		var containerEl = elements.item(0);
		if (!containerEl) {
			throw new Terrasoft.NullOrEmptyException({
				message: Terrasoft.Resources.Exception.NullOrEmptyException
			});
		}
		var mask = Ext.apply({
			containerEl: containerEl,
			id: maskId
		}, maskConfig);
		if (config.showSpinner) {
			mask.progressSpinner = mask.progressSpinner || Ext.create("Terrasoft.ProgressSpinner", {
				extraComponentClasses: "ts-mask-spinner"
			});
		} else {
			this._destroyProgressSpinner(mask);
		}
		this.storage[maskId] = mask;
		return mask;
	},

	/**
  * @private
  */
	_destroyProgressSpinner: function (mask) {
		if (mask.progressSpinner) {
			mask.progressSpinner.destroy();
		}
	},

	/**
  * The method checks whether the mask is superimposed on the element by the selector being passed.
  * @param {String} selector
  * @return {Boolean}
  * @private
  */
	isMaskExist: function (selector) {
		var isMaskExist = false;
		Terrasoft.each(this.storage, function (mask) {
			if (mask.containerEl.is(selector)) {
				isMaskExist = true;
				return false;
			}
		});
		return isMaskExist;
	},

	/**
  * The method displays the mask by the transmitted identifier.
  * @param {String} maskId mask identifier.
  * @private
  */
	renderMask: function (maskId) {
		var mask = this.storage[maskId];
		var containerEl = mask.containerEl;
		var parentStylePosition = mask.parentStylePosition = containerEl.getStyle("position");
		if (parentStylePosition !== "absolute" && parentStylePosition !== "relative" && parentStylePosition !== "fixed") {
			containerEl.setStyle("position", "relative");
		}
		var tpl = mask.tpl = new Ext.XTemplate(this.tpl.join(""), {});
		var opacity = mask.opacity;
		var styleConfig = {
			"opacity": opacity,
			"backgroundColor": mask.backgroundColor
		};
		if (Ext.isIE9m) {
			styleConfig.filter = "alpha(opacity=" + opacity * 100 + ")";
		}
		var maskSpinnerClass = "ts-mask-spinner-wrap";
		if (mask.frameVisible) {
			maskSpinnerClass += " ts-mask-spinner-frame-visible";
		}
		var opacityStyle = Ext.DomHelper.generateStyles(styleConfig);
		var position = containerEl.dom.nodeName === "BODY" ? "position: fixed" : "";
		var progressSpinnerHtml = mask.progressSpinner && mask.progressSpinner.generateHtml();
		var showSpinnerEl = mask.showSpinnerEl === false ? false : true;
		var maskEl = mask.el = tpl.append(containerEl, {
			opacityStyle: opacityStyle,
			progressSpinnerHtml: progressSpinnerHtml,
			caption: mask.caption,
			position: position,
			showSpinnerEl: showSpinnerEl,
			maskSpinnerClass: maskSpinnerClass,
			maskOpacityClass: this.maskOpacityClass
		}, true);
		maskEl.on("click", this.onMaskElClick);
		mask.rendered = true;
	},

	/**
  * The method intercepts the click handler by mask and stops the event propagation.
  * @param {Object} e Event
  * @private
  */
	onMaskElClick: function (e) {
		e.stopPropagation();
	}
});