Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.RecordDefinition
 * @extends Ext.util.Observable
 * The definition for a {@link Ext.data.Record record}
 * for a particular message class. Each message class can have its
 * own set of fields and default values. Within this class, the
 * specific values are kept. By default all fields and default values
 * are inherited from the parent definition. So each definition only
 * contains the data which is new for this particular message class.
 *
 * NOTE: This class should never be used outside of the
 * {@link Zarafa.core.data.RecordFactory RecordFactory}.
 */
Zarafa.core.data.RecordDefinition = Ext.extend(Ext.util.Observable, {
	/**
	 * @cfg {Zarafa.core.data.RecordDefinition} parent The parent record definition for this definition.
	 */
	parent : undefined,

	/**
	 * The object class definition for the record.
	 * This type is always a subclass of the {@link Zarafa.core.data.RecordDefinition.base base},
	 * field within this {@link Zarafa.core.data.RecordDefinition definition}.
	 * @property
	 * @type Class
	 * @private
	 */
	type: undefined,

	/**
	 * The configuration object in where all options for the Record instances are configured.
	 *
	 * This is editable as long as no record has been created using this definition yet, after
	 * that this property can no longer be used.
	 * @property
	 * @type Object
	 * @private
	 */
	cfg : undefined,

	/**
	 * @cfg {Object} base The object class definition which must be used as base for the record.
	 * In most cases this base must be extended from {@link Ext.data.Record record}.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	base: undefined,

	/**
	 * @cfg {Object} subStores The key-value array where for each subStore type is provided with the type
	 * of the {@link Zarafa.core.data.SubStore SubStore} which must be used for the given subStore.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	subStores : undefined,

	/**
	 * @cfg {Ext.data.Field[]} fields The array of fields which belong to this {@link Ext.data.Record record}.
	 * This is mapped on the server to MAPI properties which are stored in the zarafa-server.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	fields: undefined,

	/**
	 * @cfg {Object} defaults The key-value array where the default values for particular fields is provided.
	 * When a new {@link Ext.data.Record record} is created (one that does
	 * not yet exist on the server) all default values will be applied to the new
	 * {@link Ext.data.Record record}.
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	defaults: undefined,

	/**
	 * @cfg {Object} createDefaults The key-value array for default values that should be applied
	 * when a record is created (either a phantom or non-phantom).
	 *
	 * This is applied on the {@link #cfg} object and can be changed as long as no record has been
	 * created using this definition yet, after that this property can no longer be used.
	 */
	createDefaults : undefined,

	/**
	 * @constructor
	 * @param {Object} config Configuration object
	 */
	constructor : function(config)
	{
		this.addEvents([
			/**
			 * @event createphantom
			 * Fires when the {@link Zarafa.core.data.RecordFactory RecordFactory} has
			 * created a new phantom record.
			 * @param {Ext.data.Record} record The created phantom record
			 * @param {Object} data The data used to initialize the record
			 */
			'createphantom',
			/**
			 * @event createrecord
			 * Fires when the {@link Zarafa.core.data.RecordFactory RecordFactory} has
			 * created a new record.
			 * @param {Ext.data.Record} record The created record
			 * @param {Object} data The data used to initialize the record
			 */
			'createrecord'
		]);

		// Only the parent is applied on the current
		// class. the rest can be applied to the cfg object.
		this.parent = config.parent;
		delete config.parent;

		this.cfg = Ext.apply({}, config);

		Zarafa.core.data.RecordDefinition.superclass.constructor.call(this, config);
	},

	/**
	 * Create a new {@link Ext.data.Record record} object
	 * based on this {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * The data object used to instantiate the new Record will be created using the
	 * default values from {@link Ext.data.Field field} definitions as obtained by
	 * {@link #getFieldDefaultValues}, the default values configured through
	 * the {@link Zarafa.core.data.RecordFactory Record Factory} which are obtained
	 * by the {@link #getDefaultValues} function. And finally the data passed as
	 * argument will be applied. This ordering implies that the {@link Ext.data.Field field}
	 * default values are most likely to be overridden by the other values.
	 *
	 * @param {Object} data (Optional) An object, the properties of which provide
	 * values for the new Record's fields. If not specified the {@link Ext.data.Field.defaultValue}
	 * for each field will be assigned.
	 * @param {Object} id (Optional) The id of the Record. The id is used by the
	 * {@link Ext.data.Store Store} object which owns the {@link Ext.data.Record}
	 * to index its collection of Records (therefore this id should be unique within
	 * each store). If an id is not specified a phantom Record will be created with an
	 * automatically generated id.
	 * @return {Ext.data.Record} the new record which was created
	 */
	createRecord : function(data, id)
	{
		var RecordClass = this.getType();
		var applyData = {};
		Ext.apply(applyData, this.getFieldDefaultValues());
		if (!Ext.isDefined(id)) {
			Ext.apply(applyData, this.getDefaultValues());
		}
		if (data) {
			Ext.apply(applyData, data);
		}

		// Apply the createDefaults object to initialize the record with.
		var record = new RecordClass(applyData, id, this);

		if (record.phantom) {
			this.fireEvent('createphantom', record, data);
		} else {
			// field default values will be applied by the jsonreader
			this.fireEvent('createrecord', record, data);
		}

		// All changes which were made should be committed,
		// as only after this function returns should the changes
		// for the record be kept.
		record.commit();

		return record;
	},

	/**
	 * Obtain the {@link #type object class} definition which can be used for constructing
	 * new {@link Ext.data.Record records}. The type will be a subclass of
	 * {@link Zarafa.core.data.RecordDefinition#base base}.
	 * @return {Class} The object class definition for this record definition
	 */
	getType : function()
	{
		if (!Ext.isDefined(this.type)) {
			var baseClass = this.getBaseClass();
			var fields = this.getFields();
			this.type = Zarafa.core.data.Record.create(fields, baseClass);
		}
		return this.type;
	},

	/**
	 * Set the {@link #base} class for the {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {Class} baseClass The base class to set.
	 */
	setBaseClass : function(baseClass)
	{
		this.cfg.base = baseClass;
	},

	/**
	 * Obtain the {@link #base} class which must be used when creating a new record
	 * from this {@link Zarafa.core.data.RecordDefinition definition}. If
	 * no base class is yet provided, this function will call recursively
	 * into its {@link #parent parents} until a valid base class has been found.
	 * @return {Class} The base class to be used for creating a new record.
	 * @private
	 */
	getBaseClass : function()
	{
		if (!Ext.isDefined(this.base)) {
			if (this.cfg.base) {
				this.base = this.cfg.base;
			} else if (Ext.isDefined(this.parent)) {
				this.base = this.parent.getBaseClass();
			} else {
				this.base = Ext.data.Record;
			}
		}
		return this.base;
	},

	/**
	 * Set the type which must be used for a particular SubStore.
	 * When a SubStore type has been set on a RecordType, then the
	 * subStore is considered 'supported' by the Record.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {String} name The name of the SubStore (This matches the name how the
	 * data for this subStore is send through Json).
	 * @param {Type} type The Object type which must be used to allocate the subStore
	 */
	setSubStore : function(name, type)
	{
		this.cfg.subStores = Ext.value(this.cfg.subStores, {});
		this.cfg.subStores[name] = type;
	},

	/**
	 * Obtain the key-value array of subStore types which are active for
	 * this {@link Zarafa.core.data.RecordDefinition definition}. This object
	 * is used to {@link Zarafa.core.data.MAPIRecord#setSubStoreTypes set} on
	 * the {@link Zarafa.core.data.MAPIRecord MAPIRecord} which is created.
	 * @return {Object} key-value array of all subStore Types
	 * @private
	 */
	getSubStores : function()
	{
		if (!this.subStores) {
			this.subStores = {};
			if (Ext.isDefined(this.parent)) {
				Ext.apply(this.subStores, this.parent.getSubStores());
			}
			Ext.apply(this.subStores, this.cfg.subStores);
		}

		return Ext.apply({}, this.subStores);
	},

	/**
	 * Add a {@link Ext.data.Field field} to the {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {Ext.data.Field} field The field to add.
	 */
	addField : function(field)
	{
		this.cfg.fields = Ext.value(this.cfg.fields, []);

		if (!Array.isArray(field)) {
			this.cfg.fields.push(field);
		} else {
			this.cfg.fields = this.cfg.fields.concat(field);
		}
	},

	/**
	 * Obtain the list of {@link Ext.data.Field fields} which are active
	 * for this {@link Zarafa.core.data.RecordDefinition definition}.
	 * These fields must be used to create a record, to indicate which
	 * {@link Ext.data.Field fields} are allowed.
	 * This function will recursively call into its parents to obtain
	 * all fields from the parents.
	 *
	 * @return {Ext.data.Field[]} All fields which are active for
	 * this definition.
	 * @private
	 */
	getFields : function()
	{
		if (!this.fields) {
			this.fields = [];

			if (Ext.isDefined(this.parent)) {
				this.fields = this.fields.concat(this.parent.getFields());
			}
			if (this.cfg.fields) {
				this.fields = this.fields.concat(this.cfg.fields);
			}
		}

		return this.fields;
	},

	/**
	 * Add a default value for a {@link Ext.data.Field field} to the
	 * {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * NOTE: If {@link #createRecord} has been called, then changes made by this function will
	 * no longer have effect.
	 *
	 * @param {Ext.data.Field} field The field for which the default value applies
	 * @param {Mixed} value The default value for the field
	 */
	addDefaultValue : function(field, value)
	{
		this.cfg.defaults = Ext.value(this.cfg.defaults, {});
		var name = Ext.isString(field) ? field : field.name;
		this.cfg.defaults[name] = value;
	},

	/**
	 * Apply all default values for the {@link Ext.data.Field field}
	 * which have been set for the {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * This function will recursively call into its parents to apply
	 * all default values from the parents.
	 * @private
	 */
	getDefaultValues : function()
	{
		if (!this.defaults) {
			this.defaults = {};

			if (Ext.isDefined(this.parent)) {
				Ext.apply(this.defaults, this.parent.getDefaultValues());
			}

			if (this.cfg.defaults) {
				Ext.apply(this.defaults, this.cfg.defaults);
			}
		}

		return this.defaults;
	},

	/**
	 * Apply all default values from the {@link Ext.data.Field field}
	 * definitions. These are applied by the {@link Ext.data.DataReader DataReader}
	 * when reading {@link Ext.data.Record records} from the server, but not for
	 * {@link Ext.data.Record#phantom phantom} records.
	 * @private
	 */
	getFieldDefaultValues : function()
	{
		if (!this.fieldDefaults) {
			var fields = this.getFields();

			this.fieldDefaults = {};
			for (var i = 0, len = fields.length; i < len; i++) {
				var field = fields[i];
				var value = field.defaultValue;
				if (!Ext.isDefined(value)) {
					value = Ext.data.Field.prototype.defaultValue;
				}

				this.fieldDefaults[field.name] = value;
			}
			Ext.apply(this.fieldDefaults, this.cfg.createDefaults);
		}

		return this.fieldDefaults;
	},

	/**
	 * Fires the specified event with the passed parameters (minus the event name).
	 *
	 * This function will recursively call into its parents to fire the event
	 * from the parents.
	 * @param {String} eventName The name of the event to fire.
	 * @param {Object...} args The arguments for the event
	 * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
	 */
	fireEvent : function()
	{
		if (Ext.isDefined(this.parent)) {
			this.parent.fireEvent.apply(this.parent, arguments);
		}
		Zarafa.core.data.RecordDefinition.superclass.fireEvent.apply(this, arguments);
	}
});