/*!
 * UI development toolkit for HTML5 (OpenUI5)
 * (c) Copyright 2009-2016 SAP SE or an SAP affiliate company.
 * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
 */

// Provides inactive support for controls
sap.ui.define(['jquery.sap.global', 'sap/ui/core/Element', 'sap/ui/core/Control'],
	function(jQuery, Element, Control) {
		"use strict";

		/**
		 * @class Mixin for Controls which enables stashing of controls declaratively in XMLViews
		 *
		 * Two new SpecialSettings and belonging methods are added to the controls prototype. <code>stashed</code> Controls are not
		 * created and added to the Control tree, but remain uncreated. Although they are not instances of their actual
		 * class, a surrogate is created which serves as id holder. That means it is availyble with <code>sap.ui.getCore().byId</code>
		 * and unstashable. Currently this is a one-time operation. A once unstashed control cannot be re-stashed again.
		 *
		 * Parts of the code here are commented out, as the SpecialSetting <code>stashed</code> may change to be Property lateron.
		 *
		 * @name sap.ui.core.StashedControlSupport
		 * @since 1.35
		 * @private
		 */
		var StashedControlSupport = {},

			/**
			 * inactive controls registry
			 * @private
			 */
			stashedControls = {},

			/**
			 * @class surrogate for controls - serves as id holder
			 *
			 * Surrogate control which serves as id holder for its actual Control. The implementation uses an element for
			 * economic reasons. The constructor should not be called directly, see {@link sap.ui.core.StashedControlSupport.createStashedControl}
			 *
			 * @name sap.ui.core.StashedControlSupport
			 * @since 1.35
			 * @private
			 */
			StashedControl = Element.extend("sap.ui.core._StashedControl", {
				constructor: function(sId, mSettings) {
					Element.apply(this, arguments);
					// stashed is always true for instances of StashedControl
					mSettings.stashed = true;
					jQuery.sap.extend(this, mSettings);
					this._stash(mSettings.sParentId, mSettings.sParentAggregationName);
					return this;
				},
				metadata: {
					specialSettings : {
						/**
						 * the stashed state of the control
						 */
						stashed: { type: 'boolean', visibility: 'hidden' },
						/**
						 * id of the actual parent of the control (virtual control tree position)
						 */
						sParentId: { type: 'string', visibility: 'hidden' },
						/**
						 * name of the aggregation in which the actual control would be placed
						 */
						sParentAggregationName: { type: 'string', visibility: 'hidden' },
						/**
						 * hook for the later creation, which should return the actual control
						 */
						fnCreate: { type: 'function', visibility: 'hidden' }
					}
				}
			});

		/**
		 * adding the Surrogate to the control tree should not be allowed
		 *
		 * @private
		 */
		StashedControl.prototype.setParent = function() {
			jQuery.sap.log.error("Cannot set parent on a StashedControl", this.getId());
		};

		/**
		 * cloning requires special treatment
		 *
		 * @return {sap.ui.core._StashedControl} the clone
		 * @private
		 */
		StashedControl.prototype.clone = function() {
			var c = Element.prototype.clone.apply(this, arguments);
			c.stashedAlias = this.stashedAlias;
			stashedControls[c.getId()] = c;
			return c;
		};

		/**
		 * destruction requires special treatment
		 *
		 * @private
		 */
		StashedControl.prototype.destroy = function() {
			delete stashedControls[this.getId()];
			Element.prototype.destroy.apply(this, arguments);
		};

		// enable StashedControlSupport for surrogates
		mixInto(StashedControl, true);

		/**
		 * Mixin function to enhance control functionality
		 *
		 * @name sap.ui.core.StashedControlSupport.mixInto
		 * @param {function} fnClass the class to be enhanced
		 * @param {boolean} [bDefaultValue=true] default value for the stashed setting in the stashed control
		 *
		 * @private
		 */
		StashedControlSupport.mixInto = function(fnClass, bDefaultValue /*=true*/) {
			jQuery.sap.assert(!fnClass.getMetadata().hasProperty("stashed"), "StashedControlSupport: fnClass already has property 'active', sideeffects possible", fnClass.getMetadata().getName());
			jQuery.sap.assert(!fnClass.prototype.setStashed, "StashedControlSupport: fnClass already has method 'setStashed', sideeffects possible", fnClass.getMetadata().getName());
			mixInto(fnClass, bDefaultValue);
		};

		// private function without validity checks
		function mixInto(fnClass, bDefaultValue) {
			// add the properties
			fnClass.getMetadata().addSpecialSetting("stashed", {type: "boolean", defaultValue: !!bDefaultValue});

			// mix the required methods into the target fnClass
			fnClass.prototype.setStashed = function(bStashed) {
				if (this.stashed === true && !bStashed) {
					if (this.sParentId) {
						var oControl = unstash(this, sap.ui.getCore().byId(this.sParentId));
						// we need to set the property to the stashed control
						oControl.stashed = false;
						return;
					}
				} else if (bStashed) {
					jQuery.sap.log.warning("Cannot re-stash a control", this.getId());
				}
			};

			fnClass.prototype.getStashed = function() {
				return this.stashed;
			};

			var fnDestroy = fnClass.prototype.destroy;
			fnClass.prototype.destroy = function() {
				delete stashedControls[this.getId()];
				fnDestroy.apply(this, arguments);
			};

			fnClass.prototype._stash = function(sParentId, sParentAggregationName) {
				// for later unstash these parent infos have to be kept
				this.sParentId = sParentId;
				this.sParentAggregationName = sParentAggregationName;
				stashedControls[this.getId()] = this;
			};
		}

		function unstash(oControl, oParent) {
			if (oControl instanceof StashedControl) {
				// remember activation function and parent aggregation name of surrogate
				var aControls, Component, oOwnerComponent,
					fnCreate = oControl.fnCreate,
					sParentAggregationName = oControl.sParentAggregationName;
				// destroy obsolete surrogate control - free the id
				oControl.destroy();

				// as the runAsOwner context is missing here we need to call it
				Component = sap.ui.require("sap/ui/core/Component");
				oOwnerComponent = Component && Component.getOwnerComponentFor(oParent);
				if (oOwnerComponent) {
					aControls = oOwnerComponent.runAsOwner(fnCreate);
				} else {
					aControls = fnCreate();
				}

				// call hook to create the actual control (multiple controls in case of fragment)
				aControls.forEach(function(c) {
					oParent.getMetadata().getAggregation(sParentAggregationName).add(oParent, c);
				});
			}
			delete stashedControls[oControl.getId()];
			return oControl;
		}

		function getStashedControls(bAsInstance, sParentId) {
			var aStashedChildren = [];
			for (var sId in stashedControls) {
				var vInstanceOrId = bAsInstance ? stashedControls[sId] : stashedControls[sId].getId();
				if (!sParentId || stashedControls[sId].sParentId === sParentId) {
					aStashedChildren.push(vInstanceOrId);
				}
			}
			return aStashedChildren;
		}

		/**
		 * Gets the ids of StashedControls
		 *
		 * @name sap.ui.core.StashedControlSupport.getStashedControlIds
		 * @param {string} [sParentId] if set only StashedControlIds for a specific parent are returned
		 * @return {string[]} array with the ids of the StashedControls
		 *
		 * @private
		 */
		StashedControlSupport.getStashedControlIds = function(sParentId) {
			return getStashedControls(false, sParentId);
		};

		/**
		 * Gets the instances of StashedControls
		 *
		 * @name sap.ui.core.StashedControlSupport.getStashedControlIds
		 * @param {string} [sParentId] if set only StashedControls for a specific parent are returned
		 * @return {Control[]} array with the the StashedControls
		 *
		 * @private
		 */
		StashedControlSupport.getStashedControls = function(sParentId) {
			return getStashedControls(true, sParentId);
		};

		/**
		 * StashedControl factory function
		 *
		 * @name sap.ui.core.StashedControlSupport.createStashedControl
		 * @param {string} sId id of the actual control the StashedControl serves as surrogate for
		 * @param {object} mSettings the settings object
		 * @param {string} mSettings.sParentId id of the actual parent of the control (virtual control tree position)
		 * @param {string} mSettings.sParentAggregationName name of the aggregation in which the actual control would be placed
		 * @param {string} mSettings.fnCreate hook for the later creation, which should return the actual control
		 * @return {sap.ui.core._StashedControl} the StashedControl
		 *
		 * @private
		 */
		StashedControlSupport.createStashedControl = function(sId, mSettings) {
			return new StashedControl(sId, mSettings);
		};

		return StashedControlSupport;

	});
