var Calculator = function(){}

Calculator.instances = {};

Event.observe(document,'dom:loaded',function(){
	for ( var calculatorId in Calculator.instances ){
		Calculator.instances[calculatorId].init();
	}
});

Calculator.prototype.__construct = function(id){
	this.id = id;
	
	Calculator.instances[this.id] = this;
}

Calculator.prototype.init = function(){
	this.formElement = $(this.id);
	
	var self = this;
	this.elements = {};
	
	this.formElement.getElements().each(function(element){
		element.observe('change',self.updateValues.bind(self));
		
		self.elements[element.name] = element;
	});
	
	this.updateValues();
}

Calculator.prototype.updateValues = function(){
	var values = {};
	var formulaes = {};
	
	for( var varName in this.elements ) {
		var element = this.elements[varName];
		var formulae = element.value;
		formulaes[varName] = formulae;
		
		try {
			var x = 0;
			if ( element.hasClassName('int') ) x = parseInt(formulae);
			else if ( element.hasClassName('float') || element.hasClassName('chf') ) x = parseFloat(formulae);
			else if ( element.hasClassName('percent') ) x = parseFloat(formulae)/100;
			else {
				with ( values ) {
					eval('x = '+ formulae +';');
				}
			}
			
			values[varName] = x;
		} catch ( exception ) {
		}
	}
	
	$(this.formElement).select('.calculatorVariableOutput').each(function(outputElement){
		var variable = outputElement.readAttribute('alt');
		
		var value = values[variable];
		
		if ( outputElement.hasClassName('chf') ){
			value = 'Fr. '+ Calculator.Format.decimal(value,2,true);
		} else if ( outputElement.hasClassName('int') ){
			value = Calculator.Format.decimal(value);
		} else if ( outputElement.hasClassName('float') ){
			value = Calculator.Format.decimal(value,2,true);
		} else if ( outputElement.hasClassName('percent') ){
			value = Calculator.Format.decimal(value*100,2)+'%';
		}
		
		outputElement.update(value);
	})
	
	$(this.formElement).select('.calculatorChart').each(function(graphElement){
		ChartGraph.getInstance(graphElement.id).update(values,formulaes);
	});
}

Calculator.Format = function(){}

Calculator.Format.decimal = function(value,precision,padToPrecision){
	if ( !precision ) precision = 0;
	
	var factor = Math.pow(10,precision);
	var parts = String(Math.round(value*factor)/factor).split('.');
	
	var string = parts[0];
	var decimal = '';
	
	for( var i=0;i<string.length;i++){
		var pos = string.length-i-1;
		decimal = string.charAt(pos) + (i > 0 && i%3==0?"'":'') + decimal;
	}
	
	if ( precision > 0 ) {
		string = (parts[1]?parts[1]:'');
		
		if ( precision > string.length && padToPrecision ) string += '0'.times(precision-string.length);
		
		if ( string ) decimal += '.'+ string;
	}
	
	return decimal;
}

var ChartGraph = function(){}
ChartGraph.instances = {};
ChartGraph.getInstance = function(id){
	if ( ChartGraph.instances[id] ) return ChartGraph.instances[id];
	return null;
}

ChartGraph.prototype.__construct = function(id,config){
	this.id = id;
	this.config = config;
	
	ChartGraph.instances[this.id] = this;
	
	return this;
}

ChartGraph.prototype.update = function(values,formulaes){
	this.element = $(this.id);
	
	var endValue = values[this.config['endVariable']];
	var startValue = values[this.config['startVariable']];
	var incValue = values[this.config['incVariable']];
	var countVariable = this.config['countVariable'];
	
	var barVariables = this.config['barVariables'].split(',');
	
	var result = {};
	
	if ( startValue > endValue ) return;
	if ( incValue <= 0 ) return;
	
	for( var x = startValue; x <= endValue; x += incValue ){
		values[countVariable] = x;
		
		result[x] = new Array();
		
		barVariables.each(function(barVariable){
			with ( values ) {
				var y = 0;
				try {
					eval('y = '+ formulaes[barVariable] +';');
				} catch ( exception ) {}
				
				result[x].push(y);
			}
		});
	}
	
	this.setValues(result);
}

ChartGraph.prototype.setValues = function(values){
	var xValues = Object.keys(values);
	
	var min = [];
	var max = [];
	var cols = xValues.length;
	var bars = 0;
	
	for( var x in values ) {
		var bars = Math.max(bars,values[x].length);
		
		min.push(values[x].min());
		max.push(values[x].max());
	}
	
	min = min.min();
	max = max.max();
	
	var maxAbs = Math.round(max);
	var maxTop = Math.pow(10,String(maxAbs).length);
	var maxReducer = Math.pow(10,String(maxAbs).length-1);
	
	while ( maxTop-maxReducer >= maxAbs ) maxTop -= maxReducer;
	
	var boxElement = new Element('div',{'class':'box'});
	var colElement = new Element('div',{'class':'col'});
	var barElement = new Element('div',{'class':'bar'});
	var lineElement = new Element('div',{'class':'line'}).update(maxTop);
	
	this.element.insert(lineElement);
	var chartData = ChartGraph.getInnerDimensions(this.element,this.element.getDimensions(),false);
	var lineData = ChartGraph.getInnerDimensions(lineElement,chartData,true);
	lineData.realHeight = lineElement.getHeight();
	
	this.element.update(boxElement);
	boxElement.insert(colElement);
	boxElement.insert(barElement);
	
	var boxData = ChartGraph.getInnerDimensions(boxElement,chartData,true);
	
	var colOffsetData = {
		'width':boxData.width / cols,
		'height':boxData.height
	};
	
	var colData = ChartGraph.getInnerDimensions(colElement,colOffsetData,true);
	colData.left = 0;
	colData.top = 0;
	
	var barOffsetData = {
		'width':colData.width / bars,
		'height':colData.height
	};
	
	var barData = ChartGraph.getInnerDimensions(barElement,barOffsetData,true);
	
	barData.factor = barData.height/maxTop;
	barData.topOffset = barOffsetData.height - barData.height;
	
	this.element.setStyle({
		'position':'relative'
	});
	
	boxElement.update('');
	boxElement.setStyle({
		'width':boxData.width+'px',
		'height':boxData.height+'px',
		'position':'relative'
	});
	
	var yOffset = barData.height / lineData.realHeight;
	
	var lines = Math.floor(yOffset);
	var lineTop = boxData.offset.top.all + colData.offset.top.all + barData.offset.top.all + barData.height;
	
	if ( lineData.offset.bottom.border ) lineTop -= lineData.realHeight;
	
	var maxOffset = maxTop/(yOffset);
	
	for( var i=0; i<=lines; i++){
		var line = new Element('div',{'class':'line'}).update(Calculator.Format.decimal(i*maxOffset));
		line.setStyle({
			'position':'absolute',
			'width':(lineData.width)+'px',
			'left':'0px',
			'top':lineTop+'px'
		});
		
		lineTop -= lineData.realHeight;
		
		this.element.insert(line);
	}
	
	for( var colName in values ) {
		var colElement = new Element('div',{'class':'col'})
		.setStyle({
			'position':'absolute',
			'width':colData.width+'px',
			'height':colData.height+'px',
			'left':colData.left+'px',
			'top':colData.top+'px'
		});
		
		barData.left = 0;
		var nr = 0;
		
		values[colName].each(function(y){
			barData.height = y*barData.factor;
			barData.top = colData.height - barData.topOffset - barData.height;
			
			nr++;
			
			var barElement = new Element('div',{'class':'bar bar'+nr,'title':Calculator.Format.decimal(y,2)})
			.setStyle({
				'position':'absolute',
				'width':barData.width+'px',
				'height':barData.height+'px',
				'left':barData.left+'px',
				'top':barData.top+'px'
			});
			
			colElement.insert(barElement);
			
			barData.left += barOffsetData.width;
		});
		
		colElement.insert(new Element('span',{'class':'coltitle'}).update(Calculator.Format.decimal(colName,2)));
		boxElement.insert(colElement);
		
		colData.left += colOffsetData.width;
	}
}

ChartGraph.getInnerDimensions = function(element,outer,margin){
	var offset = function(direction){
		var m = parseInt((margin?element.getStyle('margin'+direction):0));
		var b = parseInt(element.getStyle('border'+direction+'Width'));
		var p = parseInt(element.getStyle('padding'+direction));
		
		var v = {
			'margin':(isNaN(m)?0:m),
			'border':(isNaN(b)?0:b),
			'padding':(isNaN(p)?0:p)
		};
		
		v.all = v.margin + v.border + v.padding;
		
		return v;
	}
	
	var inner = {};
	
	inner.offset = {
		'top':offset('Top'),
		'bottom':offset('Bottom'),
		'left':offset('Left'),
		'right':offset('Right')
	};
	
	inner.offset.width = inner.offset.left.all + inner.offset.right.all;
	inner.offset.height = inner.offset.top.all + inner.offset.bottom.all;
	
	inner.width = outer.width - inner.offset.width;
	inner.height = outer.height - inner.offset.height;
	
	return inner;
}