var Prolific = window.Prolific = function () {
	var _cache = {},
		_config = {},
		_classIterator = 0,
		debugMode = true,
	augment = function (obj, membs, override) {
		var m;
		for (m in membs) {
			if (membs.hasOwnProperty(m) && (override || !obj.hasOwnProperty(m))) {
				obj[m] = membs[m];
			}
		}
		return obj;
	},
	beget = function (obj, membs) {
		var F = function () {};
		F.prototype = obj;
		return membs? override(new F(), membs): new F();
	},
	override = function (obj, membs) {
		return augment(obj, membs, true);
	},
	give = function (receiver, giver, membList) {
		var membArr = membList.replace(' ', '').split(','),
			i, m;
		for (i = 0; i < membArr.length; i++) {
			m = membArr[i];
			if (giver[m]) {
				receiver[m] = giver[m];
			}
		}
		return receiver;
	},
	methodize = function (func) {
		return function () {
			args = Array.prototype.slice.call(arguments, 0, arguments.length);
			args.push(this);
			return func.apply(this, args);
		};
	},
	Class = function (raw) {
		var constr = function (params, extra) {
				var self = raw.apply(this, arguments);
				self.constructor = constr;
				augment(self, {
					isInstanceOf: function (testClass) {
						if (!testClass) return false;
						return typeof testClass.getId === 'function'? false: testClass.getId() === self.getId();
					},
					augment: function (membs) {
						return augment(this, membs);
					},
					beget: function (membs) {
						return beget(this, membs);
					},
					override: function (membs) {
						return override(this, membs);
					},
					give: function (giver, membList) {
						return give(this, giver, membList);
					},
					getParams: function () {
						return params;
					}
				});
				return extra? override(self, extra): self;
			};
		return augment(constr, {
				extend: function (subName, subRaw, superParams) {
					Prolific.classes[subName] = constr[subName] = Extended(constr, subRaw, superParams);
					return this;
				},
				getId: function () {
					var id = _classIterator++;
					return function () {
						return id;
					};
				}()
		});
	},
	P = Class(function (obj) {
		return obj;
	}),
	Extended = function (superConstr, subRaw, superParams) {
		var thisClass = Class(function (subParams) {
			var proto = superConstr.call({}, superParams? override(superParams, subParams): subParams),
				self = {
					superConstructor: superConstr,
					getSuperParams: function () {
						return superParams;
					},
					isInstanceOf: function (testClass) {
						var curDesc = thisClass;
  						while (curDesc && curDesc.getId) {
  							if (curDesc.getId() === testClass.getId()) {
  								return true;
  							} else {
  								curDesc = curDesc.superClass;
  							}
  						}
						return false;
					}
				};
			return beget(proto, override(self, subRaw.call(proto, subParams)));
		});
		return augment(thisClass, {
			superClass: superConstr
		});
	},
	module = function (name, constr) {
		try {
			var m, raw;
			this[name] = give(constr.call(this), Prolific, 'module, classes');
			if (debugMode) {
				for (m in this[name]) {
					if (this[name].hasOwnProperty(m) && typeof this[name][m] === 'function') {
						raw = this[name][m];
						this[name][m] = function () {
							var func = raw,
								funcName = m;
							return function () {
								try {
									return func.apply(this, arguments);
								} catch (e) {
									alert(e + ' (Prolific.' + name + '.' + funcName + ')');
								}
							};
						}()
					}
				}
			}
		} catch (e) {
			if (debugMode === true) alert(e + ' (Prolific.' + name + ')');
		}
		return this;
	},
	each = function (obj, func) {
		var o;
		for (o in obj) {
			if (obj.hasOwnProperty(o)) {
				func.call(obj[o], o);
			}
		}
		return obj;
	},	
	map = function (arr, func) {
		var mapArr = [];
		each(arr, function (i) {
			mapArr[i] = func.call(arr[i], i);
		});
		return mapArr;
	},
	Prolificize = function () {
		return augment(map(arguments, function (obj) {
			return P(obj);
		}), {
			augmentAll: function (membs) {
				each(this, function () {
					augment(this, membs);
				});
				return this;
			},
			begetAll: function (membs) {
				each(this, function () {
					beget(this, membs);
				});
				return this;
			},
			giveAll: function (giver, membsList) {
				each(this, function () {
					give(this, giver, membsList);
				});
				return this;
			},
			overrideAll: function (membs) {
				each(this, function () {
					override(this, membs);
				});
				return this;
			},
			each: function (func) {
				return each(this, func);
			},
			map: function (func) {
				return map(this, func);
			}
		});
	},
	addClassBatch = function (map) {
		var m;
		for (m in map) {
			Prolific.classes.add(m, map[m]);
		}
	};
	return augment(Prolificize, {
		classes: {
			add: function (name, constr) {
				if (typeof name === 'object') {
					addClassBatch(name);
				} else {
					this[name] = Class(constr);
				}
				return this;
			}
		},
		Class: Class,
		Extended: Extended,
		augment: augment,
		beget: beget,
		override: override,
		give: give,
		methodize: methodize,
		module: module,
		each: each,
		map: map,
		enableDebug: function () {
			debugMode = true;
			return this;
		},
		disableDebug: function () {
			debugMode = false;
			return this;
		},
		config: function (name, value) {
			if (typeof name === 'object') {
				override(_config, name);
				return this;
			} else if (value) {
				_config[name] = value;
				return this;
			} else {
				return _config[name];
			}			
		},
		cache: function (name, value) {
			var cacheRef = _cache[name];
			if (value) {
				if (value === '++') {
					cacheRef = typeof cacheRef === 'number'? cacheRef + 1: 1;
					return cacheRef;
				} else if (value === '--') {
					cacheRef = typeof cacheRef === 'number'? cacheRef - 1: -1;
					return cacheRef;
				} else {
					cacheRef = value;
					return this;
				}
			} else {
				return cacheRef;
			}
		}
	});
}(), P = P? P: Prolific;