/*!
 * Deluge.OptionsManager.js
 * 
 * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, write to:
 *     The Free Software Foundation, Inc.,
 *     51 Franklin Street, Fifth Floor
 *     Boston, MA  02110-1301, USA.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the OpenSSL
 * library.
 * You must obey the GNU General Public License in all respects for all of
 * the code used other than OpenSSL. If you modify file(s) with this
 * exception, you may extend this exception to your version of the file(s),
 * but you are not obligated to do so. If you do not wish to do so, delete
 * this exception statement from your version. If you delete this exception
 * statement from all source files in the program, then also delete it here.
 */
Ext.namespace('Deluge');

/**
 * @class Deluge.OptionsManager
 * @extends Ext.util.Observable
 * A class that can be used to manage options throughout the ui.
 * @constructor
 * Creates a new OptionsManager
 * @param {Object} config Configuration options
 */
Deluge.OptionsManager = Ext.extend(Ext.util.Observable, {
	
	constructor: function(config) {
		config = config || {};
		this.binds = {};
		this.changed = {};
		this.options = (config && config['options']) || {};
		this.focused = null;

		this.addEvents({
			/**
			 * @event add
			 * Fires when an option is added
			 */
			'add': true,

			/**
			 * @event changed
			 * Fires when an option is changed
			 * @param {String} option The changed option
			 * @param {Mixed} value The options new value
			 * @param {Mixed} oldValue The options old value
			 */
			'changed': true,

			/**
			 * @event reset
			 * Fires when the options are reset
			 */
			'reset': true
		});
		this.on('changed', this.onChange, this);

		Deluge.OptionsManager.superclass.constructor.call(this);
	},

	/**
	 * Add a set of default options and values to the options manager
	 * @param {Object} options The default options.
	 */
	addOptions: function(options) {
		this.options = Ext.applyIf(this.options, options);
	},
	
	/**
	 * Binds a form field to the specified option.
	 * @param {String} option
	 * @param {Ext.form.Field} field
	 */
	bind: function(option, field) {
		this.binds[option] = this.binds[option] || [];
		this.binds[option].push(field);
		field._doption = option;

		field.on('focus', this.onFieldFocus, this);
		field.on('blur', this.onFieldBlur, this);
		field.on('change', this.onFieldChange, this);
		field.on('check', this.onFieldChange, this);
		field.on('spin', this.onFieldChange, this);
		return field;
	},

	/**
	 * Changes all the changed values to be the default values
	 */
	commit: function() {
		this.options = Ext.apply(this.options, this.changed);
		this.reset();
	},

	/**
	 * Converts the value so it matches the originals type
	 * @param {Mixed} oldValue The original value
	 * @param {Mixed} value The new value to convert
	 */
	convertValueType: function(oldValue, value) {
		if (Ext.type(oldValue) != Ext.type(value)) {
			switch (Ext.type(oldValue)) {
				case 'string':
					value = String(value);
					break;
				case 'number':
					value = Number(value);
					break;
				case 'boolean':
					if (Ext.type(value) == 'string') {
						value = value.toLowerCase();
						value = (value == 'true' || value == '1' || value == 'on') ? true : false;
					} else {
						value = Boolean(value);
					}
					break;
			}
		}
		return value;
	},

	/**
	 * Get the value for an option or options.
	 * @param {String} [option] A single option or an array of options to return.
	 * @returns {Object} the options value.
	 */
	get: function() {
		if (arguments.length == 1) {
			var option = arguments[0];
			return (this.isDirty(option)) ? this.changed[option] : this.options[option];
		} else {
			var options = {};
			Ext.each(arguments, function(option) {
				if (!this.has(option)) return;
				options[option] = (this.isDirty(option)) ? this.changed[option] : this.options[option];
			}, this);
			return options;
		}
	},

	/**
	 * Get the default value for an option or options.
	 * @param {String|Array} [option] A single option or an array of options to return.
	 * @returns {Object} the value of the option
	 */
	getDefault: function(option) {
		return this.options[option];
	},

	/**
	 * Returns the dirty (changed) values.
	 * @returns {Object} the changed options
	 */
	getDirty: function() {
		return this.changed;
	},

	/**
	 * @param {String} [option] The option to check
	 * @returns {Boolean} true if the option has been changed from the default.
	 */
	isDirty: function(option) {
		return !Ext.isEmpty(this.changed[option]);
	},

	/**
	 * Check to see if an option exists in the options manager
	 * @param {String} option
	 * @returns {Boolean} true if the option exists, else false.
	 */
	has: function(option) {
		return (this.options[option]);
	},

	/**
	 * Reset the options back to the default values.
	 */
	reset: function() {
		this.changed = {};
	},

	/**
	 * Sets the value of specified option(s) for the passed in id.
	 * @param {String} option
	 * @param {Object} value The value for the option
	 */
	set: function(option, value) {
		if (option === undefined) {
			return;
		} else if (typeof option == 'object') {
			var options = option;
			this.options = Ext.apply(this.options, options);
			for (var option in options) {
				this.onChange(option, options[option]);
			}
		} else {
			this.options[option] = value;
			this.onChange(option, value)
		}
	},

	/**
	 * Update the value for the specified option and id.
	 * @param {String/Object} option or options to update
	 * @param {Object} [value];
	 */
	update: function(option, value) {
		if (option === undefined) {
			return;
		} else if (value === undefined) {
			for (var key in option) {
				this.update(key, option[key]);
			}
		} else {
			var defaultValue = this.getDefault(option);
			value = this.convertValueType(defaultValue, value);

			var oldValue = this.get(option);
			if (oldValue == value) return;

			if (defaultValue == value) {
				if (this.isDirty(option)) delete this.changed[option];
				this.fireEvent('changed', option, value, oldValue);
				return;
			}

			this.changed[option] = value;
			this.fireEvent('changed', option, value, oldValue);
		}
	},

	/**
	 * Lets the option manager know when a field is blurred so if a value
	 * so value changing operations can continue on that field.
	 */
	onFieldBlur: function(field, event) {
		if (this.focused == field) {
			this.focused = null;
		}
	},

	/**
	 * Stops a form fields value from being blocked by the change functions
	 * @param {Ext.form.Field} field
	 * @private
	 */
	onFieldChange: function(field, event) {
		if (field.field) field = field.field // fix for spinners
		this.update(field._doption, field.getValue());
	},

	/**
	 * Lets the option manager know when a field is focused so if a value
	 * changing operation is performed it won't change the value of the
	 * field.
	 */
	onFieldFocus: function(field, event) {
		this.focused = field;
	},

	onChange: function(option, newValue, oldValue) {
		// If we don't have a bind there's nothing to do.
		if (Ext.isEmpty(this.binds[option])) return;
		Ext.each(this.binds[option], function(bind) {
			// The field is currently focused so we don't want to 
			// change it.
			if (bind == this.focused) return;
			// Set the form field to the new value.
			bind.setValue(newValue);
		}, this);
	}
});
