/**
 * System calculation class, given a new construction.
 */
function Calculator(construction)
{
	/***** SHARED CALCULATIONS HELPERS *****/
	
	/**
	 * Calculate the lifetime of the construction based on the normal method (constant weights).
	 * @param a (sub)construction object.
	 * @return the lifetime, if defined.
	 */
	this.calculate_lifetime_real = function(a_construction) {
		var facts = a_construction.get_data_facts();
		var lifetime = facts.std_lifetime;
		var round_lifetime = facts.round_lifetime_to;
		
		// Return if undefined.
		if (lifetime === undefined || round_lifetime === undefined) {
			return undefined;
		}
		
		// Create the product from specified constants.
		// Equation 1.
		for (var key in facts.constants) {
			if(facts.constants.hasOwnProperty(key) === true) {
				var setting = a_construction.get_setting(key);
				lifetime *= facts.constants[key][setting.get_active_value()];
			}
		}
		
		// Round the lifetime.
		// Equation 2.
		return round_lifetime * Math.round(lifetime / round_lifetime);
	};
	
	/**
	 * Calculate the lifetime of the construction with conditions.
	 * @param a (sub)construction object.
	 * @return the lifetime, if defined.
	 */
	this.calculate_lifetime_conditions = function(a_construction) {
		var conditions = a_construction.get_data_facts().conditions;
		if (conditions === undefined || conditions.length === 0) { return undefined; }
		
		var result = undefined;
		
		for (var key = 0; key < conditions.length; key++) {
			var triggers = conditions[key].triggers;
			var triggers_passed = true;
			
			for (var trigger_key = 0; trigger_key < triggers.length; trigger_key++) {					
				var trigger = triggers[trigger_key];
				
				var current_value =
					a_construction.get_setting(trigger.field).get_active_value();
				
				var trigger_passed = false;
				for (var i in trigger.value) {
					if (trigger.value.hasOwnProperty(i) === true &&
						current_value == trigger.value[i])
					{	
						trigger_passed = true;
						break;
					}
				}
				if (trigger_passed === false) {
					triggers_passed = false;
					break;
				}
			}
			
			if (triggers_passed === true) {
				if (conditions[key].argument === undefined) {
					conditions[key].argument = { };
				}
				
				var cur_result = conditions[key].func(construction, a_construction,
													  this, conditions[key].argument);
				if (cur_result !== undefined) {
					result = cur_result;
				}
			}
		}
		
		return result;
	};
	
	/**
	 * Calculate the lifetime of a single (sub)construction.
	 * @param the construction.
	 * @return the lifetime.
	 */
	this.calculate_single_lifetime = function(a_construction) {
		var calc_lt = this.calculate_lifetime_conditions(a_construction);
		if (calc_lt === undefined) {
			calc_lt = this.calculate_lifetime_real(a_construction);
		}
		if (calc_lt === undefined || calc_lt <= 0) {
			if(a_construction.get_lifetime_correction() === 0) {
				return undefined;
			} else {
				calc_lt = 0;
			}
		}
		
		return {
			calculated:	calc_lt,
			standard:	a_construction.get_data_facts().std_lifetime,
			corrected:	calc_lt + a_construction.get_lifetime_correction()
		};
	};
	
	/***** SUBCONSTRUCTION CALCULATIONS *****/
	
	/**
	 * Calculate the costs of a subconstruction.
	 * @return object containing calculated costs.
	 */
	var calculate_subconstruction = function(a_subconstruction) {
		var maintain_default = a_subconstruction.get_data_facts().std_maintainance_percent;
		var maintain_correction = a_subconstruction.get_maintainance_percent_correction();
		var maintain_corrected = maintain_default + maintain_correction;
		
		// Equation 3
		var maintain_cost = maintain_corrected * construction.get_cost();
		
		var w_cost = a_subconstruction.get_work_cost();
		
		return {
			maintainance_default:		maintain_default,
			maintainance_correction:	maintain_correction,
			maintainance_corrected:		maintain_corrected,
			maintainance_cost:			maintain_cost,
			work_cost:					w_cost,
			total_cost:					w_cost + maintain_cost
		};
	};
	
	/***** CONSTRUCTION CALCULATIONS *****/
	
	/**
	 * Calculate the cost of a construction.
	 * @return an object containing the calculated costs.
	 */
	this.calculate_construction = function() {
		if (construction.get_cost() === 0) { return undefined; }
		var other_calculations = [ ];
		var main_lifetime = this.calculate_single_lifetime(construction);
		if (main_lifetime === undefined || main_lifetime.corrected === undefined || main_lifetime.corrected <= 0) {
			return undefined;
		}
		
		var lifetime = main_lifetime.corrected;
		if(lifetime === undefined || lifetime <= 0 || lifetime === Infinity) {
			return undefined;
		}
		var calculation_rate = construction.get_setting('calculation_rate').get_active_value();
		var accumulated_cost = construction.get_cost();
		
		// Equation 4
		var subconstructions = construction.get_subconstructions();
		for (var key in subconstructions) {
			if(subconstructions.hasOwnProperty(key) === true) {
				var lt = this.calculate_single_lifetime(subconstructions[key]);
				if (lt === undefined || lt.corrected === undefined || lt.corrected <= 0) {
					other_calculations.push({
						lifetime: undefined,
						costs: undefined
					});
					continue;
				}
				
				var cost = calculate_subconstruction(subconstructions[key]);
				
				var subcon_lifetime = lt.corrected;
				var subcon_cost_total = cost.total_cost;
				
				var year = subcon_lifetime;
				while(year <= lifetime) {
					accumulated_cost += subcon_cost_total * Math.pow(1 + calculation_rate, - year);
					year += subcon_lifetime;
				}
				
				other_calculations.push({
					lifetime: lt,
					costs: cost
				});
			}
		}
		
		accumulated_cost += construction.get_extra_cost() * construction.get_cost() * 
			Math.pow(1 + calculation_rate, - lifetime);
		
		// Equation 5
		var rate = Math.pow(1 + calculation_rate, lifetime);

		// Equation 6
		var ammortisation_constant = (rate * calculation_rate) / (rate - 1);
		// Equation 7
		var yearly = accumulated_cost * ammortisation_constant;
		
		return {
			main: {
				total_current_cost: accumulated_cost,
				yearly_cost: yearly,
				lifetime: main_lifetime
			},
			others: other_calculations
		};
	};
}

