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

/**
 * @class Zarafa.core.data.RecordFactory
 * The factory class for creating objects of {@link Ext.data.Record record}
 * which are specialized for certain messageclasses (IPM.Note, IPM.Contact, etc).
 * The exact field definition of the different {@link Ext.data.Record records}
 * can be configured by {@link Zarafa.core.Context contexts} and {@link Zarafa.core.Plugin plugins}
 * to add new fields, but also to control the default values for particular fields.
 *
 * For fine-grained control over all classes, the factory also supports
 * {@link Ext.data.Record record} for sub messageclasses like (IPM.Note.NDR),
 * where the sub messageclass depends on its parent (IPM.Note.NDR inherits its fields, and
 * default values from IPM.Note).
 *
 * For plugins with custom types (no official MAPI ObjectType, or Message Class definition),
 * we have the {@link Zarafa.core.data.RecordCustomObjectType Custom Type} definitions to which
 * they can register their custom type, which can then be used to work together with
 * the record factory.
 *
 * @singleton
 */
Zarafa.core.data.RecordFactory = {
	/**
	 * Key-value map of {@link Zarafa.core.data.RecordDefinition} as registered by
	 * {@link #createRecordDefinition}. 
	 *
	 * @property
	 * @type Object
	 */
	definitions : {},

	/**
	 * Obtain the parent messageclass for the given messageclass.
	 * The hierarchy of the messageclass is based on the dot ('.')
	 * character. Each dot indicates a step deeper into the hierarchy.
	 * Thus if the messageclass is IPM.Note, the parent is IPM.
	 *
	 * @param {String} messageClass The messageclass for which
	 * the parent is requested.
	 * @return {String} The parent messageclass (undefined, if
	 * the messageclass has no parent.
	 * @private
	 */
	getMessageClassParent : function(messageClass)
	{
		var lastDot = messageClass.lastIndexOf('.');
		if (lastDot > 0) {
			return messageClass.substr(0, lastDot);
		}
	},

	/**
	 * Obtain the parent {@link Zarafa.core.data.RecordDefinition RecordDefinition}
	 * for this messageclass.
	 * @param {String} The messageclass for the parent is searched for
	 * @return {Zarafa.core.data.RecordDefinition} The parent record definition.
	 * Undefined if the messageclass does not have a parent.
	 */
	getMessageClassParentDefinition : function(messageClass)
	{
		var messageClassParent = this.getMessageClassParent(messageClass);

		if (Ext.isDefined(messageClassParent)) {
			return this.getRecordDefinitionByMessageClass(messageClassParent);
		} 
	},

	/**
	 * Create a new {@link Zarafa.core.data.RecordDefinition RecordDefinition}
	 * for the given key value.
	 *
	 * @param {Mixed} key The key value on how the definition is stored
	 * in the definitions table.
	 * @param {Zarafa.core.data.RecordDefinition} parent The parent record
	 * definition.
	 * @param {Object} defaults The default values which must be applied to
	 * the record definition.
	 * @return {Zarafa.core.data.RecordDefinition} The new record definition.
	 */
	createRecordDefinition : function(key, parent, defaults)
	{
		var definition = new Zarafa.core.data.RecordDefinition({
			base : undefined,
			parent : parent,
			createDefaults : defaults
		});

		this.definitions[key] = definition;
		return definition;
	},

	/**
	 * Obtain the {@link Zarafa.core.data.RecordDefinition RecordDefinition} object
	 * which belongs to the given custom type. If the custom type is previously unknown
	 * and the {@link Zarafa.core.data.RecordDefinition definition} does not exist,
	 * a new {@link Zarafa.core.data.RecordDefinition definition} will be created and
	 * returned.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type for which the definition is requested.
	 * @return {Zarafa.core.data.RecordDefinition} The definition object for the custom type
	 * @private
	 */
	getRecordDefinitionByCustomType : function(customType)
	{
		var definition = this.definitions[customType];
		if (Ext.isDefined(definition)) {
			return definition;
		}

		// Custom types don't have any hierarchy...
		return this.createRecordDefinition(customType, undefined);
	},

	/**
	 * Obtain the {@link Zarafa.core.data.RecordDefinition RecordDefinition} object
	 * which belongs to the given object class. If the object type is previously unknown
	 * and the {@link Zarafa.core.data.RecordDefinition definition} does not exist,
	 * a new {@link Zarafa.core.data.RecordDefinition definition} will be created and
	 * returned.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type for which the definition is requested.
	 * @return {Zarafa.core.data.RecordDefinition} The definition object for the object type
	 * @private
	 */
	getRecordDefinitionByObjectType : function(objectType)
	{
		var definition = this.definitions[objectType];
		if (Ext.isDefined(definition)) {
			return definition;
		}

		// Object types don't have any hierarchy...
		return this.createRecordDefinition(objectType, undefined, {'object_type' : objectType});
	},

	/**
	 * Obtain the {@link Zarafa.core.data.RecordDefinition RecordDefinition} object
	 * which belongs to the given messageclass. If the messageClass is previously unknown
	 * and the {@link Zarafa.core.data.RecordDefinition definition} does not exist,
	 * a new {@link Zarafa.core.data.RecordDefinition definition} will be created and
	 * returned.
	 *
	 * @param {String} messageClass The messageclass for which the definition is requested.
	 * @return {Zarafa.core.data.RecordDefinition} The definition object for the messageclass. 
	 * @private
	 */
	getRecordDefinitionByMessageClass : function(messageClass)
	{
		var keyName = messageClass.toUpperCase();
		var definition = this.definitions[keyName];
		var parent;

		if (Ext.isDefined(definition)) {
			return definition;
		}

		parent = this.getMessageClassParentDefinition(keyName);
		if (!Ext.isDefined(parent)){
			/* - If record contains message_class which has no IPM as prefix or suffix followed by dot(.)
			 * like MEMO, REPORT etc, which are not supported by webapp and no fields are available as well (fields
			  * are not registered by any record in webapp) hence record is treated as faulty message in webapp.
			 *
			 * - Webapp can create such records as Ext.data.Record, without throwing any warning or error,
			 * this particular record not able to use Zarafa.core.data.IPMRecord functions.
			 *
			 * - To Fix this, If webapp found any message_class of record which has no IPM as prefix or
			 * suffix followed by dot(.), then by default we set the IPM record definition as parent of this record
			 * definition, further webapp will treat this message as faulty message.
			 */
			parent = this.definitions["IPM"];
		}
		return this.createRecordDefinition(keyName, parent, {'message_class' : messageClass});
	},

	/**
	 * Create a new {@link Ext.data.Record record} object based on the
	 * definitions given for the custom type.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type for which
	 * the record is created.
	 * @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
	 */
	createRecordObjectByCustomType : function(customType, data, id)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		return definition.createRecord(data, id);
	},

	/**
	 * Create a new {@link Ext.data.Record record} object based on the
	 * definitions given for the object type.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type for which
	 * the record is created.
	 * @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
	 */
	createRecordObjectByObjectType : function(objectType, data, id)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		return definition.createRecord(data, id);
	},

	/**
	 * Create a new {@link Ext.data.Record record} object based on the
	 * definitions given for the messageclass.
	 *
	 * @param {String} messageClass The messageclass for which the record is created.
	 * @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
	 */
	createRecordObjectByMessageClass : function(messageClass, data, id)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		return definition.createRecord(data, id);
	},

	/**
	 * Create a new {@link Ext.data.Record record} object based on the
	 * definitions from the record data, this will determine if
	 * the messageclass of object type has been provided. If neither has been provided
	 * this function will return undefined.
	 *
	 * @param {Object} recordData The record data from which the record class must be detected
	 * it slaos contains the properties of which provide values for the new Record's fields. 
	 * @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 {Class} The record class definition
	 */
	createRecordObjectByRecordData : function(recordData, id)
	{
		if (!Ext.isEmpty(recordData.message_class)) {
			return this.createRecordObjectByMessageClass(recordData.message_class, recordData, id);
		} else if (!Ext.isEmpty(recordData.object_type)) {
			return this.createRecordObjectByObjectType(recordData.object_type, recordData, id);
		}
	},

	/**
	 * Get the record class definition for this custom type
	 * This class definition can be used to construct new {@link Ext.data.Record record}
	 * objects which is completely tuned for the given object type.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The object type for which the
	 * record class is requested
	 * @return {Class} The record class definition
	 */
	getRecordClassByCustomType : function(customType)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		return definition.getType();
	},

	/**
	 * Get the record class definition for this object type
	 * This class definition can be used to construct new {@link Ext.data.Record record}
	 * objects which is completely tuned for the given object type.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type for which the
	 * record class is requested
	 * @return {Class} The record class definition
	 */
	getRecordClassByObjectType : function(objectType)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		return definition.getType();
	},

	/**
	 * Get the record class definition for this messageclass
	 * This class definition can be used to construct new {@link Ext.data.Record record}
	 * objects which is completely tuned for the given messageclass.
	 *
	 * @param {String} messageClass The messageclass for which the record class is requested
	 * @return {Class} The record class definition
	 */
	getRecordClassByMessageClass : function(messageClass)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		return definition.getType();
	},

	/**
	 * Get the record class definition for this record data, this will determine if
	 * the messageclass of object type has been provided. If neither has been provided
	 * this function will return undefined.
	 * This class definition can be used to construct new {@link Ext.data.Record record}
	 * objects which is completely tuned for the given messageclass.
	 *
	 * @param {Object} recordData The record data from which the record class must be detected
	 * @return {Class} The record class definition
	 */
	getRecordClassByRecordData : function(recordData)
	{
		if (Ext.isDefined(recordData.message_class)) {
			return this.getRecordClassByMessageClass(recordData.message_class);
		} else if (Ext.isDefined(recordData.object_type)) {
			return this.getRecordClassByObjectType(recordData.object_type);
		}
	},

	/**
	 * This will set a new base class on the {@link Zarafa.core.data.RecordDefinition definition}.
	 * The base class is used when creating a new record, by default the ExtJS
	 * {@link Ext.data.Record record} will be used as base.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type for which
	 * the new base class must be set.
	 * @param {Class} baseClass The new base class to use
	 */
	setBaseClassToCustomType : function(customType, baseClass)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		definition.setBaseClass(baseClass);
	},

	/**
	 * This will set a new base class on the {@link Zarafa.core.data.RecordDefinition definition}.
	 * The base class is used when creating a new record, by default the ExtJS
	 * {@link Ext.data.Record record} will be used as base.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type for which
	 * the new base class must be set.
	 * @param {Class} baseClass The new base class to use
	 */
	setBaseClassToObjectType : function(objectType, baseClass)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		definition.setBaseClass(baseClass);
	},

	/**
	 * This will set a new base class on the {@link Zarafa.core.data.RecordDefinition definition}.
	 * The base class is used when creating a new record, by default the ExtJS
	 * {@link Ext.data.Record record} will be used as base.
	 *
	 * @param {String} messageClass The messageclass for which
	 * the new base class must be set.
	 * @param {Class} baseClass The new base class to use
	 */
	setBaseClassToMessageClass : function(messageClass, baseClass)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		definition.setBaseClass(baseClass);
	},

	/**
	 * This will set the SubStore type for a particular subStore name
	 * in the {@link Zarafa.core.data.RecordDefinition definition}. Inside
	 * a SubStore a Table can be loaded like Attachments, recipients, members, etc.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type for which the
	 * SubStore type must be set
	 * @param {String} name The name of the SubStore table
	 * @param {Constructor} type The SubStore type
	 */
	setSubStoreToCustomType : function(customType, name, type)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		definition.setSubStore(name, type);
	},

	/**
	 * This will set the SubStore type for a particular subStore name
	 * in the {@link Zarafa.core.data.RecordDefinition definition}. Inside
	 * a SubStore a Table can be loaded like Attachments, recipients, members, etc.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type for which the
	 * SubStore type must be set
	 * @param {String} name The name of the SubStore table
	 * @param {Constructor} type The SubStore type
	 */
	setSubStoreToObjectType : function(objectType, name, type)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		definition.setSubStore(name, type);
	},

	/**
	 * This will set the SubStore type for a particular subStore name
	 * in the {@link Zarafa.core.data.RecordDefinition definition}. Inside
	 * a SubStore a Table can be loaded like Attachments, recipients, members, etc.
	 *
	 * @param {String} messageClass The messageclass for which
	 * @param {String} name The name of the SubStore table
	 * @param {Constructor} type The SubStore type
	 */
	setSubStoreToMessageClass : function(messageClass, name, type)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		definition.setSubStore(name, type);
	},

	/**
	 * This will add a new {@link Ext.data.Field field} to the
	 * {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type to which the field
	 * should be added.
	 * @param {String/Ext.data.Field} field The field which must be added.
	 */
	addFieldToCustomType : function(customType, field)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		definition.addField(field);
	},

	/**
	 * This will add a new {@link Ext.data.Field field} to the
	 * {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type to which the field
	 * should be added.
	 * @param {String/Ext.data.Field} field The field which must be added.
	 */
	addFieldToObjectType : function(objectType, field)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		definition.addField(field);
	},

	/**
	 * This will add a new {@link Ext.data.Field field} to the
	 * {@link Zarafa.core.data.RecordDefinition definition}.
	 *
	 * @param {String} messageClass The messageclass to which the field
	 * should be added.
	 * @param {String/Ext.data.Field} field The field which must be added.
	 */
	addFieldToMessageClass : function(messageClass, field)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		definition.addField(field);
	},

	/**
	 * This will add a default value for a {@link Ext.data.Field field}
	 * to the {@link Zarafa.core.data.RecordDefinition definition}. When
	 * creating a new local item
	 * ({@link Zarafa.core.data.RecordFactory.createRecordObjectByCustomType createRecordObjectByCustomType}
	 * with the phantom argument to true) the default values which be applied
	 * to the newly created {@link Ext.data.Record record}.
	 *
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type to which the field belongs
	 * @param {String/Ext.data.Field} field The field for which the default
	 * value should be applied
	 * @param {Mixed} defaultValue the default value for the given fieldname
	 */
	addDefaultValueToCustomType : function(customType, field, defaultValue)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		definition.addDefaultValue(field, defaultValue);
	},

	/**
	 * This will add a default value for a {@link Ext.data.Field field}
	 * to the {@link Zarafa.core.data.RecordDefinition definition}. When
	 * creating a new local item
	 * ({@link Zarafa.core.data.RecordFactory.createRecordObjectByObjectType createRecordObjectByObjectType}
	 * with the phantom argument to true) the default values which be applied
	 * to the newly created {@link Ext.data.Record record}.
	 *
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type to which the field belongs
	 * @param {String/Ext.data.Field} field The field for which the default
	 * value should be applied
	 * @param {Mixed} defaultValue the default value for the given fieldname
	 */
	addDefaultValueToObjectType : function(objectType, field, defaultValue)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		definition.addDefaultValue(field, defaultValue);
	},

	/**
	 * This will add a default value for a {@link Ext.data.Field field}
	 * to the {@link Zarafa.core.data.RecordDefinition definition}. When
	 * creating a new local item
	 * ({@link Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass createRecordObjectByMessageClass}
	 * with the phantom argument to true) the default values which be applied
	 * to the newly created {@link Ext.data.Record record}.
	 *
	 * @param {String} messageClass The messageclass to which the field belongs
	 * @param {String/Ext.data.Field} field The field for which the default
	 * value should be applied
	 * @param {Mixed} defaultValue the default value for the given fieldname
	 */
	addDefaultValueToMessageClass : function(messageClass, field, defaultValue)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		definition.addDefaultValue(field, defaultValue);
	},

	/**
	 * Appends an event handler to the {@link Zarafa.core.data.RecordDefinition RecordDefinition}
	 * for the given CustomType.
	 * @param {Zarafa.core.data.RecordCustomObjectType} customType The custom type to which the event must be added
	 * @param {String} eventName The name of the event to listen for
	 * @param {Function} handler The method the event invokes
	 * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed.
	 * If omitted, defaults to the object which fired the event.
	 * @param {Object} options (optional) An object containing handler configuration properties.
	 */
	addListenerToCustomType : function(customType, eventName, handler, scope, options)
	{
		var definition = this.getRecordDefinitionByCustomType(customType);
		definition.on(eventName, handler, scope, options);
	},

	/**
	 * Appends an event handler to the {@link Zarafa.core.data.RecordDefinition RecordDefinition}
	 * for the given ObjectType.
	 * @param {Zarafa.core.mapi.ObjectType} objectType The object type to which the event must be added
	 * @param {String} eventName The name of the event to listen for
	 * @param {Function} handler The method the event invokes
	 * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed.
	 * If omitted, defaults to the object which fired the event.
	 * @param {Object} options (optional) An object containing handler configuration properties.
	 */
	addListenerToObjectType : function(objectType, eventName, handler, scope, options)
	{
		var definition = this.getRecordDefinitionByObjectType(objectType);
		definition.on(eventName, handler, scope, options);
	},

	/**
	 * Appends an event handler to the {@link Zarafa.core.data.RecordDefinition RecordDefinition}
	 * for the given messageClass.
	 * @param {String} messageClass The messageclass to which the event must be added
	 * @param {String} eventName The name of the event to listen for
	 * @param {Function} handler The method the event invokes
	 * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed.
	 * If omitted, defaults to the object which fired the event.
	 * @param {Object} options (optional) An object containing handler configuration properties.
	 */
	addListenerToMessageClass : function(messageClass, eventName, handler, scope, options)
	{
		var definition = this.getRecordDefinitionByMessageClass(messageClass);
		definition.on(eventName, handler, scope, options);
	}
};