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

/**
 * @class Zarafa.hierarchy.data.IPFSubStore
 * @extends Zarafa.core.data.IPFStore
 * @xtype zarafa.ipfsubstore
 *
 * {@link Zarafa.hierarchy.data.IPFSubStore} holds {@link Zarafa.hierarchy.data.MAPIFolderRecords} as records, which defines store information
 * of all opened stores(own, shared, public)
 */
Zarafa.hierarchy.data.IPFSubStore = Ext.extend(Zarafa.core.data.IPFStore, {
	/**
	 * The {@link Zarafa.core.data.MAPIRecord MAPIRecord} that is the parent of this store.
	 * @property
	 * @type Zarafa.core.data.MAPIRecord
	 */
	parentRecord: null,

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

		var recordType = Zarafa.core.data.RecordFactory.getRecordClassByObjectType(Zarafa.core.mapi.ObjectType.MAPI_FOLDER);

		Ext.applyIf(config, {
			proxy: new Zarafa.hierarchy.data.HierarchyProxy(),
			writer : new Zarafa.core.data.JsonWriter(),
			reader: new Zarafa.core.data.JsonReader({}, recordType)
		});

		Zarafa.hierarchy.data.IPFSubStore.superclass.constructor.call(this, config);

		this.on({
			'beforesave' : this.onBeforeSave,
			'exception' : this.onException,
			scope : this
		});
	},

	// some ugly duplication of code between MAPISubStore and IPFSubStore

	/**
	 * Called when member is added on store. Should not be used directly.
	 * It's called by Store#add automatically
	 * @FIXME now IPFSubStore is not child of NoSyncStore, 
	 * so we don't need to mark parentrecord dirty, remove this.
	 * @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.hierarchy.data.IPFSubStore.superclass.createRecords.call(this, store, record, index);
	},

	/**
	 * Destroys a record or records. Should not be used directly.
	 * It's called by Store#remove automatically
	 * @param {Store} store
	 * @param {Ext.data.Record/Ext.data.Record[]} record
	 * @param {Number} index
	 * @private
	 */
	destroyRecord : function(store, record, index)
	{
		if(this.getParentRecord()){
			this.getParentRecord().markDirty();
		}
		Zarafa.hierarchy.data.IPFSubStore.superclass.destroyRecord.call(this, store, record, index);
	},

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

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

	/**
	 * Event handler will be called when an error/exception is occured at server side,
	 * and error is returned.
	 * @param {Zarafa.core.data.MAPIProxy} proxy object that received the error
	 * and which fired exception event.
	 * @param {String} type 'request' if an invalid response from server recieved,
	 * 'remote' if valid response received from server but with succuessProperty === false.
	 * @param {String} action Name of the action {@link Ext.data.Api.actions}.
	 * @param {Object} options The options for the action that were specified in the request.
	 * @param {Object} response response received from server depends on type.
	 * @param {Mixed} args
	 */
	onException : function(proxy, type, action, options, response, args)
	{
		if (Ext.isDefined(args) && Array.isArray(args.sendRecords)) {
			Ext.each(args.sendRecords, function(record) {
				// Clear all previous message actions
				record.clearMessageActions();
				record.reject();
			});
		}
	},

	/**
	 * Checks whether any of the stores that were included in the parameters during the last load, 
	 * matches the supplied entryid argument.
	 *
	 * @param {String|Array} entryidList Entryid of the folder
	 * @return {Boolean} Returns true when entryid matches, false when it does not.
	 */
	containsStoreInLastLoad: function(entryidList)
	{
		if (!Array.isArray(entryidList)) {
			entryidList = [ entryidList ];
		}

		if (this.parentRecord) {
			var entryId = this.parentRecord.get('store_entryid');

			for (var i = 0, len = entryidList.length; i < len; i++) {
				if (Zarafa.core.EntryId.compareStoreEntryIds(entryId, entryidList[i])) {
					return true;
				}
			}
		}

		return false;
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectDeleted objectDeleted}
	 * notification has been recieved.
	 *
	 * This will remove the folder from the store.
	 *
	 * @param {Zarafa.core.data.Notifications} action The notification action
	 * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification.
	 * @param {Object} data The data which has been recieved from the PHP-side which must be applied
	 * to the given records.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received
	 * @param {Boolean} success The success status, True if the notification was successfully recieved.
	 * @private
	 */
	onNotifyObjectdeleted : function(action, records, data, timestamp, success)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		// To prevent the Deletion to be saved back to the server again,
		// we mark all the records which we are about to delete as phantom.
		for (var i = 0, len = records.length; i < len; i++) {
			records[i].setEventPropagation(false);
			records[i].phantom = true;
		}
		
		this.remove(records);
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectModified objectModified}
	 * notification has been recieved.
	 *
	 * This will update the folder in the store.
	 *
	 * @param {Zarafa.core.data.Notifications} action The notification action
	 * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification.
	 * @param {Object} data The data which has been recieved from the PHP-side which must be applied
	 * to the given records.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received
	 * @param {Boolean} success The success status, True if the notification was successfully recieved.
	 * @private
	 */
	onNotifyObjectmodified : function(action, records, data, timestamp, success)
	{
		if (!Array.isArray(records)) {
			records = [ records ];
		}

		// Temporarily disable event propagation, every store (which contains the provided records)
		// will receive this notification. So we have to disable event propagation to prevent
		// bouncing events around.
		for (var i = 0, len = records.length; i < len; i++) {
			var record = records[i];
			var singleData =  (Array.isArray(data)) ? data[i] : data;

			record.setEventPropagation(false);

			if (singleData instanceof Ext.data.Record) {
				// Merge the changes into the record without using the JSONReader. 
				record.applyData(singleData);
			} else {
				// Simply merge the record using the JsonReader, this will cause a 'update' event to be fired with
				// a COMMIT action. Because it is a commit, this store will not mark the record as dirty.
				this.reader.update(record, singleData);
			}
			
			record.setEventPropagation(true);
		}
	},

	/**
	 * Notification handler called by {@link #onNotify} when
	 * a {@link Zarafa.core.data.Notifications#objectCreated objectCreated}
	 * notification has been recieved.
	 *
	 * This will add the folder to the store.
	 *
	 * @param {Zarafa.core.data.Notifications} action The notification action
	 * @param {Ext.data.Record/Array} records The record or records which have been affected by the notification.
	 * @param {Object} data The data which has been recieved from the PHP-side which must be applied
	 * to the given records.
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the notification was received
	 * @param {Boolean} success The success status, True if the notification was successfully recieved.
	 * @private
	 */
	onNotifyObjectcreated : function(action, records, data, timestamp, success)
	{
		if (!Array.isArray(data)) {
			data = [ data ];
		}

		var isFavoritesStoreInstance = this instanceof Zarafa.common.favorites.data.MAPIFavoritesSubStore;

		if (data[0] instanceof Ext.data.Record) {
			this.loadRecords({ records : data }, { add : true });
		} else if(!isFavoritesStoreInstance) {
			// Don't go for loadData if store is MAPIFavoritesSubStore because we are never
			// send non Ext.data.Record in server side notification.
			this.loadData({ item : data }, true);
		}
	},

	/**
	 * Saves all pending changes to the store.  If the commensurate Ext.data.Api.actions action is not configured, then
	 * the configured <code>{@link #url}</code> will be used.
	 * <pre>
	 * change            url
	 * ---------------   --------------------
	 * removed records   Ext.data.Api.actions.destroy
	 * phantom records   Ext.data.Api.actions.create
	 * {@link #getModifiedRecords modified records}  Ext.data.Api.actions.update
	 * </pre>
	 * @TODO:  Create extensions of Error class and send associated Record with thrown exceptions.
	 * e.g.:  Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
	 * @return {Number} batch Returns a number to uniquely identify the "batch" of saves occurring. -1 will be returned
	 * if there are no items to save or the save was cancelled.
	 */
	save : function()
	{
		// If the removed array is non-empty shared stores are being closed
		// shared stores get a special treatmeent
		if (this.removed.length) {
			var removed = [];
			var sharedfolders = [];

			for (var i = 0, len = this.removed.length; i < len; i++) {
				var item = this.removed[i];

				if (item.isSharedFolder() && !item.isFavoritesFolder()) {
					sharedfolders.push(item);
				} else {
					removed.push(item);
				}
			}

			if (!Ext.isEmpty(sharedfolders)) {
				var data = {
					'close' : sharedfolders
				};

				if (this.fireEvent('beforesave', this, data) !== false) {
					try {
						var batch = ++this.batchCounter;
						this.execute('destroy', data['close'], { actionType : 'closesharedfolder' }, batch);
					} catch (e) {
						this.handleException(e);
					}

					// Continue with the filtered list
					// of removed folders.
					this.removed = removed;
				}
			}
		}

		Zarafa.hierarchy.data.IPFSubStore.superclass.save.apply(this, arguments);
	},

	/**
	 * Event handler which is fired when Folder changes are about to be saved.
	 * This will check if a record is being destroyed, if that is a shared folder,
	 * then the settings will be updated to save the state.
	 * @param {Ext.data.Store} store The store which fired the event.
	 * @param {Object} data The data object containing the various changes
	 * which will be send to the server
	 * @private
	 */
	onBeforeSave : function(store, data)
	{
		// The 'close' key is only used for shared stores/folders
		if (!Ext.isEmpty(data['close'])) {
			var settings = container.getSettingsModel();
			var folders = data['close'];

			settings.beginEdit();

			for (var i = 0, len = folders.length; i < len; i++) {
				var sharedfolder = folders[i];
				var user = sharedfolder.getMAPIStore().get('user_name').toLowerCase();
				var type = sharedfolder.getSharedFolderKey();

				// Remove this specific folder from the settings
				// to prevent it from being loaded during the next
				// reload.
				settings.remove('zarafa/v1/contexts/hierarchy/shared_stores/' + user + '/' + type);

				// The server doesn't need the entryids to be sent,
				// only the user_name and folder_type.
				sharedfolder.addIdProp('user_name');
				sharedfolder.addIdProp('folder_type');
				sharedfolder.removeIdProp('parent_entryid');
				sharedfolder.removeIdProp('store_entryid');

				// Now we do have to check, as the 2 id properties are not defined
				// in the Record Fields array. So we insert them into the record.
				sharedfolder.set('user_name', user);
				sharedfolder.set('folder_type', type);
			}

			settings.endEdit();
		}
	}
});

Ext.reg('zarafa.ipfsubstore', Zarafa.hierarchy.data.IPFSubStore);
