Ext.namespace('Zarafa.common.rules.dialogs');

/**
 * @class Zarafa.common.rules.dialogs.RulesConditionContainer
 * @extends Ext.Container
 * @xtype zarafa.rulesconditioncontainer
 *
 * The container in which all conditions can be edited. This container
 * can be expanded to include multiple conditions, and is able to parse
 * the rules_condition property of a {@link Zarafa.common.rules.data.RulesRecord rule}.
 */
Zarafa.common.rules.dialogs.RulesConditionContainer = Ext.extend(Ext.Container, {
	/**
	 * The current number of condition boxes which are present in the container.
	 * This number is changed by {@link #addConditionBox} and {@link #removeConditionBox}.
	 * @property
	 * @type Number
	 * @private
	 */
	conditionCount : 0,

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

		config.plugins = Ext.value(config.plugins, []);
		config.plugins.push('zarafa.recordcomponentupdaterplugin');

		Ext.applyIf(config, {
			layout : 'form',
			autoHeight: true,
			items : [{
				xtype : 'zarafa.compositefield',
				hideLabel : true,
				items : [{
					xtype : 'button',
					ref : '../addConditionBtn',
					text : _('Add condition'),
					handler : this.addConditionBox,
					scope : this
				},{
					xtype : 'button',
					ref : '../removeConditionBtn',
					text : _('Remove condition'),
					handler : this.removeConditionBox,
					scope : this
				}]
			}]
		});

		Zarafa.common.rules.dialogs.RulesConditionContainer.superclass.constructor.call(this, config);
	},

	/**
	 * Generic function to create containers in which a condition is represented. This consists of
	 * 2 components, the first one is the combobox in which the condition type is selected, and the
	 * second in which special option for the given condition can be configured.
	 * @param {Number} The index of the condition which is created
	 * @return {Object} config object to create a {@link Ext.Container}.
	 * @private
	 */
	createConditionBox : function(index)
	{
		var id =  'rule-condition-' + String(index);
		var profileStore = {
			xtype : 'jsonstore',
			fields : [
				{ name : 'name' },
				{ name : 'value', type : 'int' }
			],
			data : Zarafa.common.rules.data.ConditionProfiles
		};

		return {
			xtype : 'container',
			id : id,
			flex : 1,
			height : 25,
			layout : {
				type : 'hbox',
				align : 'stretch',
				defaultMargins : '0 5 0 0'
			},
			items : [{
				xtype : 'combo',
				width : 300,
				store : profileStore,
				mode : 'local',
				triggerAction : 'all',
				displayField : 'name',
				valueField : 'value',
				lazyInit : false,
				forceSelection : true,
				editable : false,
				value : _('Select one...'),
				listeners : {
					'select' : this.onConditionComboSelect,
					'scope' : this
				}
			}, {
				xtype : 'container',
				flex : 1,
				layout : 'card',
				activeItem : 0,
				items : this.createConditionContentPanels(id)
			}]
		};
	},

	/**
	 * Create a set of ContentPanels which are used to configure the various condition type.
	 * The array which is returned contains should be applied on a {@link Ext.Container} with
	 * a {@link Ext.layout.CardLayout CardLayout} to ensure only one container is visible
	 * at a time.
	 * In each container the user is able to set various configuration options for the
	 * condition type as selected in the combobox.
	 * @param {String} baseId The baseId which is used to create the id for the individual containers.
	 * @return {Array} Array of config objects to create a {@link Ext.Container}.
	 * @private
	 */
	createConditionContentPanels : function(baseId)
	{
		return [{
			xtype : 'container',
			id : baseId + '-empty'
		},{
			xtype : 'zarafa.userselectionlink',
			id : baseId + '-from'
		},{
			xtype : 'zarafa.wordselectionlink',
			id : baseId + '-senderwords'
		},{
			xtype : 'zarafa.wordselectionlink',
			id : baseId + '-words'
		},{
			xtype : 'zarafa.wordselectionlink',
			id : baseId + '-bodywords'
		},{
			xtype : 'zarafa.importancelink',
			id : baseId + '-importance'
		},{
			xtype : 'zarafa.userselectionlink',
			id : baseId + '-to'
		},{
			xtype : 'zarafa.senttomelink',
			id : baseId + '-to-me-only'
		},{
			xtype : 'zarafa.senttolink',
			id : baseId + '-to-me'
		},{
			xtype : 'zarafa.attachmentlink',
			id : baseId + '-attachment'
		},{
			xtype : 'zarafa.sentccmelink',
			id : baseId + '-cc-me'
		},{
			xtype : 'zarafa.nametocclink',
			id : baseId + '-name-to-cc'
		},{
			xtype : 'zarafa.sensitivitylink',
			id : baseId + '-sensitivity'
		},{
			xtype : 'zarafa.receivedafterlink',
			id : baseId + '-received-after'
		},{
			xtype : 'zarafa.receivedbeforelink',
			id : baseId + '-received-before'
		},{
			xtype : 'zarafa.nonelink',
			id : baseId + '-no-condition'
		}];
	},

	/**
	 * Function that can be used to add more conditions in a rule.
	 * @return {Ext.Container} The Condition Box which was inserted
	 * @private
	 */
	addConditionBox : function()
	{
		this.conditionCount++;

		var container = this.createConditionBox(this.conditionCount);
		container = this.insert(this.items.getCount() - 1, container);

		// Toggle the removeConditionBtn
		this.removeConditionBtn.setDisabled(this.conditionCount <= 1);

		this.doLayout();

		return container;
	},

	/**
	 * Function that can be used to remove a condition from a rule.
	 * This will always remove the last condition.
	 * @private
	 */
	removeConditionBox : function()
	{
		if (this.conditionCount > 1) {
			// Don't remove the last item, as that is the container
			// to add and remove conditions.
			this.remove(this.get(this.items.getCount() - 2));
			this.conditionCount--;

			// Toggle the removeConditionBtn
			this.removeConditionBtn.setDisabled(this.conditionCount <= 1);

			this.doLayout();
		}
	},

	/**
	 * {@link #addConditionBox add} or {@link #removeConditionBox remove}
	 * Condition Boxes for a rule, until the {@link #conditionCount} reaches
	 * the given count.
	 * @param {Number} count The desired number of condition boxes
	 * @private
	 */
	setConditionBoxCount : function(count)
	{
		while (count < this.conditionCount) {
			this.removeConditionBox();
		}
		while (count > this.conditionCount) {
			this.addConditionBox();
		}
	},

	/**
	 * Updates the panel by loading data from the record into the form panel.
	 * @param {Zarafa.common.rules.data.RulesRecord} record The record update the panel with.
	 * @param {Boolean} contentReset force the component to perform a full update of the data.
	 */
	update : function(record, contentReset)
	{
		this.record = record;

		if (contentReset || record.isModifiedSinceLastUpdate('rule_condition')) {
			var conditions = record.get('rule_condition');
			if (Ext.isEmpty(conditions)) {
				this.setConditionBoxCount(1);
				return;
			}

			conditions = this.getConditionsArray(conditions);

			// We have to ensure that there are sufficient condition fields
			// present in the container. When the rule doesn't have any
			// condition specified, we will create an empty condition
			var count = Math.max(1, conditions.length);
			this.setConditionBoxCount(count);

			for (var i = 0, len = conditions.length; i < len; i++) {
				// Apply the action to the corresponding container
				if (conditions[i]) {
					this.applyCondition(this.get(i), conditions[i]);
				}
			}
		}
	},

	/**
	 * Update the given {@link Zarafa.core.data.IPMRecord record} with
	 * the values from this {@link Ext.Panel panel}.
	 * @param {Zarafa.core.data.IPMRecord} record The record to update
	 */
	updateRecord : function(record)
	{
		var conditions = [];
		var conditionsValid = true;

		for (var i = 0; i < this.conditionCount; i++) {
			var panel = this.get(i);
			var combo = panel.get(0);
			var activeItem = panel.get(1).layout.activeItem;
			var condition = null;

			if (Ext.isFunction(activeItem.getCondition)) {
				condition = activeItem.getCondition();  
			}

			// If no valid condition was found, then
			// we have a problem and we can't save
			// the action. Break out of the loop
			// and invalidate the rule_condition property.
			if (!condition) {
				combo.markInvalid();
				conditionsValid = false;
			}

			conditions.push(condition);
		}

		// Check if we need to create a AND restriction
		if (conditions) {
			if (conditions.length > 1) {
				conditions = Zarafa.core.data.RestrictionFactory.createResAnd(conditions);
			} else {
				conditions = conditions[0];
			}
		}

		record.set('rule_condition', conditions);
		record.setConditionsValid(conditionsValid);
	},

	/**
	 * Convert the conditions object as stored in a {@link Zarafa.common.rules.data.RulesRecord rule}
	 * and convert it to an array of individual conditions. Each element in the returned array represents
	 * a single condition.
	 * @param {Object} conditions The condition which must be converted into an array
	 * @return {Array} The array of conditions
	 * @private
	 */
	getConditionsArray : function(conditions)
	{
		// Check the conditions, if the RES property indicates a AND
		// restriction we have to check the contents, as we need to determine
		// if this represents a single condition or a list of conditions.
		if (conditions[0] === Zarafa.core.mapi.Restrictions.RES_AND) {
			var single = false;
			var totalConditions = conditions[1].length;

			// As of now, there are three property restrictions at most in single condition.
			if (totalConditions >= 3) {
				var conditionCounter = 0;
				// Check if this AND/OR restriction represents a single
				// condition or not.
				for (var i = 0; i < totalConditions; i++) {
					var innerCondition = conditions[1][i];
					if (innerCondition) {
						if (innerCondition[0] === Zarafa.core.mapi.Restrictions.RES_PROPERTY) {
							var ulPropTagValue = innerCondition[1][Zarafa.core.mapi.Restrictions.ULPROPTAG];
							var isPrMessageToMe = (ulPropTagValue === 'PR_MESSAGE_TO_ME');
							var isPrMessageCcMe = (ulPropTagValue === 'PR_MESSAGE_CC_ME');
							var isPrMessageRecipMe = (ulPropTagValue === 'PR_MESSAGE_RECIP_ME');
							var isPrDisplayCc = (ulPropTagValue === 'PR_DISPLAY_CC');

							if (isPrMessageToMe || isPrMessageCcMe || isPrMessageRecipMe || isPrDisplayCc) {
								conditionCounter++;
							}
						} else if (innerCondition[0] === Zarafa.core.mapi.Restrictions.RES_NOT) {
							innerCondition = innerCondition[1];
							if ('PR_DISPLAY_TO' === innerCondition[1][Zarafa.core.mapi.Restrictions.ULPROPTAG]) {
								conditionCounter++;
							}
						}

						if (conditionCounter === 3) {
							single = true;
							break;
						}
					}
				}
			}

			// Now return the conditions
			if (single) {
				conditions = [ conditions ];
			} else {
				conditions = conditions[1];
			}
		} else {
			// Single condition, just convert it to an array
			conditions = [ conditions ];
		}

		return conditions;
	},

	/**
	 * Load a Condition from a {@Link Zarafa.common.rules.data.RulesRecord} and apply it
	 * onto the {@link Ext.Container} which was created by {@link #addConditionBox}. 
	 * @param {Ext.Container} panel The container on which the condition will be loaded
	 * @param {Object} condition The condition which should be loaded
	 * @private
	 */
	applyCondition : function(panel, condition)
	{
		var conditionFlag = this.getConditionFlagFromCondition(condition);
		var combo = panel.get(0);
		var content = panel.get(1);
		var store = combo.store;

		// Apply the correct value to the combobox.
		var index = store.findExact(combo.valueField, conditionFlag);
		if (index >= 0) {
			var record = store.getAt(index);
			combo.setValue(conditionFlag);
			this.onConditionComboSelect(combo, record, index);
		} else {
			conditionFlag = Zarafa.common.rules.data.ConditionFlags.UNKNOWN;
			combo.setValue(_('Unknown condition'));
			combo.markInvalid(_('This condition for the current rule is unknown'));
		}

		// Fill the content with the data from the condition
		var layout = content.getLayout();
		switch (conditionFlag) {
			case Zarafa.common.rules.data.ConditionFlags.UNKNOWN:
			/* falls through*/
			default:
				break;
			case Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS:
			case Zarafa.common.rules.data.ConditionFlags.BODY_WORDS:
			case Zarafa.common.rules.data.ConditionFlags.IMPORTANCE:
			case Zarafa.common.rules.data.ConditionFlags.RECEIVED_AFTER:
			case Zarafa.common.rules.data.ConditionFlags.RECEIVED_BEFORE:
			case Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM:
			case Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS:
			case Zarafa.common.rules.data.ConditionFlags.SENSITIVITY:
			case Zarafa.common.rules.data.ConditionFlags.SENT_TO:
			case Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME:
			case Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY:
			case Zarafa.common.rules.data.ConditionFlags.SENT_CC_ME:
			case Zarafa.common.rules.data.ConditionFlags.NONE:
				layout.activeItem.setCondition(conditionFlag, condition);
				break;
		}
	},

	/**
	 * Read a Condition object as located in the {@link Zarafa.common.rules.data.RulesRecord Rule}
	 * and convert it to the corresponding ConditionFlag which properly represents the condition.
	 * @param {Object} condition The condition which should be converted to a Condition Flag
	 * @return {Zarafa.common.rules.data.ConditionFlags} The Condition Flag
	 * @private
	 */
	getConditionFlagFromCondition : function(condition)
	{
		var Restrictions = Zarafa.core.mapi.Restrictions;

		switch (condition[0]) {
			case Restrictions.RES_COMMENT:
				switch (condition[1][Restrictions.RESTRICTION][1][Restrictions.ULPROPTAG]) {
					case 'PR_SENDER_SEARCH_KEY':
						return Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM;
					default:
						return Zarafa.common.rules.data.ConditionFlags.UNKNOWN;
				}
				break;
			case Restrictions.RES_CONTENT:
			case Restrictions.RES_PROPERTY:
			case Restrictions.RES_SUBRESTRICTION:
			case Restrictions.RES_BITMASK:
				switch (condition[1][Restrictions.ULPROPTAG]) {
					case 'PR_BODY':
						return Zarafa.common.rules.data.ConditionFlags.BODY_WORDS;
					case 'PR_SUBJECT':
						return Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS;
					case 'PR_IMPORTANCE':
						return Zarafa.common.rules.data.ConditionFlags.IMPORTANCE;
					case 'PR_MESSAGE_RECIPIENTS':
						return Zarafa.common.rules.data.ConditionFlags.SENT_TO;
					case 'PR_MESSAGE_TO_ME':
						return Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME;
					case 'PR_SENDER_SEARCH_KEY':
						return Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS;
					case 'PR_MESSAGE_FLAGS':
						return Zarafa.common.rules.data.ConditionFlags.ATTACHMENT;
					case 'PR_MESSAGE_RECIP_ME':
						return Zarafa.common.rules.data.ConditionFlags.NAME_TO_CC;
					case 'PR_SENSITIVITY':
						return Zarafa.common.rules.data.ConditionFlags.SENSITIVITY;
					case 'PR_MESSAGE_DELIVERY_TIME':
						if (condition[1][1] === Restrictions.RELOP_LT) {
							return Zarafa.common.rules.data.ConditionFlags.RECEIVED_BEFORE;
						} else if (condition[1][1] === Restrictions.RELOP_GT) {
							return Zarafa.common.rules.data.ConditionFlags.RECEIVED_AFTER;
						}
						/* falls through*/
					default:
						return Zarafa.common.rules.data.ConditionFlags.UNKNOWN;
				}
			/* falls through*/
			case Restrictions.RES_AND:
				for (var i = 0, len = condition[1].length; i < len; i++) {
					var sub = condition[1][i];

					// PR_MESSAGE_CC_ME is only used in the SENT_CC_ME restriction for now
					if (sub[0] === Restrictions.RES_PROPERTY &&
					    sub[1][Restrictions.ULPROPTAG] === 'PR_MESSAGE_CC_ME') {
						return Zarafa.common.rules.data.ConditionFlags.SENT_CC_ME;
					}
					// Check if the RES_AND contains the restriction for PR_MESSAGE_TO_ME,
					// this indicates that this restriction is the SENT_TO_ME_ONLY condition
					if (sub[0] === Restrictions.RES_PROPERTY &&
					    sub[1][Restrictions.ULPROPTAG] === 'PR_MESSAGE_TO_ME') {
						return Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY;
					}
				}
				return Zarafa.common.rules.data.ConditionFlags.UNKNOWN;
			case Restrictions.RES_OR:
				for (var i = 0, len = condition[1].length; i < len; i++) {
					var sub = condition[1][i];
					var type = this.getConditionFlagFromCondition(sub);
					if (type !== Zarafa.common.rules.data.ConditionFlags.UNKNOWN) {
						return type;
					}
				}
				return Zarafa.common.rules.data.ConditionFlags.UNKNOWN;
			case Restrictions.RES_EXIST:
				return Zarafa.common.rules.data.ConditionFlags.NONE;
			default:
				return Zarafa.common.rules.data.ConditionFlags.UNKNOWN;
		}
	},

	/**
	 * The event handler for the {@link Ext.form.ComboBox#select} event for the combobox for
	 * a particular action. This will update the corresponding content panel to show the correct
	 * content type.
	 * @param {Ext.form.ComboBox} combo The combobox which fired the event
	 * @param {Ext.data.Record} record The record which was selected from the combobox
	 * @param {Number} index The selected index from the combobox list
	 * @private
	 */
	onConditionComboSelect : function(combo, record, index)
	{
		var panel = combo.ownerCt;
		var content = panel.get(1);

		var layout = content.getLayout();
		var value = record.get(combo.valueField);

		switch (value) {
			case Zarafa.common.rules.data.ConditionFlags.UNKNOWN:
			/* falls through*/
			default:
				layout.setActiveItem(panel.id + '-empty');
				break;
			case Zarafa.common.rules.data.ConditionFlags.RECEIVED_FROM:
				layout.setActiveItem(panel.id + '-from');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SENDER_WORDS:
				layout.setActiveItem(panel.id + '-senderwords');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SUBJECT_WORDS:
				layout.setActiveItem(panel.id + '-words');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.BODY_WORDS:
				layout.setActiveItem(panel.id + '-bodywords');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.IMPORTANCE:
				layout.setActiveItem(panel.id + '-importance');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SENT_TO:
				layout.setActiveItem(panel.id + '-to');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME_ONLY:
				layout.setActiveItem(panel.id + '-to-me-only');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SENT_TO_ME:
				layout.setActiveItem(panel.id + '-to-me');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.ATTACHMENT:
				layout.setActiveItem(panel.id + '-attachment');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SENSITIVITY:
				layout.setActiveItem(panel.id + '-sensitivity');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.SENT_CC_ME:
				layout.setActiveItem(panel.id + '-cc-me');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.NAME_TO_CC:
				layout.setActiveItem(panel.id + '-name-to-cc');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.RECEIVED_AFTER:
				layout.setActiveItem(panel.id + '-received-after');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.RECEIVED_BEFORE:
				layout.setActiveItem(panel.id + '-received-before');
				layout.activeItem.setCondition(value);
				break;
			case Zarafa.common.rules.data.ConditionFlags.NONE:
				layout.setActiveItem(panel.id + '-no-condition');
				layout.activeItem.setCondition(value);
				break;
		}
	}
});

Ext.reg('zarafa.rulesconditioncontainer', Zarafa.common.rules.dialogs.RulesConditionContainer);