/*
 * We depend on IPMRecipientRecord rather the RecordCustomObjectType
 * as ZARAFA_RECIPIENT is defined in IPMRecipientRecord.
 * #dependsFile client/zarafa/core/data/IPMRecipientRecord.js
 */
Ext.namespace('Zarafa.core.data');

/**
 * @class Zarafa.core.data.IPMRecipientStore
 * @extends Zarafa.core.data.MAPISubStore
 */
Zarafa.core.data.IPMRecipientStore = Ext.extend(Zarafa.core.data.MAPISubStore, {
	/**
	 * The proxy that handles the resolving requests.
	 * @property
	 * @type Zarafa.core.data.IPMRecipientResolveProxy
	 */
	resolveProxy: undefined,

	/**
	 * The {@link Ext.data.DataReader} used for handling resolving responses
	 * @property
	 * @type Ext.data.DataReader
	 */
	resolveReader: undefined,

	/**
	 * @cfg {Boolean} autoResolve True to enable automatic {@link #resolve resolving} of
	 * recipients when they are added into the store. Defaults to true.
	 */
	autoResolve : true,

	/**
	 * @cfg {Zarafa.core.data.RecordCustomObjectType} customObjectType The custom object type
	 * which represents the {@link Ext.data.Record records} which should be created using
	 * {@link Zarafa.core.data.RecordFactory#createRecordObjectByCustomType}.
	 */
	customObjectType : Zarafa.core.data.RecordCustomObjectType.ZARAFA_RECIPIENT,

	/**
	 * @cfg {Boolean} allowResolvingToLocalContacts True to allow recipients to resolve to local
	 * contacts as well. If set to false it can only be resolved to GAB users. This information is
	 * send to the server with every resolve request done from this store. Defaults to true.
	 */
	allowResolvingToLocalContacts: true,

	/**
	 * @cfg {Boolean} allowResolvingToGABGroups True to allow recipients to resolve to GAB groups
	 * as well. If set to false it can cannot resolve to groups like "Everyone". This information is
	 * send to the server with every resolve request done from this store. Defaults to true.
	 */
	allowResolvingToGABGroups: true,

	/**
	 * The proxy that handles the expand requests.
	 * @property
	 * @type Zarafa.core.data.IPMRecipientResolveProxy
	 */
	expandProxy: undefined,

	/**
	 * The {@link Zarafa.core.data.JsonReader} used for handling expand responses
	 * @property
	 * @type Ext.data.DataReader
	 */
	expandReader: undefined,

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

		Ext.applyIf(config, {
			// provide a default writer
			writer : new Zarafa.core.data.JsonRecipientWriter(),
			// provide a default reader
			reader : new Zarafa.core.data.JsonRecipientReader({
				customObjectType : config.customObjectType || this.customObjectType
			})
		});

		this.addEvents(
			/**
			 * @event beforeresolve
			 * Fires when a resolve request has been sent to the server.
			 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
			 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records that have been send for resolving
			 */
			'beforeresolve',
			/**
			 * @event resolved
			 * Fires when the server has returned a request for resolving records.
			 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
			 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records that have been send for resolving
			 */
			'resolved',
			/**
			 * @event exception
			 * Fires if an exception occurs in the Proxy during a remote request.
			 * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
			 * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
			 * for additional details.
			 * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
			 * for description.
			 */
			'exception'
		);

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

		this.resolveProxy = new Zarafa.core.data.IPMRecipientResolveProxy();
		this.expandProxy = new Zarafa.core.data.IPMExpandDistlistProxy();

		this.relayEvents(this.resolveProxy,  ['exception']);

		this.resolveReader = new Ext.data.JsonReader({
			root: 'result'
		}, Zarafa.core.data.IPMRecipientResolveRecord);

		this.expandReader = new Zarafa.core.data.JsonReader({
			root: 'result',
			id : 'entryid',
			dynamicRecord : false
		}, Zarafa.core.data.IPMExpandDistlistRecord);

		this.on({
			'add' : this.onRecipientAdd,
			'update' : this.onRecipientUpdate,
			'exception' : this.onResolveException,
			scope: this
		});
	},

	/**
	 * Parse a String into a {@link Zarafa.core.data.IPMRecipientRecord}.
	 *
	 * @param {String} str The string to parse
	 * @param {Zarafa.core.mapi.RecipientType} type The recipientType which must be applied to the recipient
	 * @return {Zarafa.core.data.IPMRecipientRecord} The created recipient
	 */
	parseRecipient : function(str, type)
	{
		var recipient = Zarafa.core.data.RecordFactory.createRecordObjectByCustomType(this.customObjectType);
		var mailStart = str.indexOf('<');
		var mailEnd = str.indexOf('>');

		if ((mailStart != -1 && mailEnd == -1) ||
			(mailStart == -1 && mailEnd != -1)) {
				return null;
		}

		recipient.beginEdit();
		if (mailStart != -1) {
			recipient.set('display_name', str.substring(0, mailStart).trim());
			recipient.set('smtp_address', str.substring(mailStart + 1, mailEnd).trim());
		} else {
			recipient.set('display_name', str);

			/* The string may be just a user@domain.com, if so then we can already fill in the SMTP address */
			if (!Zarafa.core.Util.validateEmailAddress(str)) {
				recipient.set('smtp_address', '');
			} else {
				recipient.set('smtp_address', str);
				recipient.set('object_type',Zarafa.core.mapi.ObjectType.MAPI_MAILUSER);
			}
		}

		recipient.set('recipient_type', type || Zarafa.core.mapi.RecipientType.MAPI_TO);
		recipient.endEdit();
		return recipient;
	},

	/**
	 * Obtain the list of currently {@link Zarafa.core.data.IPMRecipientRecord#isResolved resolved}
	 * {@link Zarafa.core.data.IPMRecipientRecord recipients}.
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records (optional) The list of records which
	 * must be filtered. If not provided, {@link #getRange} is assumed.
	 * @return {Zarafa.core.data.IPMRecipientRecord[]} The list of resolved recipients
	 */
	getResolvedRecipients : function(records)
	{
		var resolved = [];

		records = records || this.data.items;

		for (var i = 0, len = records.length; i < len; i++) {
			var recipient = records[i];
			if (recipient.isResolved()) {
				resolved.push(recipient);
			}
		}

		return resolved;
	},

	/**
	 * Obtain the list of currently {@link Zarafa.core.data.IPMRecipientRecord#isResolved non-resolved}
	 * {@link Zarafa.core.data.IPMRecipientRecord recipients}. We also filter the list with recipients
	 * which have not yet been {@link Zarafa.core.data.IPMRecipientRecord#attemptedToResolve attempted}
	 * to resolve to prevent too much traffic to the server.
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records (optional) The list of records which
	 * must be filtered. If not provided, {@link #getRange} is assumed.
	 * @return {Zarafa.core.data.IPMRecipientRecord[]} The list of non-resolved recipients
	 */
	getUnresolvedRecipients : function(records)
	{
		var unresolved = [];

		records = records || this.data.items;

		for (var i = 0, len = records.length; i < len; i++) {
			var recipient = records[i];
			if (!recipient.isResolved() && (!recipient.attemptedToResolve() || recipient.isAmbiguous())) {
				unresolved.push(recipient);
			}
		}

		return unresolved;
	},

	/**
	 * Obtain the list of currently {@link Zarafa.core.data.IPMRecipientRecord which are invalid}
	 * {@link Zarafa.core.data.IPMRecipientRecord recipients}.reciepitns which do not have valid
	 * SMTP address neither have a entryid and have been attempted to be resolved
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records (optional) The list of records which
	 * must be filtered. If not provided, {@link #getRange} is assumed.
	 * @return {Zarafa.core.data.IPMRecipientRecord[]} The list of invalid recipients
	 */
	getInvalidRecipients : function(records)
	{
		var invalid = [];

		records = records || this.data.items;

		for (var i = 0, len = records.length; i < len; i++) {
			var recipient = records[i];
			if (!recipient.isResolved() && recipient.attemptedToResolve() && !recipient.isValidSMTP()) {
				invalid.push(recipient);
			}
		}

		return invalid;
	},

	/**
	 * Resolve the given {@link Zarafa.core.data.IPMRecipientRecord IPMRecipientRecord IPMRecipientRecords}
	 * @param {Zarafa.core.data.IPMRecipientRecord/Array} records (optional) The record or records to resolve,
	 * if nothing is provided, all {@link #getUnresolvedRecipients unresolved} recipients will be resolved.
	 * @param {Object} options (optional) Additional options
	 */
	resolve : function(records, options)
	{
		if (Ext.isDefined(records) && !Array.isArray(records)) {
			records = [ records ];
		}

		records = this.getUnresolvedRecipients(records);
		if (records.length === 0) {
			return;
		}

		var pendingRecords = [];
		var resolveRequests = [];
		// Get the records from the passed recipients that need resolving
		for (var i = 0; i < records.length; i++) {
			var recipientRecord = records[i];

			var displayName = recipientRecord.get('display_name').trim();
			var smtpAddress = recipientRecord.get('smtp_address').trim();
			var addressType = recipientRecord.get('address_type');

			/*
			 * Sometimes emailaddress in not there in the property so it will return undefined
			 * So doing trim() on undefined will return error, This is happening with mails
			 * migrated from lotus notes, and this might happen, if we migrate for other applications
			 */
			var emailAddress = recipientRecord.get('email_address');
			if (!Ext.isEmpty(emailAddress)) {
				emailAddress = emailAddress.trim();
			}

			// Prefer sending the email_address property,
			// if not set send smtp address but only if it is a valid address.
			if (Ext.isEmpty(emailAddress)) {
				if (Zarafa.core.Util.validateEmailAddress(smtpAddress)) {
					emailAddress = smtpAddress;
				} else {
					emailAddress = '';
				}
			}

			resolveRequests.push({
				id: recipientRecord.id,
				display_name : displayName,
				email_address : emailAddress,
				address_type : addressType
			});

			pendingRecords.push(recipientRecord);
		}

		// Setup the parameters that will make up the resolve request to the server
		var parameters = {
			resolverequests: resolveRequests,
			exclude_local_contacts: !this.allowResolvingToLocalContacts,
			exclude_gab_groups: !this.allowResolvingToGABGroups
		};

		// The arguments that can be used in the callback function
		var args = Ext.apply({}, options, {
			actionType: Zarafa.core.Actions['checknames'],
			listRequest : true,
			pendingRecords: pendingRecords,
			params: parameters
		});

		this.fireEvent('beforeresolve', this, pendingRecords);

		this.resolveProxy.request(Zarafa.core.Actions['checknames'], records, parameters, this.resolveReader, this.onCheckNamesResult, this, args);
	},

	/**
	 * Callback function from the {@link Zarafa.core.data.IPMRecipientResolveResponseHandler CheckNamesResponseHandler},
	 * which will be called when the response has been read. Each returned resolve response will be
	 * handled separately. If for a resolve request one result is returned the data is applied to
	 * the recipient directly. If multiple results are returned a dialog is opened where the user can
	 * select the correct one.
	 * {@link Zarafa.common.Actions.openCheckNamesContent openCheckNamesContent} will be called to
	 * open a content panel to let the user choose to what entry the recipient should be resolved. If there
	 * are no results returned the user is notified of that fact.
	 * @param {Ext.data.Record/Array} records The record or records which have been received from the server.
	 * @param {Object} options The options object used for the request
	 * @param {Boolean} success True if the request was successfull.
	 * @private
	 */
	onCheckNamesResult: function(records, options, success)
	{
		if (!Array.isArray(records)) {
			records = [records];
		}

		for (var i = 0; i < records.length; i++) {
			var resolveResult = records[i];
			// Check if we have the ID of the IPMRecipientRecord
			if (!Ext.isEmpty(resolveResult.id)) {
				// Get the RecipientRecord using the recordID
				var recipientRecord = this.getById(resolveResult.id);

				// When the recipientRecord can not be found, skip this one
				if (!recipientRecord) {
					break;
				}

				// When only one match is found we can apply it directly
				if (resolveResult.result.totalRecords == 1) {
					var checknamesRecord = resolveResult.result.records[0];
					// Apply resolving data
					recipientRecord.applyResolveRecord(checknamesRecord, true);

				// Display the checknames dialog to let the user choose what of the multiple matches he wants
				} else if (resolveResult.result.totalRecords > 1) {
					recipientRecord.resolveAttemptAmbiguous = true;
					Zarafa.common.Actions.openCheckNamesContent(resolveResult.result.records, recipientRecord);
				}

				// Set the resolveAttempted property
				recipientRecord.resolveAttempted = true;
			}
		}

		this.fireEvent('resolved', this, options.pendingRecords);
	},

	/**
	 * Expand the given {@link Zarafa.core.data.IPMRecipientRecord IPMRecipientRecord}
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The record to expand.
	 * @param {Boolean} recurse True if we want expand a distribution list in a distribution list.
	 * @param {Object} options (optional) Additional options
	 */
	expand : function(record, recurse, options)
	{
		// Setup the parameters that will make up the expand request to the server
		var parameters = {
			entryid: record.get('entryid'),
			recurse: recurse || false
		};

		// The arguments that can be used in the callback function
		var args = Ext.apply({}, options, {
			actionType: Zarafa.core.Actions['expand'],
			listRequest : true,
			recipientType : record.get('recipient_type'),
			params: parameters
		});

		this.expandProxy.request(Zarafa.core.Actions['expand'], record, parameters, this.expandReader, this.onExpandResult, this, args);
	},

	/**
	 * Callback function from the {@link Zarafa.core.data.IPMExpandDistlistResponseHandler ExpandDistlistResponseHandler},
	 * which will be called when the response has been read. Each returned expand response will be
	 * handled separately. Resulted member record(s) are converted to the recipient record and added to the recipient store.
	 * @param {Ext.data.Record/Array} records The record or records which have been received from the server.
	 * @param {Object} options The options object used for the request
	 * @param {Boolean} success True if the request was successfull.
	 * @private
	 */
	onExpandResult: function(records, options, success)
	{
		if (success) {
			if (!Array.isArray(records)) {
				records = [records];
			}

			var recipients = [];

			for (var i = 0; i < records.length; i++) {
				var memberRecord = records[i];

				if (!Ext.isEmpty(memberRecord)) {
					recipients.push(memberRecord.convertToRecipient(options.recipientType));
				}
			}

			// Add all the member records into the recipient store
			this.add(recipients);
		}
	},

	/**
	 * Event handler which is fired when a recipient is added to this store, when {@link #autoResolve}
	 * is enabled and the records are dirty, the records will directly be resolved.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records which were added
	 * @private
	 */
	onRecipientAdd : function(store, records)
	{
		if (this.autoResolve) {
            Ext.each(records,function(record){
                    if(record.dirty){
                        this.resolve(records);
                        return false;
                    }
            },this);
        }
	},

	/**
	 * Event handler which is fired when a recipient is updated inside this store, when {@link #autoResolve}
	 * is enabled, the records will be re-resolved.
	 * @param {Zarafa.core.data.IPMRecipientStore} store The store which fired the event
	 * @param {Zarafa.core.data.IPMRecipientRecord[]} records The records which were updated
	 * @private
	 */
	onRecipientUpdate : function(store, records)
	{
		if (this.autoResolve) {
			// The PresenceManager can also fire the 'update' event for records
			// in a IPMRecipientStore. Because records are not modified then, we
			// do not have to resolve them.
			var modifiedRecords = [];
			if ( !Array.isArray(records) ){
				records = [records];
			}
			Ext.each( records, function(record){
				if ( record.isModified() ){
					modifiedRecords.push(record);
				}
			});

			this.resolve(modifiedRecords);
		}
	},

	/**
	 * Called when an error occurred on the server-side.
	 * @param {Zarafa.core.data.IPMRecipientResolveProxy} proxy The proxy which fired the event.
	 * @param {String} type The value of this parameter will be either 'response' or 'remote'.
	 * @param {String} action Name of the action (see {@link Ext.data.Api#actions}).
	 * @param {Object} options The object containing the object which was send to the PHP-side.
	 * @param {Object} response The response object as received from the PHP-side
	 */
	onResolveException: function(proxy, type, action, options, response)
	{
		this.fireEvent('resolved', this, options.pendingRecords);
	},

	/**
	 * Function which is use to verified that any recipient from the list of {@link Zarafa.core.data.IPMRecipientRecord}
	 * has recipient type {@link Zarafa.core.mapi.RecipientType.MAPI_BCC}.
	 * @param {Array} recipients (optional) The list of {@link Zarafa.core.data.IPMRecipientRecord}.
	 * If not provided, {@link #getRange} is assumed.
	 * @return {Boolean} True if any recipient from the given recipients list has recipient type
	 * {@link Zarafa.core.mapi.RecipientType.MAPI_BCC}.
	 */
	hasBccRecipients : function(recipients)
	{
		var hasBcc = false;

		recipients = recipients || this.data.items;

		for (var i = 0, len = recipients.length; i < len; i++) {
			var recipient = recipients[i];
			if (recipient.get('recipient_type') === Zarafa.core.mapi.RecipientType.MAPI_BCC) {
				hasBcc = true;
				break;
			}
		}

		return hasBcc;
	},

	/**
	 * Check the given recipient was exists in recipient store.
	 *
	 * @param {Zarafa.core.data.IPMRecipientRecord} record The recipient record which need to check.
	 * @param {Ext.Function} callbackFn (optional) The callbackFn function used to check the give recipient
	 * is already exists in recipient store.
	 * @param {Object} scope (optional) The scope in which given callbackFn will be executed.
	 * @returns {boolean} true if recipient already exists in recipient store else false.
	 */
	isRecipientExists : function(record, callbackFn, scope)
	{
		var records = this.getRange();
		if (Ext.isFunction(callbackFn)) {
			return records.some(callbackFn, scope || this);
		}

		var entryid = record instanceof Ext.data.Record ? record.get('entryid') : record['entryid'];
		return records.some(function(recipient) {
			if (recipient.isOneOff()) {
				return recipient.get('entryid') === entryid;
			}
			return Zarafa.core.EntryId.compareEntryIds(recipient.get('entryid'), entryid);
		}, scope || this);
	}
});
