/**
* A single option in a setting
* @param the label of the option
* @param the setting of the option
*/
function Option(label, value) {
	this.get_value = function() { return value; };
	this.get_label = function() { return label; };
}

/**
* Describes a setting
* @param the config entry to bind to
* @param the name of the setting
*/
function Setting(config, name) {
	var active_option = null;
	var options = [ ];
	
	/**
	* Return the name of the setting.
	*/
	this.get_name = function() {
		return name;
	};
	
	/**
	* Set the active value for the setting.
	* @param the option id.
	*/
	this.set_active_value = function(option_id) {
		if (this.get_type() != "static") {
			if (options[option_id] === undefined) {
				throw "Invalid id '"+ option_id +"'.";
			}
		
			active_option = options[option_id];
		} else {
			active_option = new Option("", option_id);
		}
	};
	
	/**
	* Get label for the setting.
	*/
	this.get_label = function() {
		return config.label;
	};
	
	/**
	* Get all labels for the setting options.
	*/
	this.get_labels = function() {
		return config.labels;
	};
	
	/**
	* Returns the value of the currently selected option.
	*/
	this.get_active_value = function() {
		return active_option.get_value();
	};
	
	/**
	* Returns the label of the active option.
	*/
	this.get_active_label = function() {
		return active_option.get_label();
	};
	
	/**
	* Returns the type of the setting.
	*/
	this.get_type = function() {
		return config.type;
	};
	
	/**
	* Returns the options for the group.
	*/
	this.get_options = function() {
		return options;
	};
	
	// Constructor
	var default_value = config.defval;
	if (default_value === undefined) {
		default_value = 0;
	}
	
	switch (config.type) {
		case "radio":
			var count = 0;
			for (var key in config.labels) {
				if(config.labels.hasOwnProperty(key) === true) {
					options.push( new Option(config.labels[key], count++));
				}
			}
			active_option = options[default_value];
			break;
		
		case "checkbox":
			options.push(new Option("unchecked", 0));
			options.push(new Option("checked", 1));
			active_option = options[default_value];
			break;
		
		case "static":
			active_option = new Option("", config.value);
			options.push(active_option);
			break;
		
		default:
			throw "Type '"+ config.type +"' is not defined.";
	}
}

/**
* Describes a subconstruction
* @param The config entry to bind to
* @param The construction object that owns the subconstruction
*/
function SubConstruction(config, parent_construction) {
	var m_active_element = {
		element: null,
		id: null
	};
	
	var m_cost = 0;
	var m_lifetime_correction = 0;
	var m_maintainance_percent_correction = 0;
	var m_work_cost = 0;
	
	var m_name = (config !== undefined) ? config.name : "";
	
	/**
	* Set the cost.
	* @param the cost.
	*/
	this.set_cost = function(cost) {
		m_cost = cost;
	};
	
	/**
	* Set the lifetime correction.
	* @param the lifetime correction.
	*/
	this.set_lifetime_correction = function(ltc) {
		m_lifetime_correction = ltc;
	};
	
	/**
	* Set the cost correction in percent.
	* @param the cost correction.
	*/
	this.set_maintainance_percent_correction = function(maintainance_percent_correction) {
		m_maintainance_percent_correction = maintainance_percent_correction;
	};
	
	/**
	* Set the work cost.
	* @param the work cost.
	*/
	this.set_work_cost = function(work_cost) {
		m_work_cost = work_cost;
	};
	
	/**
	* Get the cost and corrections
	*/
	this.get_cost = function() { return m_cost; };
	this.get_lifetime_correction = function() { return m_lifetime_correction; };
	this.get_maintainance_percent_correction = function() { return m_maintainance_percent_correction; };
	this.get_work_cost = function() { return m_work_cost; };
	
	/**
	* Return the name of the subconstruction.
	*/
	this.get_name = function() { return m_name; };
	
	/**
	* Set the name if no config is set.
	*/
	this.set_name = function(name) {
		if (config === undefined) { m_name = name; }
	};
	
	/**
	* Get data facts.
	* @return an object containing facts.
	*/
	this.get_data_facts = function() {
		return this.get_active_element();
	};
	
	/**
	* Returns a list of the elemenets in the subconstruction.
	*/
	this.list_elements = function() {
		if (config !== undefined) {
			var elements = [ ];
			for (var key in config.elements) {
				if(config.elements.hasOwnProperty(key) === true) {
					elements.push({
						id: key,
						name: config.elements[key].name
					});
				}
			}
			return elements;
		} else {
			return { id: 0, name: "freeform" };
		}
	};
	
	/**
	* Returns the active element.
	*/
	this.get_active_element = function() {
		if (config !== undefined) {
			return m_active_element.element;
		} else {
			return {
				name: "",
				constants: { },
				std_lifetime: 0,
				round_lifetime_to: 1,
				std_maintainance_percent: 0
			};
		}
	};
	
	/**
	* Returns the active element id.
	*/
	this.get_active_element_id = function() {
		if (config !== undefined) {
			return m_active_element.id;
		} else {
			return 0;
		}
	};
	
	/**
	* Set the active element.
	* @param the element id.
	*/
	this.set_active_element = function(element_id) {
		if(config !== undefined)
		{
			if (config.elements[element_id] === undefined) {
				throw "The element with id '"+ element_id +"' was not found.";
			}
			
			m_active_element.element = config.elements[element_id];
			m_active_element.id = element_id;
		}
	};
	
	/**
	* Return a setting with a given id.
	* @param the setting id.
	* @return the setting.
	*/
	this.get_setting = function(setting_id) {
		return parent_construction.get_setting(setting_id);
	};
	
	// Constructor
	if(config !== undefined)
	{
		if (config.elements.length === 0) {
			throw "The subconstruction '" + config.name +"' has no elements.";
		}
		
		var default_element = config.default_element;
		if (default_element === undefined) {
			default_element = 0;
		}
		
		this.set_active_element(default_element);
	}
}

/**
* Creates a new construction.
* @param the configuration.
* @param reference from the parent group.
*/
function Construction(config, id, parent_group) {
	var m_subconstructions = [ ];
	
	// Constants specified by the user
	var m_cost = 0;
	var m_lifetime_correction = 0;
	var m_extra_cost = 0;
	
	var m_comment = "";
	
	var m_name = (config !== undefined) ? config.name : "";
	
	/**
	* Set the cost.
	* @param the cost.
	*/
	this.set_cost = function(cost) {
		m_cost = cost;
	};
	
	/**
	* Set the lifetime correction.
	* @param the lifetime correction.
	*/
	this.set_lifetime_correction = function(ltc) {
		m_lifetime_correction = ltc;
	};
	
	/**
	* Set the extra costs, in percent, to be added at the end of life.
	* @param the extra cost.
	*/
	this.set_extra_cost = function(extra_cost) {
		m_extra_cost = extra_cost;
	};
	
	/**
	* Set a comment on the maintainance percent correction.
	*/
	this.set_comment = function(comment) {
		m_comment = comment;
	};
	
	/**
	* Return the id of the construction from the configuration.
	*/
	this.get_id = function() { return id; };
	
	/**
	* Get the parent group.
	*/
	this.get_group = function() {
		return parent_group;
	};
	
	/**
	* Return true if the construction is freeform, false otherwice.
	*/
	this.is_freeform = function() { return (config == undefined); };
	
	/**
	* Returns the name of the construction.
	*/
	this.get_name = function() { return m_name; };
	
	/**
	* Set the name
	*/
	this.set_name = function(name) {
		if (config === undefined) { m_name = name; }
	};
	
	/**
	* Get the cost and corrections
	*/
	this.get_cost = function() { return m_cost; };
	this.get_lifetime_correction = function() { return m_lifetime_correction; };
	this.get_extra_cost = function() { return m_extra_cost; };
	
	/**
	* Get the comments.
	*/
	this.get_comment = function() {
		return m_comment;
	};
	
	this.should_show_comment = function() {
		if (this.get_lifetime_correction() != 0) return true;

		var should_show = false;
		for (var key in m_subconstructions) {
			var subcon = m_subconstructions[key];
			if (subcon.get_lifetime_correction() != 0 ||
				subcon.get_maintainance_percent_correction() != 0.0) {
				should_show = true;
				break;
			}
		}
		return should_show;
	};


	/**
	* Get data facts.
	* @return an object containing facts.
	*/
	this.get_data_facts = function() {
		if (config !== undefined) {
			return config;
		} else {
			return {
				std_lifetime: 0,
				round_lifetime_to: 1
			};
		}
	};
	
	/**
	* Return the subconstructions.
	* @return an array describing the subconstructions.
	*/
	this.get_subconstructions = function() {
		return m_subconstructions;
	};
	
	/**
	* Return a setting with a given id.
	* @param the setting id.
	* @return the setting.
	*/
	this.get_setting = function(setting_id) {
		return parent_group.get_setting(setting_id);
	};
	
	// Constructor
	if(config !== undefined) {
		for (var key in config.subconstructions) {
			if(config.subconstructions.hasOwnProperty(key) === true) {
				m_subconstructions.push(
					new SubConstruction(config.subconstructions[key], this)
				);
			}
		}
	}
	else
	{
		var num_subconstructions = 3;
		try {
			num_subconstructions = parent_group.get_setting('freeform_subconstruction_count').get_active_value();
		} catch(e) {}
		for (var i = 0; i < num_subconstructions; i++)
		{
			m_subconstructions.push(
				new SubConstruction(undefined, this)
			);
		}
	}
}

/**
 * Create a group from specified configuration.
 * @param the configuration for the group.
 * @param a reference to the project which the group is under.
 */
function Group(config, parent_project) {
	var constructions = [ ];
	var settings = { };
		
	/**
	 * Return the group id.
	 */
	this.get_id = function() {
		return config.id;
	};

	/**
	 * Return the group name.
	 */
	this.get_name = function() {
		return config.name;
	};
	
	/**
	 * Returns setting groups for the project.
	 * @return array of setting groups.
	 */
	this.get_setting_groups = function() {
		return config.setting_groups;
	};
	
	/**
	 * Return a setting with a given id.
	 * @param the setting id.
	 * @return the setting.
	 */
	this.get_setting = function(setting_id) {
		if (settings[setting_id] !== undefined) {
			return settings[setting_id];
		} else {
			return parent_project.get_setting(setting_id);
		}
	};
	
	/**
	 * Returns the local settings.
	 */
	this.get_local_settings = function() {
		return settings;
	};
	
	/**
	 * List all constructions in the group.
	 * @return object with construction id and name.
	 */
	this.list_constructions = function() {
		var c = [ ];
		for (var key in config.constructions) {
			if(config.constructions.hasOwnProperty(key)) {
				c.push({
					id: key,
					name: config.constructions[key].name
				});
			}
		}
		return c;
	};
	
	/**
	 * Add a construction.
	 * @param the construction type id.
	 * @return a construction object.
	 */
	this.add_construction = function(construction_id) {
		if (config.constructions[construction_id] === undefined)
		{
			if(construction_id != "freeform") {
				throw "The construction id '" + construction_id + "' is not defined.";
			} else {
				return this.add_freeform_construction();
			}
		}
		var construction = new Construction(config.constructions[construction_id],
											construction_id, this);
		constructions.push(construction);
		return construction;
	};
	
	/**
	 * Add a freeform construction.
	 * @param the construction type id.
	 * @return a construction object.
	 */
	this.add_freeform_construction = function() {
		var construction = new Construction(undefined, "freeform", this);
		constructions.push(construction);
		return construction;
	};
	
	/**
	 * Return a list of all created constructions in the group.
	 * @return an object with construction id and construction object.
	 */
	this.get_constructions = function(construction_id) {
		return constructions;
	};
	
	/**
	 * Removes a single, previously created, construction from the group.
	 * @return boolean true if the construction was deleted, else false.
	 */
	this.remove_construction = function(construction_ref) {
		var pos = -1;
		for(var i = 0; i < constructions.length; i++)
		{
			if(constructions[i] == construction_ref)
			{
				pos = i;
				break;
			}
		}
		if(pos === -1) { return false; }
		constructions.splice(pos, 1);
		return true;
	};
	
	// Constructor
	for (var key in config.settings) {
		if(config.settings.hasOwnProperty(key) === true)
		{
			settings[key] = new Setting(config.settings[key], key);
		}
	}
}

/**
 * Create a new project from specified configuration.
 * @param project configuration.
 */
function Project(config) {
	var groups = { };
	var settings = { };
	var general_information = {
		builder_name: { value: "", required: true },
		builder_phone: { value: "", required: true },
		builder_address: { value: "", required: true },
		builder_email: { value: "", required: true },
		builder_postal_code: { value: "", required: false },
		builder_city: { value: "", required: false },
		
		project_name: { value: "", required: true },
		project_department_name: { value: "", required: false },
		project_department_number: { value: "", required: false },
		project_organisation_name: { value: "", required: false },
		project_organisation_number: { value: "", required: false },
		project_count_housings: { value: "", required: false },
		project_count_floors: { value: "", required: false },
		project_date: { value: "", required: false },
		project_journal_number: { value: "", required: false },
		
		contact_name: { value: "", required: false },
		contact_phone: { value: "", required: false },
		contact_email: { value: "", required: false },
		
		notice: { value: "", required: false }
	};
	
	/**
	 * Returns an object containing alle general informations.
	 */
	this.export_general_information = function() {
		var informations = [ ];
		for (var key in general_information) {
			if(general_information.hasOwnProperty(key) === true) {
				informations.push({
					id: key,
					value: general_information[key].value,
					required: general_information[key].required
				});
			}
		}
		return informations;
	};
	
	/**
	* Get field from general information.
	* @param the field id.
	* @return the value.
	*/
	this.get_general_information = function(field_id) {
		if (general_information[field_id] === undefined)
		{
			throw "The id '"+ field_id +"' is not in general information.";
		}
		return general_information[field_id].value;
	};
	
	/**
	* Set a field in general information.
	* @param the field id.
	* @param the value to set into the field.
	* @return true on success, false on error.
	*/
	this.set_general_information = function(field_id, value) {
		if (general_information[field_id] !== undefined) {
			general_information[field_id].value = value;
			return true;
		} else {
			return false;
		}
	};
	
	/**
	 * Checks if all the required project information is filled.
	 * @return an array with all non-filled required fields.
	 */
	this.unfilled_required_general_information = function() {
		var ret = [ ];
		var info = this.export_general_information();
		for (var i = 0; i < info.length; i++) {
			if (info[i].value.length == 0 && info[i].required)
				ret.push(info[i]);
		}
		return ret;
	};

	/**
	* Returns setting groups for the project.
	* @return array of setting groups.
	*/
	this.get_setting_groups = function() {
		return config.setting_groups;
	};
	
	/**
	* Return a setting with a given id.
	* @param the setting id.
	* @return the setting.
	*/
	this.get_setting = function(setting_id) {
		if (settings[setting_id] === undefined)
		{
			throw "Setting with " + setting_id + " could not be found.";
		}
		
		return settings[setting_id];
	};
	
	/**
	* Returns the local settings.
	*/
	this.get_local_settings = function() {
		return settings;
	};
	
	/**
	* List groups in the project.
	* @return array of objects with group id and name
	*/
	this.list_groups = function() {
		var g = [ ];
		for (var key in config.groups) {
			if(config.groups.hasOwnProperty(key) === true)
			{
				g.push({
					id: config.groups[key].id,
					name: config.groups[key].name
				});
			}
		}
		return g;
	};
	
	/**
	* Returns a group object.
	*/
	this.get_group = function(group_id) {
		return groups[group_id];
	};
	
	/**
	* @return true if group exists, false otherwise.
	*/
	this.has_group = function(group_id) {
		return groups[group_id] !== undefined;
	};

	// Constructor
	for (var key in config.groups) {
		if(config.groups.hasOwnProperty(key) === true) {
			groups[config.groups[key].id] = new Group(config.groups[key], this);
		}
	}
	
	for (key in config.settings) {
		if(config.settings.hasOwnProperty(key) === true) {
			settings[key] = new Setting(config.settings[key], key);
		}
	}
}
