module('lively.Traits').requires().toRun(function() {

Object.subclass('RealTrait',
'global state', {
	traitRegistry: {},
	isTrait: true,
},
'initializing', {
	initialize: function(name) {
		if (!name || !Object.isString(name)) throw new Error('Trait needs a name!')
		this.name = name;
		this.def = {};
		this.categories = {};
		this.extendedObjectsAndOptions = {classes: {}, traits: {}};
		this.traitRegistry[name] = this;
	},
},
'manipulating', {
	extend: function(category, def) {
		if (!def) return;
		this.removeFromDependents();
		for (var name in def)
			if (def.hasOwnProperty(name) && Object.isFunction(def[name]))
				def[name].belongsToTrait = this;
		Object.extend(this.def, def);
		this.categories[category] = def;
		this.updateDependents();
	},
},
'updating', {
	updateDependents: function() {
		Properties.forEachOwn(this.extendedObjectsAndOptions.classes, function(className, options) {
			var klass = Class.forName(className);
			if (klass) this.applyToClass(klass, options) 
		}, this);
		Properties.forEachOwn(this.extendedObjectsAndOptions.traits, function(traitName, options) {
			var trait = Trait(traitName);
			if (trait) this.applyToTrait(trait, options);
		}, this);
	},
	applyTo: function(obj, options) {
		if (Class.isClass(obj)) return this.applyToClass(obj, options);
		if (obj.isTrait) return this.applyToTrait(obj, options);
		throw new Error('Cannot apply trait ' + this + ' to ' + obj);
	},
	applyToClass: function(klass, options) {
		this.removeFrom(klass.prototype);
		this.basicApplyTo(this.def, klass.prototype, options)
		this.extendedObjectsAndOptions.classes[klass.type || klass.name] = options;
	},
	applyToTrait: function(trait, options) {
		trait.removeFromDependents();
		this.removeFrom(trait.def);
		var def = {};
		for (var name in this.def)
			if (this.def.hasOwnProperty(name) && !trait.def[name])
				def[name] = this.def[name];
		this.basicApplyTo(def, trait.def, options)
		this.extendedObjectsAndOptions.traits[trait.name] = options;
		trait.updateDependents();
	},
	basicApplyTo: function(source, target, options) {
		var def = {},
			aliasing = (options && options.aliasing) || {},
			exclusion = (options && options.exclusion) || [];
		for (var name in source) {
			if (!source.hasOwnProperty(name)) continue;
			if (exclusion.include(name)) continue;
			var aliasedName = aliasing[name] || name;
			if (target[aliasedName]) continue;
			def[aliasedName] = source[name];	
		}
		Object.extend(target, def);
	},
},
'removing', {
	remove: function() {
		this.removeFromDependents();
		delete this.traitRegistry[this.name];
	},
	removeFrom: function(obj) {
		var own = Properties.ownValues(this.def),
			props = Functions.all(obj);
		for (var i = 0; i < props.length; i++)
			if (own.include(obj[props[i]]))
				delete obj[props[i]]
	},
	removeFromDependents: function() {
		Properties.forEachOwn(this.extendedObjectsAndOptions.classes, function(className, options) {
			var klass = Class.forName(className);
			if (!klass) return;
			this.removeFrom(klass.prototype) 
		}, this);
		Properties.forEachOwn(this.extendedObjectsAndOptions.traits, function(traitName, options) {
			var trait = Trait(traitName);
			if (!trait) return;
			trait.removeFromDependents();
			this.removeFrom(trait.def);
			trait.updateDependents();
		}, this);
	},
},
'categories', {
	getCategoryNames: function() { return Properties.own(this.categories) },
},
'debugging', {
	toString: function() { return 'Trait(\'' + this.name + '\')' },
});

Object.extend(RealTrait, {
	named: function(name, def) {
		return this.prototype.traitRegistry[name] || new RealTrait(name);
	},
});

Object.extend(Global, {
	Trait: function(/*traitName, def ... */) {
		var args = $A(arguments),
			traitName = args.shift(),
			trait = RealTrait.named(traitName),
			category = ' default category';
		for (var i = 0; i < args.length; i++) {
			if (Object.isString(args[i])) {
				category = args[i];
			} else {
				trait.extend(category, args[i]);
			}
		}
		return trait;
	},
});
}) // end of module