/*!
 * ${copyright}
 */

// Provides class sap.ui.base.ManagedObjectObserver.
sap.ui.define([
	'sap/ui/base/Object',
	'sap/ui/base/ManagedObject',
	'sap/ui/base/EventProvider',
	"sap/base/util/array/uniqueSort",
	'sap/base/util/deepExtend'
], function(BaseObject, ManagedObject, EventProvider, uniqueSort, deepExtend) {
	"use strict";

	/**
	 * Constructor for a new ManagedObjectObserver.
	 *
	 * @classdesc
	 * Use the <code>ManagedObjectObserver</code> to get notified when properties, aggregations or associations of a
	 * <code>ManagedObject</code> instance have changed.
	 *
	 * Use the {@link #observe} method to add instances of ManagedObject that should be observed or to enhance
	 * the set of observed properties, aggregations etc. for an already observed instance.
	 *
	 * Use the {@link #unobserve} method to stop observing an instance of ManagedObject or to reduce the set of
	 * observed properties, aggregations etc. for an observed instance.
	 *
	 * Use the {@link #disconnect} method to completely stop observing all instances of ManagedObject hat previously
	 * had been added to this observer.
	 *
	 * The only parameter to the constructor is a function <code>fnCallback</code> which will be called for every
	 * observed change. Depending on the type of the change, different change objects are passed to the callback:
	 *
	 * <h4>Property Change</h4>
	 * {string}
	 *      change.name the name of the property that changed<br>
	 * {string}
	 *      change.type 'property'<br>
	 * {object}
	 *      change.object the managed object instance on which the change occurred<br>
	 * {any}
	 *      change.old the old value<br>
	 * {any}
	 *      change.current the new value<br>
	 *
	 * <h4>Aggregation Change</h4>
	 * {string}
	 *      change.name the name of the aggregation that changed<br>
	 * {string}
	 *      change.type 'aggregation'<br>
	 * {object}
	 *      change.object the managed object instance on which the change occurred<br>
	 * {any}
	 *      change.mutation 'remove' or 'insert'<br>
	 * {sap.ui.base.ManagedObject}
	 *      change.child the child managed object instance<br>
	 *
	 * <h4>Association Change</h4>
	 * {string}
	 *      change.name the name of the association that changed<br>
	 * {string}
	 *      change.type 'association'<br>
	 * {object}
	 *      change.object the managed object instance on which the change occurred<br>
	 * {any}
	 *      change.mutation 'remove' or 'insert'<br>
	 * {string|string[]}
	 *      change.ids the ids that changed<br>
	 *
	 * <h4>Event Registry Change</h4>
	 * {string}
	 *      change.name the name of the event that changed<br>
	 * {string}
	 *      change.type 'event'<br>
	 * {object}
	 *      change.object the managed object instance on which the change occurred<br>
	 * {any}
	 *      change.mutation 'remove' or 'insert'<br>
	 * {object}
	 *      change.listener the listener object<br>
	 *{object}
	 *      change.func the listeners function<br>
	 *{object}
	 *      change.data the events data<br>
	 *
	 * <h4>Binding Change</h4>
	 * {string}
	 *      change.name the name of the binding that changed<br>
	 * {string}
	 *      change.type 'binding'<br>
	 * {object}
	 *      change.object the managed object instance on which the change occurred<br>
	 * {any}
	 *      change.mutation 'prepare', 'ready' or 'remove'<br>
	 * {object}
	 *      change.bindingInfo the binding info object<br>
	 * {string}
	 *      change.memberType 'property' or 'aggregation'<br>
	 *
	 * <h4>destroy managed Object</h4>
	 * {string}
	 *      change.type 'destroy'<br>
	 * {object}
	 *      change.object the managed object instance on which the change occurred<br>
	 *
	 * @param {function} fnCallback Callback function for this observer, to be called whenever a change happens
	 *
	 * @since 1.50.0
	 * @private
	 * @ui5-restricted sap.ui.model.base
	 * @constructor
	 * @alias sap.ui.base.ManagedObjectObserver
	 */
	var ManagedObjectObserver = BaseObject.extend("sap.ui.base.ManagedObjectObserver", {
		constructor: function (fnCallback) {
			if (!fnCallback && typeof fnCallback !== "function") {
				throw new Error("Missing callback function in ManagedObjectObserver constructor");
			}
			this._fnCallback = fnCallback;
		}
	});

	/**
	 * Starts observing the given object. A configuration is used to specify the meta data settings that should be observed.
	 * Configuration should be as specific as possible to avoid negative performance impact.
	 * Observing all settings (properties, aggregations, associations) should be avoided.
	 *
	 * @param {sap.ui.base.ManagedObject}
	 *     oObject the managed object instance to be observed
	 * @param {object}
	 *     oConfiguration a mandatory configuration specifying the settings to observe for the object
	 * @param {boolean|string[]} [oConfiguration.properties]
	 *     true if all properties should be observed or list of the property names to observe
	 * @param {boolean|string[]} [oConfiguration.aggregations]
	 *     true if all aggregations should be observed or list of the aggregation names to observe
	 * @param {boolean|string[]} [oConfiguration.associations]
	 *     true if all associations should be observed or list of the association names to observe
	 * @param {boolean|string[]} [oConfiguration.bindings]
	 *     true if all bindings should be observed or list of the binding names to observe
	 * @param {boolean|string[]} [oConfiguration.events]
	 *     true if all events should be observed or list of the event names to observe
	 * @param {boolean} [oConfiguration.destroy]
	 *     true if destroy should be observed
	 * @param {boolean} [oConfiguration.parent]
	 *     true if an API parent change should be observed
	 * @throws {TypeError} if the given object is not a ManagedObject and not <code>null</code> or <code>undefined</code>
	 *
	 * @private
	 * @ui5-restricted sap.ui.model.base
	 */
	ManagedObjectObserver.prototype.observe = function (oObject, oConfiguration) {
		if (!(oObject instanceof ManagedObject)) {
			// silently ignore calls with null or undefined
			if (oObject == null) {
				return;
			}
			throw new TypeError("ManagedObjectObserver can only handle ManagedObjects, but observe was called for " + oObject);
		}
		normalizeConfiguration(oObject, oConfiguration);
		create(oObject, this, oConfiguration);
	};

	/**
	 * Stops observing the given object. A configuration is used to specify the meta data settings that should be ignored.
	 * Configuration should be as specific as possible to avoid negative performance impact.
	 *
	 * @param {sap.ui.base.ManagedObject} oObject
	 *     the managed object instance that was observed
	 * @param {object} oConfiguration
	 *     a configuration specifying the settings to stop observing for the object.
	 *     If no configuration is provided, the object is unobserved completely
	 * @param {boolean|string[]} [oConfiguration.properties]
	 *     true if all properties should be stopped observing or list of the property names to stop observing
	 * @param {boolean|string[]} [oConfiguration.aggregations]
	 *     true if all aggregations should be stopped observing or list of the aggregation names to stop observing
	 * @param {boolean|string[]} [oConfiguration.associations]
	 *     true if all associations should be stopped observing or list of the association names to stop observing
	 * @param {boolean|string[]} [oConfiguration.bindings]
	 *     true if all bindings should be stopped observing or list of the binding names to stop observing
	 * @param {boolean|string[]} [oConfiguration.events]
	 *     true if all events should be stopped observing or list of the event names to stop observing
	 * @param {boolean} [oConfiguration.destroy]
	 *     true if destroy should be stopped observing
	 * @param {boolean} [oConfiguration.parent]
	 *     true if a parent change should be stopped observing
	 * @throws {TypeError} if the given object is not a ManagedObject and not <code>null</code> or <code>undefined</code>
	 *
	 * @private
	 * @ui5-restricted sap.ui.model.base
	 */
	ManagedObjectObserver.prototype.unobserve = function (oObject, oConfiguration) {
		if (!(oObject instanceof ManagedObject)) {
			// silently ignore calls with null or undefined
			if (oObject == null) {
				return;
			}
			throw new TypeError("ManagedObjectObserver can only handle ManagedObjects, but unobserve was called for " + oObject);
		}

		if (oConfiguration) {
			normalizeConfiguration(oObject, oConfiguration);
		}
		remove(oObject, this, oConfiguration);
	};

	/**
	 * Checks whether a given configuration set for a control is observed.
	 *
	 * All given settings must be observed for the method to return true.
	 *
	 * @param {sap.ui.base.ManagedObject} oObject
	 *        the managed object instance that was observed
	 * @param {object} oConfiguration
	 *        a configuration specifying the settings to check for the object.
	 *        If no configuration is provided it checks if the object observes at least for one property, etc.
	 * @param {boolean|string[]} [oConfiguration.properties]
	 *        true if all properties should be checked or list of the property names to check
	 * @param {boolean|string[]} [oConfiguration.aggregations]
	 *        true if all aggregations should be checked or list of the aggregation names to check
	 * @param {boolean|string[]} [oConfiguration.associations]
	 *        true if all associations should be checked or list of the association names to check
	 * @return {boolean} <code>true</code> if configuration is observed
	 * @throws {TypeError} if the given object is not a ManagedObject and not <code>null</code> or <code>undefined</code>
	 *
	 * @private
	 * @ui5-restricted sap.ui.model.base
	 */
	ManagedObjectObserver.prototype.isObserved = function (oObject, oConfiguration) {
		if (!(oObject instanceof ManagedObject)) {
			// silently ignore calls with null or undefined
			if (oObject == null) {
				return false;
			}
			throw new TypeError("ManagedObjectObserver can only handle ManagedObjects, but isObserved was called for " + oObject);
		}
		return isObjectObserved(oObject, this, oConfiguration);
	};

	/**
	 * Disconnect the observer from all objects.
	 * @private
	 * @ui5-restricted sap.ui.model.base
	 */
	ManagedObjectObserver.prototype.disconnect = function () {
		disconnectFromListener(this);
	};

	/**
	 * Returns the current configuration of a given control
	 *
	 * @param {sap.ui.base.ManagedObject} oObject the managed object instance that is observed
	 * @return {object} oConfiguration the configuration of the given object
	 */
	ManagedObjectObserver.prototype.getConfiguration = function (oObject) {
		return getConfiguration(oObject, this);
	};

	// private implementation
	var Observer = {},
		mTargets = Object.create(null),
		mListeners = new WeakMap();

	// observer interface for ManagedObject implementation.

	/**
	 * Called from sap.ui.base.ManagedObject if a property is changed.
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @param {string} sName the name of the property that changed
	 * @param {any} vOld the old value of the property
	 * @param {any} vNew the new value of the property
	 * @private
	 * @ui5-restricted sap.ui.base.ManagedObject
	 */
	Observer.propertyChange = function (oManagedObject, sName, vOld, vNew) {
		// managed object does a propertyChange.call(this, sName, vOld, vNew)
		handleChange("properties", oManagedObject, sName, function () {
			return {
				type: "property",
				old: vOld,
				current: vNew
			};
		});
	};

	/**
	 * Called from sap.ui.base.ManagedObject if an aggregation is changed.
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @param {string} sName the name of the aggregation that changed
	 * @param {string} sMutation "remove" or "insert"
	 * @param {sap.ui.base.ManagedObject|sap.ui.base.ManagedObject[]} vObjects the removed or inserted object or objects array
	 * @private
	 * @ui5-restricted sap.ui.base.ManagedObject
	 */
	Observer.aggregationChange = function (oManagedObject, sName, sMutation, vObjects) {
		// managed object does an aggregationChange.call(this, sName, sMutation, vObjects)
		handleChange("aggregations", oManagedObject, sName, function () {
			return {
				type: "aggregation",
				mutation: sMutation,
				children: Array.isArray(vObjects) ? vObjects : null,
				child: !Array.isArray(vObjects) ? vObjects : null
			};
		});
	};

	/**
	 * Called from sap.ui.base.ManagedObject if a parent is changed.
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @param {string} sName the name of the triggering parent aggregation
	 * @param {string} sMutation "set" or "unset"
	 * @param {sap.ui.base.ManagedObject} oParent the changed parent
	 * @private
	 * @ui5-restricted sap.ui.base.ManagedObject
	 */
	Observer.parentChange = function(oManagedObject, sName, sMutation, oParent) {
		//managed object does a parent change handle this
		handleChange("parent", oManagedObject, sName, function () {
			return {
				type: "parent",
				mutation: sMutation,
				parent: oParent
			};
		});

		//As the change of a parent is only possible when an aggregation change is applied
		//handle this change
		var sParentMutation = sMutation === "unset" ? "remove" : "insert";
		Observer.aggregationChange(oParent, sName, sParentMutation, oManagedObject);
	};

	/**
	 * Called from sap.ui.base.ManagedObject if an association is changed.
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @param {string} sName the name of the association that changed
	 * @param {string} sMutation "remove" or "insert"
	 * @param {string|string[]} vIds the removed or inserted id or list of ids
	 * @private
	 * @ui5-restricted sap.ui.base.ManagedObject
	 */
	Observer.associationChange = function (oManagedObject, sName, sMutation, vIds) {
		// managed object does an associationChange.call(this, sName, sMutation, vIds)
		handleChange("associations", oManagedObject, sName, function () {
			return {
				type: "association",
				mutation: sMutation,
				ids: vIds
			};
		});
	};

	/**
	 * Called from sap.ui.base.ManagedObject if an event registration is changed.
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @param {string} sName the name of the event that changed
	 * @param {string} sMutation "remove" or "insert"
	 * @param {object} vListener the removed or inserted listener
	 * @param {function} fnFunc the removed or inserted handler function
	 * @param {object} oData the removed or inserted event data
	 * @private
	 */
	Observer.eventChange = function(oManagedObject, sName, sMutation, vListener, fnFunc, oData) {
		//managed object does a eventChange.call(this, sName, sMutation, vListener, fnFunc, oData)
		handleChange("events", oManagedObject, sName, function() {
			return {
				type: "event",
				mutation: sMutation,
				listener: vListener,
				func: fnFunc,
				data: oData
			};
		});
	};

	/**
	 * Called from sap.ui.base.ManagedObject if a binding changed for a property or aggregation.
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @param {string} sName The name of the property or aggregation of which the binding is changed
	 * @param {string} sMutation "prepared", "ready", "removed"
	 * @param {object} oBindingInfo the binding info
	 * @param {string} sMemberType 'aggregation' or 'property'
	 * @private
	 */
	Observer.bindingChange = function(oManagedObject, sName, sMutation, oBindingInfo, sMemberType) {
		//managed object does a bindingChange.call(this, sName, sMutation, oBindingInfo, sType)
		handleChange("bindings", oManagedObject, sName, function() {
			return {
				type: "binding",
				mutation: sMutation,
				bindingInfo: oBindingInfo,
				memberType: sMemberType
			};
		});
	};

	/**
	 * Called from sap.ui.base.ManagedObject if object is destroyed
	 *
	 * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change
	 * @private
	 * @ui5-restricted sap.ui.base.ManagedObject
	 */
	Observer.objectDestroyed = function(oManagedObject) {
		handleChange("destroy", oManagedObject, null, function () {
			return {
				type: "destroy"
			};
		});
		disconnectFromTarget(oManagedObject);
	};

	// disconnect listener from targets
	function disconnectFromTarget(oManagedObject) {
		var sId = oManagedObject.getId();
		if (mTargets[sId]) {
			// detachEvent doesn't fail if the listener is not registered
			oManagedObject.detachEvent("EventHandlerChange", fnHandleEventChange);
			for (var i = 0; i < mTargets[sId].listeners.length; i++) {
				var mTargetSet = mListeners.get(mTargets[sId].listeners[i]);
				if (mTargetSet) {
					mTargetSet.delete(sId);
				}
			}
			delete mTargets[sId];
		}
		delete oManagedObject._observer;
	}

	// handles the change event and pipelines it to the ManagedObjectObservers that are attached as listeners
	function handleChange(sType, oObject, sName, fnCreateChange) {
		var sId = oObject.getId(),
			oTargetConfig = mTargets[sId];

		if (oTargetConfig) {
			var oChange;
			for (var i = 0; i < oTargetConfig.listeners.length; i++) {
				if (isObserving(oTargetConfig.configurations[i], sType, sName)) {
					if (!oChange) {
						oChange = fnCreateChange();
						oChange.name = sName;
						oChange.object = oObject;
					}
					var oListener = oTargetConfig.listeners[i];
					oListener._fnCallback(oChange);
				}
			}
		}
	}

	// checks whether the type and name is part of the given configuration.
	// if true is returned a change needs to be processed.
	function isObserving(oConfiguration, sType, sName) {
		// no configuration, listen to all types
		if (oConfiguration == null || !sType) {
			return false;
		}

		if (sType != "destroy" && sType != "parent" && !sName) {
			return false;
		}

		// either all (true) properties/aggregations/associations are relevant or a specific list or names is provided
		return oConfiguration[sType] === true || (Array.isArray(oConfiguration[sType]) && oConfiguration[sType].indexOf(sName) > -1);
	}

	// adds a listener and its configuration to the internal list of observed targets mTargets.
	// if the listener is already registered to the target, only its configuration is updated.
	// adds the observer to the target managed object if an observer is missing.
	function create(oTarget, oListener, oConfiguration) {
		updateConfiguration(oTarget, oListener, oConfiguration, false);
	}

	function getConfiguration(oTarget, oListener) {
		var sId = oTarget.getId();
		var oTargetConfig = mTargets[sId];
		if (oTargetConfig && oTargetConfig.listeners) {
			var iIndex = oTargetConfig.listeners.indexOf(oListener);
			if (iIndex >= 0) {
				//return the current configuration
				var oConfiguration = deepExtend({}, oTargetConfig.configurations[iIndex]);
				return oConfiguration;
			}
		}

		return null;
	}

	// removes the given configuration for the given listener from the internal list of observed targets mTargets.
	// removes the observer from the target managed object if the target hasn't to be observed any longer
	function remove(oTarget, oListener, oConfiguration) {
		oConfiguration = oConfiguration || getConfiguration(oTarget, oListener);
		updateConfiguration(oTarget, oListener, oConfiguration, true);
	}

	function isObjectObserved(oTarget, oListener, oConfiguration) {
		var sId = oTarget.getId(),
			oTargetConfig = mTargets[sId];

		//in case no configuration is given take the current configuration
		oConfiguration = oConfiguration || getConfiguration(oTarget, oListener);

		if (!oTargetConfig) {
			return false;
		}
		var iIndex = oTargetConfig.listeners.indexOf(oListener);
		if (iIndex === -1) {
			return false;
		} else {
			//make a subset check
			return isSubArray(oTargetConfig.configurations[iIndex].properties, oConfiguration.properties) &&
				isSubArray(oTargetConfig.configurations[iIndex].aggregations, oConfiguration.aggregations) &&
				isSubArray(oTargetConfig.configurations[iIndex].associations, oConfiguration.associations) &&
				isSubArray(oTargetConfig.configurations[iIndex].bindings, oConfiguration.bindings) &&
				isSubArray(oTargetConfig.configurations[iIndex].events, oConfiguration.events) &&
				isBooleanEqual(oTargetConfig.configurations[iIndex].destroy, oConfiguration.destroy) &&
				isBooleanEqual(oTargetConfig.configurations[iIndex].parent, oConfiguration.parent);
		}
	}

	// removes a given listener by looking at all registered targets and their listeners.
	// if there are no more listeners to a target, the registered target is removed from the mTargets map.
	function disconnectFromListener(oListener) {
		var mTargetSet = mListeners.get(oListener);
		if (!mTargetSet) {
			return;
		}
		mTargetSet.forEach(function(sId) {
			var oTargetConfig = mTargets[sId];
			for (var i = 0; i < oTargetConfig.listeners.length; i++) {
				if (oTargetConfig.listeners[i] === oListener) {
					oTargetConfig.listeners.splice(i, 1);
					oTargetConfig.configurations.splice(i, 1);
				}
			}
			if (oTargetConfig.listeners.length === 0) {
				delete mTargets[sId];
				oTargetConfig.object._observer = undefined;
			}
		});
		mListeners.delete(oListener);
	}

	// update a complete configuration, create one if needed or remove it
	function updateConfiguration(oTarget, oListener, oConfiguration, bRemove) {

		var sId = oTarget.getId(),
			oTargetConfig = mTargets[sId],
			mTargetSet = mListeners.get(oListener) || new Set(),
			oCurrentConfig,
			iIndex;
		if (bRemove) {
			if (!oTargetConfig) {
				// no registration so far, nothing to remove
				return;
			}
			iIndex = oTargetConfig.listeners.indexOf(oListener);
			if (iIndex >= 0) {
				// already registered, update the configuration
				oCurrentConfig = oTargetConfig.configurations[iIndex];
			}
		} else {
			if (!oTargetConfig) {
				oTargetConfig = mTargets[sId] = {
					listeners: [],
					configurations: [],
					object: oTarget
				};
			}
			if (mTargetSet.size === 0) {
				mListeners.set(oListener, mTargetSet);
			}
			mTargetSet.add(sId);

			iIndex = oTargetConfig.listeners.indexOf(oListener);
			if (iIndex === -1) {
				// not registered, push listener and configuration
				oTargetConfig.listeners.push(oListener);
				oTargetConfig.configurations.push(oConfiguration);
			} else {
				oCurrentConfig = oTargetConfig.configurations[iIndex];
			}
		}
		if (oCurrentConfig) {
			oCurrentConfig.properties = oCurrentConfig.properties || [];
			updateSingleArray(oCurrentConfig.properties, oConfiguration.properties, bRemove);

			oCurrentConfig.aggregations = oCurrentConfig.aggregations || [];
			updateSingleArray(oCurrentConfig.aggregations, oConfiguration.aggregations, bRemove);

			oCurrentConfig.associations = oCurrentConfig.associations || [];
			updateSingleArray(oCurrentConfig.associations, oConfiguration.associations, bRemove);

			oCurrentConfig.bindings = oCurrentConfig.bindings || [];
			updateSingleArray(oCurrentConfig.bindings, oConfiguration.bindings, bRemove);

			oCurrentConfig.events = oCurrentConfig.events || [];
			updateSingleArray(oCurrentConfig.events, oConfiguration.events, bRemove);

			if (oConfiguration.destroy != null) {
				if (bRemove) {
					delete oCurrentConfig.destroy;
				} else {
					oCurrentConfig.destroy = oConfiguration.destroy;
				}
			}

			if (oConfiguration.parent != null) {
				if (bRemove) {
					delete oCurrentConfig.parent;
				} else {
					oCurrentConfig.parent = oConfiguration.parent;
				}
			}
		}
		var bEventsObserved = hasObserverFor(oTarget, "events");

		if (oTarget._observer && bRemove) {
			//delete oTarget._observer;
			if (!bEventsObserved && EventProvider.hasListener(oTarget, "EventHandlerChange", fnHandleEventChange)) {
				oTarget.detachEvent("EventHandlerChange", fnHandleEventChange);
			}
			if (!bEventsObserved &&
					!hasObserverFor(oTarget, "properties") &&
					!hasObserverFor(oTarget, "aggregations") &&
					!hasObserverFor(oTarget, "associations") &&
					!hasObserverFor(oTarget, "destroy") &&
					!hasObserverFor(oTarget, "parent") &&
					!hasObserverFor(oTarget, "bindings")) {
				disconnectFromTarget(oTarget);
			}
		} else if (!oTarget._observer && !bRemove) {
			//is any config listening to events
			if (bEventsObserved && !EventProvider.hasListener(oTarget, "EventHandlerChange", fnHandleEventChange)) {
				oTarget.attachEvent("EventHandlerChange", fnHandleEventChange);
			}
			oTarget._observer = Observer;
		}

	}

	//checks whether a given type (events, aggregations, associations, properties, bindings, destroy) is
	//currently observed on the given target
	function hasObserverFor(oTarget, sType) {
		var sId = oTarget.getId(),
			oTargetConfig = mTargets[sId];
		if (oTargetConfig) {
			var aConfigs = oTargetConfig.configurations.filter(function(oEntry) {
				return oEntry.hasOwnProperty(sType) && oEntry[sType] && (oEntry[sType] === true || oEntry[sType].length > 0);
			});
			return aConfigs.length > 0;
		}
		return false;
	}

	function fnHandleEventChange(oEvent) {
		var oTarget = oEvent.getSource(),
			sEventId = oEvent.mParameters.EventId;
		if (oTarget.getMetadata().hasEvent(sEventId)) {
			if (oEvent.mParameters.type === "listenerAttached") {
				Observer.eventChange(oTarget, sEventId, "insert", oEvent.mParameters.listener, oEvent.mParameters.func, oEvent.mParameters.data);
			} else if (oEvent.mParameters.type === "listenerDetached") {
				Observer.eventChange(oTarget, sEventId, "remove", oEvent.mParameters.listener, oEvent.mParameters.func, oEvent.mParameters.data);
			}
		}
	}

	// update the single array for observing and unobserving
	function updateSingleArray(aOrig, aAdditional, bRemove) {
		if (!aAdditional) {
			return;
		}

		for (var i = 0; i < aAdditional.length; i++) {
			var iIndex = aOrig.indexOf(aAdditional[i]);
			if (iIndex > -1 && bRemove) {
				aOrig.splice(iIndex, 1);
			} else if (iIndex === -1 && !bRemove) {
				aOrig.push(aAdditional[i]);
			}
		}
	}

	function isSubArray(aFullArray, aSubArray) {
		if (!Array.isArray(aSubArray) || aSubArray.length == 0) {
			// empty array is contained in 'anything'
			return true;
		}

		if (!Array.isArray(aFullArray) || aFullArray.length == 0) {
			// empty array contains no other (non-empty) array
			return false;
		}

		var aUnion = uniqueSort(aFullArray.concat(aSubArray)); // merge arrays, remove duplicates

		//in case aSubArray is inside aFullArray the length did not change
		return aFullArray.length === aUnion.length;
	}

	function isBooleanEqual(bOriginal,bCompare) {
		if (bCompare == null) {
			return true;
		}

		return bOriginal === bCompare;

	}

	// in case the configuration for a specific type is set to true translate this to the complete array in order not to get in trouble
	// when deregistering properties
	function normalizeConfiguration(oObject, oConfiguration) {
		var oMetadata = oObject.getMetadata(),
			aProperties = Object.keys(oMetadata.getAllProperties()),
			aAggregations = Object.keys(oMetadata.getAllAggregations()),
			aAssociations = Object.keys(oMetadata.getAllAssociations()),
			aBindings = uniqueSort(aProperties.concat(aAggregations)),
			aEvents = Object.keys(oMetadata.getAllEvents());

		oConfiguration.properties = oConfiguration.properties === true ? aProperties : oConfiguration.properties;
		oConfiguration.aggregations = oConfiguration.aggregations === true ? aAggregations : oConfiguration.aggregations;
		oConfiguration.associations = oConfiguration.associations === true ? aAssociations : oConfiguration.associations;

		oConfiguration.bindings = oConfiguration.bindings === true ? aBindings : oConfiguration.bindings;
		oConfiguration.events = oConfiguration.events === true ? aEvents : oConfiguration.events;
		oConfiguration.destroy = (oConfiguration.destroy == null) ? false : oConfiguration.destroy;
		oConfiguration.parent = (oConfiguration.parent == null) ? false : oConfiguration.parent;
	}

	return ManagedObjectObserver;
});
