/**
 * #dependsFile client/zarafa/core/data/MAPIStore.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.MAPISubStore
 * @extends Zarafa.core.data.NoSyncStore
 */
Zarafa.core.data.MAPISubStore = Ext.extend(Zarafa.core.data.NoSyncStore, {
	/**
	 * The {@link Zarafa.core.data.MAPIRecord MAPIRecord} that is the parent of this store.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	parentRecord: null,

	/**
	 * @cfg {Zarafa.core.data.JsonReader} reader The {@link Zarafa.core.data.JsonReader Reader} object which processes the
	 * data object and returns an Array of {@link Zarafa.core.data.MAPIRecord MAPIRecord} objects which are cached keyed by their
	 * <b><tt>{@link Zarafa.core.data.MAPIRecord#id id}</tt></b> property.
	 */
	reader : undefined,

	/**
	 * @cfg {Zarafa.core.data.JsonWriter} writer
	 * <p>The {@link Zarafa.core.data.JsonWriter Writer} object which processes a record object for being written
	 * to the server-side database.</p>
	 * <br><p>When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
	 * events on the store are monitored in order to remotely {@link #createRecords create records},
	 * {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.</p>
	 */
	writer : undefined,

	/**
	 * @cfg {Boolean} persistentFilter True when the {@link #filter} which
	 * has been applied on this store should be reapplied when the store
	 * has been {@link #load loaded}
	 */
	persistentFilter : true,

	/**
	 * The currently active function which was given to {@link #filterBy}.
	 * @property
	 * @type Function
	 * @private
	 */
	filterFn : undefined,

	/**
	 * The currently active {@link #filterFn function} scope which was given to {@link #filterBy}.
	 * @property
	 * @type Object
	 * @private
	 */
	filterScope : undefined,

	/**
	 * @constructor
	 * @param config Configuration structure
	 */
	constructor : function(config)
	{
		config = config || {};

		// If the reader is provided, but not the recordType, we can obtain
		// the recordType from the reader.
		if (config.reader) {
			if (!config.recordType) {
				config.recordType = config.reader.recordType;
			}
		}

		this.addEvents(
			/**
			 * @event load
			 * Fires after a new set of Records has been loaded.
			 * @param {Store} this
			 * @param {Ext.data.Record[]} records The Records that were loaded
			 * @param {Object} options The loading options that were specified (see {@link #load} for details)
			 */
			'load',
			/**
			 * @event datachanged
			 * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.)
			 * and a widget that is using this Store as a Record cache should refresh its view.
			 * @param {Store} this
			 */
			'datachanged'
		);

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

	/**
	 * Called when member is added on store. Should not be used directly.
	 * It's called by Store#add automatically
	 * @param {Store} store
	 * @param {Ext.data.Record/Ext.data.Record[]} record
	 * @param {Number} index
	 * @private
	 */
	createRecords : function(store, record, index)
	{
		var parentRecord = this.getParentRecord();
		if(parentRecord) {
			// this will add parent record to modified array of associated store
			// and will mark the record as dirty.
			parentRecord.afterEdit();
		}

		Zarafa.core.data.MAPISubStore.superclass.createRecords.call(this, store, record, index);
	},

	/**
	 * Get the {@link Zarafa.core.data.MAPIRecord IPMRecord} that is the parent of this store.
	 * @return {Zarafa.core.data.MAPIRecord} The parent IPMRecord.
	 */
	getParentRecord : function()
	{
		return this.parentRecord;
	},

	/**
	 * Set the {@link Zarafa.core.data.MAPIRecord IPMRecord} that is the parent of this store.
	 * @param {Zarafa.core.data.MAPIRecord} record The parent IPMRecord.
	 */
	setParentRecord : function(record)
	{
		this.parentRecord = record;
	},

	/**
	 * Get the Record with the specified id.
	 * If the {@link #reader} has the {@link Ext.data.JsonReader#idProperty} set to 'entryid',
	 * then this function will also use {@link Zarafa.core.EntryId#compareEntryIds}. For
	 * 'store_entryid' then {@link Zarafa.core.EntryId#compareStoreEntryIds} is used.
	 * @param {String} id The id of the Record to find.
	 * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
	 */
	getById : function(id)
	{
		// First use the original implementation
		var item = Zarafa.core.data.MAPISubStore.superclass.getById.call(this, id);

		// If no item was found, and the reader uses the 'entryid' property,
		// we should retry searching using the compareEntryIds function. If that
		// fails as well, then the item is really not present.
		if (!item) {
			var index = -1;
			if (this.reader.meta.idProperty === 'entryid') {
				index = this.findBy(function(record) { return Zarafa.core.EntryId.compareEntryIds(id, record.id); });
			} else if (this.reader.meta.idProperty === 'store_entryid') {
				index = this.findBy(function(record) { return Zarafa.core.EntryId.compareStoreEntryIds(id, record.id); });
			}

			if (index >= 0) {
				item = this.getAt(index);
			}
		}

		return item;
	},

	/**
	 * Filter by a function. Returns a <i>new</i> collection that has been filtered.
	 * The passed function will be called with each object in the collection.
	 * If the function returns true, the value is included otherwise it is filtered.
	 * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
	 * @return {MixedCollection} The new filtered collection
	 */
	filterBy : Zarafa.core.data.MAPIStore.prototype.filterBy,

	/**
	 * Revert to a view of the Record cache with no filtering applied.
	 * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
	 * {@link #datachanged} event.
	 */
	clearFilter : Zarafa.core.data.MAPIStore.prototype.clearFilter,

	/**
	 * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
	 * which understands the format of the data must have been configured in the constructor.
	 * @param {Object} data The data block from which to read the Records.  The format of the data expected
	 * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
	 * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
	 * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
	 * the existing cache.
	 * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
	 * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
	 * new, unique ids will be added.
	 */
	loadData : function(o, append)
	{
		if (Ext.isDefined(this.reader)) {
			var r = this.reader.readRecords(o);
			this.loadRecords(r, {add: append}, true);
		}
	},
 
	/**
	 * Callback function which will be called when 'read' action is executed 
	 * and {@link Zarafa.core.data.JsonReader JsonReader} has deserialized data
	 * into {@link Zarafa.core.data.MAPIRecord MAPIRecord},
	 * so the records can be added to the {@link Zarafa.core.data.NoSyncStore NoSyncStore}.
	 * @param {Object} o response object containing array of {@link Zarafa.core.data.MAPIRecord MAPIRecord}
	 * and optionally a property indicating total number of records.
	 * @param {Object} options optionally can contain 'add' which will append {@link Zarafa.core.data.MAPIRecord MAPIRecord}
	 * to the existing set of cached {@link Zarafa.core.data.MAPIRecord MAPIRecord}.
	 * @private
	 */
	loadRecords : function(o, options, success)
	{
		if (this.isDestroyed === true) {
			return;
		}
		if (!o || success === false) {
			if(success !== false) {
				this.fireEvent('load', this, [], options);
			}
			return;
		}
		var r = o.records, t = o.totalRecords || r.length;
		if (!options || options.add !== true) {
			for (var i = 0, len = r.length; i < len; i++) {
				r[i].join(this);
			}

			if(this.snapshot){
				this.data = this.snapshot;
				delete this.snapshot;
			}
			this.clearData();
			this.data.addAll(r);
			this.totalLength = t;
			this.fireEvent('datachanged', this);
		} else {
			this.totalLength = Math.max(t, this.data.length+r.length);
			this.add(r);
		}
		this.fireEvent('load', this, r, options);

		if (this.persistentFilter === true) {
			if (this.filterFn) {
				this.filterBy(this.filterFn, this.filterScope);
			}
		}
	},

	/**
	 * Sort the data in the store using the given sort function.
	 * This will call {@link Ext.util.MixedCollection#sort sort} on the
	 * {@link #data} object.
	 * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
	 * @param {Function} fn (optional) Comparison function that defines the sort order. Defaults to sorting by numeric value.
	 */
	sortBy : function(direction, fn)
	{
		this.data.sort(direction, fn);
		if (this.snapshot && this.snapshot != this.data) {
			this.snapshot.sort(direction, fn);
		}
		this.fireEvent('datachanged', this);
	}
});
