/**
	@class

	Benchmark provides some simple methods for benchmarking the performance of your application.
*/
rio.Benchmark = new (rio.Attr.create({ 
	attrAccessors: [
		["installations", []],
		["started", false]
	],
	methods: {
		install: function(obj, methodName, objString) {
			if (!objString) { objString = obj.toString(); };

			if (!this.installationFor(obj, methodName)) {
				var installation = new rio.BenchmarkInstallation({ objectString: objString, object: obj, methodName: methodName });
				this.getInstallations().push(installation);
			
				installation.install();
			}
		},

		start: function() {
			this.setStarted(true);
			this.getInstallations().invoke("reset");
		},

		stop: function() {
			this.setStarted(false);
		},
		
		begin: function(waypointName) {
			var installation = this.installationFor(waypointName);
			if (!installation) {
				installation = new rio.BenchmarkWaypoint({ objectString: "[WAYPOINT]", methodName: waypointName });
				this.getInstallations().push(installation);
			}
			installation.begin();
		},
		
		end: function(waypointName) {
			var installation = this.installationFor(waypointName);
			if (installation) {
				installation.end();
			}
		},
		
		count: function(countName) {
			var installation = this.installationFor(countName);
			if (!installation) {
				installation = new rio.BenchmarkCount({ objectString: "[COUNT]", methodName: countName });
				this.getInstallations().push(installation);
			}
			installation.increment();
		},

		metrics: function(obj, methodName) {
			var installation = this.installationFor(obj, methodName);
			return { invocations: installation.getInvocations(), time: installation.getTime() };
		},
		
		installationFor: function(obj, methodName) {
			if (Object.isString(obj)) {
				return this.getInstallations().detect(function(i) {
					return (i.constructor == rio.BenchmarkWaypoint || i.constructor == rio.BenchmarkCount) && i.getMethodName() == obj;
				});
			} else {
				return this.getInstallations().detect(function(i) { return i.getObject() == obj && i.getMethodName() == methodName });
			}
		},
		
		clear: function() {
			this.getInstallations().clear();
		},
		
		toString: function() {
			return "Benchmark";
		}
	}
}))();

rio.BenchmarkInstallation = rio.Attr.create({
	attrAccessors: [
		"object", 
		"objectString", 
		"methodName",
		["installed", false],
		["enabled", true],
		["invocations", 0], 
		["time", 0]
	],
	
	methods: {
		incrementInvocations: function() {
			this.setInvocations(this.getInvocations() + 1);
		},
		
		addTime: function(time) {
			this.setTime(this.getTime() + time);
		},
		
		reset: function() {
			this.setInvocations(0);
			this.setTime(0);
		},

		doesApply: function() {
			return rio.Benchmark.getStarted() && this.getEnabled();
		},

		install: function() {
			if (this.getInstalled()) { return; }
			this._oldMethod = this.getObject()[this.getMethodName()];
			this.getObject()[this.getMethodName()] = this.getObject()[this.getMethodName()].wrap(function() {
				var args = $A(arguments);
				var proceed = args.shift();
				
				if (this.doesApply()) {
					if (!this._inLoop) {
						this._inLoop = true;
						var t = new Date().getTime();
					}
				}
				var result = proceed.apply(this, args);

				if (this.doesApply()) {
					if (t) {
						this._inLoop = false;
						this.addTime(new Date().getTime() - t);
					}
					this.incrementInvocations();
				}
				
				return result;
			}.bind(this));
			this.setInstalled(true);
		},
		
		uninstall: function() {
			this.getObject()[this.getMethodName()] = this._oldMethod;
			this.setInstalled(false);
			
			var index = rio.Benchmark.getInstallations().indexOf(this);
			rio.Benchmark.getInstallations().splice(index, 1);
		},
		
		isRemovable: function() {
			return true;
		}
	}
});

rio.BenchmarkWaypoint = rio.Attr.create({
	attrAccessors: [
		"objectString", 
		"methodName",
		["installed", true],
		["enabled", true],
		["invocations", 0], 
		["time", 0]
	],
	
	methods: {
		addTime: function(time) {
			this.setTime(this.getTime() + time);
		},

		reset: function() {
			this.setInvocations(0);
			this.setTime(0);
		},

		doesApply: function() {
			return rio.Benchmark.getStarted() && this.getEnabled();
		},
	
		_nesting: 0,
		begin: function() {
			if (this.doesApply()) {
				if (this._nesting == 0) {
					this._beginTime = new Date().getTime();
				}
				this._nesting++;
				this.setInvocations(this.getInvocations() + 1);
			}
		},
		
		end: function() {
			if (this.doesApply()) {
				this._nesting--;
				if (this._nesting == 0) {
					this.addTime(new Date().getTime() - this._beginTime);
				}
			}
		},

		isRemovable: function() {
			return false;
		}
	}
});


rio.BenchmarkCount = rio.Attr.create({
	attrAccessors: [
		"objectString", 
		"methodName",
		["installed", true],
		["enabled", true],
		["invocations", 0], 
		["time", 0]
	],
	
	methods: {
		increment: function() {
			if (this.doesApply()) {
				this.setInvocations(this.getInvocations() + 1);
			}
		},
		
		doesApply: function() {
			return rio.Benchmark.getStarted() && this.getEnabled();
		},
		
		reset: function() {
			this.setInvocations(0);
		},

		isRemovable: function() {
			return false;
		}
	}
});