this.xtrim = this.xtrim || {};
(function(global, xtrim) {
  'use strict';

	var EventEmitter = xtrim.EventEmitter || require('events'),
		uid = xtrim.uid || require("./uid"),
		DataType = xtrim.DataType || require("./dt");

	// var EventEmitter = require('events'),
	// 	uid = require("./uid"),
	// 	DataType = require("./dt");

	///////////////////////////////
	var util = {
		getThis: function () {
			return this;
		},

		getUID: function () {
			return this._sId;
		}
	};



	/**
	 * Returns a copy of the object given as argument.
	 *
	 * @param {object} oSource the object to copy from.
	 * @returns {object} the copy of the argument.
	 */
	util.clone = function(oSource) {
		if (oSource !== null && oSource !== undefined && typeof oSource === 'object') {
			try {
				return JSON.parse(JSON.stringify(oSource));
			} catch (err) {
				return util.extendObject({}, oSource);
			}
		}
		return oSource;
	};

	/**
	 * Returns a hash-map (associative array) containing the items of the source array argument.
	 *
	 * @param {array} aArray the source array
	 * @returns {object} the destination hash-map the items of the source array.
	 */
	util.arrayToHash = function(aArray) {
		if (aArray instanceof Array === false) {
			throw new Error('Expecting an instance of Array as argument');
		}

		var hash = {},
			i;

		for (i in aArray) {
			hash[aArray[i]] = true;
		}
		return hash;
	};


	////////////////////////////
	/**
	 * @constructor {type} Class
	 * Defines the constructor function of the new class.
	 *
	 * @param  {object}   mSettings  - an optional object literal containing the instantiation values
	 * @returns    {object}    this instance, to enable method call chaining
	 */
	function fCtor (oMetadata, mSettings) {

		mSettings = typeof mSettings === "object" ? mSettings : {};

		// sets the instance identifier
		if (!this._sId) {
			uid.setId(this, mSettings["@id"]);
			delete mSettings["@id"];
		}

		// this._properties = typeof this._properties === 'object' ? this._properties : {};
		oMetadata.getSuperClass().apply(this, arguments);

		this.applySettings(mSettings, {
			private: oMetadata.getPrivate(),
			initializeFirst: true,
			suppressEvent: true
		});

		if (typeof this.init === "function") {
			return this.init.call(this, mSettings);
		}
		return this;
	}




	////////////////////////////
	function StaticType (vValue, bReadonly) {
		this.value = vValue || null;
		this.readonly = !!bReadonly;
		return this;
	}


	////////////////////////////
	function PublicType (vValue) {
		this.value = vValue || null;
		return this;
	}


	///////////////////////////
	function Class (fCtor, fSuperCtor) {
		return this;
	}
	/**
	 * Enhances the function given as the first argument by extanding its prototype
	 * with the prototype of function given as the second argument.
	 *
	 * @param {function} fCtor the constructor function to extend.
	 * @param {function} fSuperCtor the constructor function to inherit from.
	 * @returns {function} the first argument enhannced.
	 */
	Class.prototype._extendCtor = function(fCtor, fSuperCtor) {
		if (typeof fCtor !== 'function') {
			throw new Error("Expecting an argument of type 'function'");
		}

		var fSuper = typeof fSuperCtor === 'function' ? fSuperCtor : EventEmitter;
		fCtor.getSuper = util.getThis.bind(fSuper);
		fCtor.prototype = Object.create(fSuper.prototype, {
			constructor: {
				value: fCtor,
				enumerable: false,
				configurabile: true,
				writable: true
			}
		});
		return fCtor;
	};
	/**
	 * Enhances the object gived as the first argument by attaching
	 * the properties of the object given as the second argument.
	 *
	 * @param {object} oDest the object to extend.
	 * @param {object} oSource the object to inherit from.
	 * @returns {object} the first argument enhanced.
	 */
	Class.prototype._extendPrototype = function(oDest, oSource) {
		if (oSource === null || oSource === undefined || typeof oSource !== 'object') {
			return oDest;
		}
		oDest = oDest && (typeof oDest === 'object' || typeof oDest === 'function') ? oDest : {};

		var keys = Object.keys(oSource),
			k, src;

		for (k in keys) {
			if (keys.hasOwnProperty(k)) {
				// oDest[keys[k]] = oSource[keys[k]];
				src = oSource[keys[k]] || {};
				if (!!src.readonly) {
					Object.defineProperty(oDest, keys[k], {
						value: src.value,
						configurable: true,
						enumerable: true
					});
				} else {
					oDest[keys[k]] = src.hasOwnProperty("value") ? src.value : src;
				}
			}
		}
		return oDest;
	};

	Class.extend = function () {
		this.ctor =	this._extendCtor(fCtor, fSuperCtor);
		this.superCtor = this.ctor.getSuper();

	};



	///////////////////////////
	function Private (mPrivate) {
		var p, tmp;

		// validate the data fields
		mPrivate = mPrivate && typeof mPrivate === 'object' ? mPrivate : {};
		for (p in mPrivate) {
			tmp = mPrivate[p];
			if (!(tmp instanceof DataType)) {
				if (typeof tmp === 'string' && DataType.dataTypes[tmp] instanceof DataType) {
					tmp = DataType.dataTypes[tmp];
				} else if (tmp && typeof tmp === 'object') {
					tmp = new DataType(tmp.tester, tmp.defaultValue);
				} else {
					tmp = DataType.dataTypes.any;
				}
			}
			this[p] = tmp;
		}
		return this;
	}



	///////////////////
	function Static (mStatic) {
		var p, tmp;

		// validate the static members
		mStatic = typeof mStatic === 'object' ? mStatic : {};
		mStatic.getInstanceById = uid.getInstanceById;
		mStatic.getIdOf = uid.getIdOf;

		for (p in mStatic) {
			tmp = mStatic[p];
			if (!(tmp instanceof StaticType)) {
				if (typeof tmp === 'object' && tmp.hasOwnProperty("value")) {
					tmp = new StaticType(tmp.value, tmp.readonly);
				} else {
					tmp = new StaticType(tmp);
				}
			}
			this[p] = tmp;
		}
		return this;
	}


	///////////////////
	function Public(mPublic) {
		var p, tmp;

		// validate the public members
		mPublic = typeof mPublic === 'object' ? mPublic : {};
		mPublic.getUID = util.getUID;

		for (p in mPublic) {
			tmp = mPublic[p];
			if (!(tmp instanceof PublicType)) {
				if (typeof tmp === 'object' && tmp.hasOwnProperty("value")) {
					tmp = new PublicType(tmp.value);
				} else {
					tmp = new PublicType(tmp);
				}
			}
			this[p] = tmp;
		}
		return this;
	}


	///////////////////
	function Metadata (fCtor, fSuperCtor,  mPrivate, mStatic, mPublic) {
		var superMetadata = fSuperCtor.getMetadata && fSuperCtor.getMetadata();

		this._private = new Private(mPrivate);

		mStatic = typeof mStatic === "object" ? mStatic : {};
		mStatic.getMetadata = util.getThis.bind(this);
		this._static = new Static(mStatic);

		this._public = new Public(mPublic);
		
		if (superMetadata instanceof Metadata) {
			Object.setPrototypeOf(this._private, superMetadata._private || Object.prototype);
			Object.setPrototypeOf(this._static, superMetadata._static || Object.prototype);
			Object.setPrototypeOf(this._public, superMetadata._public || Object.prototype);
		}

		this._class = new Class(fCtor, fSuperCtor); // typeof fClass === "function" ? fClass : Object;
		return this;
	}

	Metadata.prototype = Object.defineProperties(Object.prototype, {
		constructor: {
			value: Metadata
		},

		private: {
			set: Function.prototype,
			get: function () {
				return this._private;
			}
		},

		static: {
			set: Function.prototype,
			get: function () {
				return this._static;
			}
		},

		public: {
			set: Function.prototype,
			get: function () {
				return this._public;
			}
		}
	});

	Metadata.prototype.getClass = function () {
		return this._class;
	};

	Metadata.prototype.getSuperClass = function () {
		return typeof this._class.getSuper === "function" ? this._class.getSuper() : null;
	};

	Metadata.prototype.getPrototype = function () {
		return typeof this._class === "function" ? this._class.prototype : null;
	};

	Metadata.prototype.getPrivate = function () {
		return typeof this.private;
	};

	Metadata.prototype.getPublic = function () {
		return typeof this._public;
	};

	Metadata.prototype.getStatic = function () {
		return typeof this._static;
	};

	Metadata.Ctor = fCtor;
	Metadata.Private = Private;
	Metadata.Static = Static;
	Metadata.Public = Public;


  if (typeof define === 'function' && define.amd) {
    define(function () {
      return Metadata;
    });
  } else if (typeof exports === 'object') {
		module.exports = Metadata;
  } else {
    xtrim.Metadata = Metadata;
  }
})(this, this.xtrim);



